JAVA并发编程之锁
1、乐观锁和悲观锁
1.1、悲观锁
- 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会加锁,确保数据不会别的线程修改。
- synchronized关键字和Lock的实现类都是悲观锁。
- 适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再造作同步资源。
- 狼性锁
1.2、乐观锁
-
认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。
-
在JAVA中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。
-
判断规则:
(1)版本号机制Version
(2) 最常采用的是CAS算法,JAVA原子类中的递增操作就通过CAS自旋实现的。 -
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升。
-
乐观锁直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再努力就是一句话:佛系锁。
2、线程八锁
- 标准访问有a、b两个线程,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone{public synchronized void sendEmail(){System.out.println("-------------sendEmail");}public synchronized void sendSMS(){System.out.println("-----------sendSMS");}
}public class Lock8Demo {public static void main(String[] args){Phone phone = new Phone();new Thread(phone::sendEmail,"a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(phone::sendSMS,"b").start();}}
synchronized 加在普通的方法上面,是对象锁,由于a线程先启动,在调用sendEmail()方法时先拿到了这个phone对象的锁,所以肯定先执行此方法,此方法执行完后,a线程释放phone对象锁,然后b线程才能拿到phone对象锁,去执行sendSMS()方法。
2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是hello?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public synchronized void sendSMS(){System.out.println("-----------sendSMS");}
}public class Lock8Demo {public static void main(String[] args){Phone phone = new Phone();new Thread(phone::sendEmail,"a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(phone::sendSMS,"b").start();}}
synchronized 加在普通的方法上面,是对象锁,由于a线程先启动,在调用sendEmail()方法时先拿到了这个phone对象的锁,此时虽然暂停了3秒钟,但phone对象锁一直被a线程持有,所以肯定还是先执行sendEmail()方法,此方法执行完后,a线程释放phone对象锁,然后b线程才能拿到phone对象锁,去执行sendSMS()方法。
- 添加一个普通的hello方法,请问先打印邮件还是hello?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public synchronized void sendSMS(){System.out.println("-----------sendSMS");}public void hello(){System.out.println("----------hello");}
}public class Lock8Demo {public static void main(String[] args){Phone phone = new Phone();new Thread(phone::sendEmail,"a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(phone::hello,"b").start();}}
a线程拿到phone对象锁以后,去执行sendEmail()方法,但中间暂停了3秒钟,同时b线程执行的是普通的hello(),所以不需要获取phone对象锁,就可以立即执行hello()方法,而不用等到a线程释放phone对象锁,再去获取phone对象锁。
- 有两个手机对象,请问先打印邮件还是短信?
/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public synchronized void sendSMS(){System.out.println("-----------sendSMS");}public void hello(){System.out.println("----------hello");}
}public class Lock8Demo {public static void main(String[] args){Phone phone = new Phone();Phone phoneOne = new Phone();new Thread(phone::sendEmail,"a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(phoneOne::sendSMS,"b").start();}}
a线程持有的是phone对象的锁,并不会影响到b线程获取phoneOne对象的锁,所以是两个线程拿到了两个不同的对象锁,互不干扰,各自执行各自的。
- 有两个静态同步方法,有一个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public static synchronized void sendSMS(){System.out.println("-----------sendSMS");}public void hello(){System.out.println("----------hello");}
}public class Lock8Demo {public static void main(String[] args){Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone.sendSMS();},"b").start();}}
静态方法属于类,在静态方法上加上synchronized关键字,线程a先启动获得的就是类锁,这时候锁住的是整个类的资源,所以线程b必须等待线程a释放类锁以后才能获取到类锁。
6. 有两个静态同步方法,有两个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public static synchronized void sendSMS(){System.out.println("-----------sendSMS");}public void hello(){System.out.println("----------hello");}
}public class Lock8Demo {public static void main(String[] args){Phone phone = new Phone();Phone phoneOne = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phoneOne.sendSMS();},"b").start();}}
静态方法属于类,在静态方法上加上synchronized关键字,线程a先启动获得的就是类锁,这时候锁住的是整个类的资源,所以线程b必须等待线程a释放类锁以后才能获取到类锁,即使创建了两个不同的对象,但这两个对象都属于同一个类,所以b线程还是要等待a线程释放整个类锁后,才能获得类锁,再执行响应的资源。
- 有一个静态同步方法,有一个普通同步方法,有一个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone {public static synchronized void sendEmail() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public synchronized void sendSMS() {System.out.println("-----------sendSMS");}public void hello() {System.out.println("----------hello");}
}public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone.sendSMS();}, "b").start();}}
获取静态同步方法,需要持有类锁,普通同步方法,获取的是对象锁,所以b线程不需要等到a线程释放完类锁,就能能获取到对象锁。
- 有一个静态同步方法,有一个普通同步方法,有两个手机对象,请问先打印邮件还是短信?
import java.util.concurrent.TimeUnit;/*** @Description: 线程8锁* @Author: yangyb* @Date:2022/9/28 22:25* Version: 1.0**/class Phone {public static synchronized void sendEmail() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------sendEmail");}public synchronized void sendSMS() {System.out.println("-----------sendSMS");}public void hello() {System.out.println("----------hello");}
}public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();Phone phoneOne = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停两毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phoneOne.sendSMS();}, "b").start();}}
获取静态同步方法,需要持有类锁,普通同步方法,获取的是对象锁,所以b线程不需要等到a线程释放完类锁,就能能获取到对象锁。
3、公平锁和非公平锁
3.1、非公平锁
import java.util.concurrent.locks.ReentrantLock;/*** @Description: 非公平锁* @Author: yangyb* @Date:2022/10/5 11:38* Version: 1.0**/
class Ticket {private int number = 20;// 默认为非公平锁ReentrantLock lock = new ReentrantLock();public void sale() {lock.lock();try {if (number > 0) {System.out.println(Thread.currentThread().getName() + "售出了第" + number-- + "张票,还剩" + number + "票");}} finally {lock.unlock();}}
}public class SaleTicketDemo {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(() -> {for (int i = 0; i < 55; i++) {ticket.sale();}}, "a").start();new Thread(() -> {for (int i = 0; i < 55; i++) {ticket.sale();}}, "b").start();new Thread(() -> {for (int i = 0; i < 55; i++) {ticket.sale();}}, "c").start();}}
3.2、公平锁
import java.util.concurrent.locks.ReentrantLock;/*** @Description: 公平锁* @Author: yangyb* @Date:2022/10/5 11:38* Version: 1.0**/
class Ticket {private int number = 20;// 公平锁ReentrantLock lock = new ReentrantLock(true);public void sale() {lock.lock();try {if (number > 0) {System.out.println(Thread.currentThread().getName() + "售出了第" + number-- + "张票,还剩" + number + "票");}} finally {lock.unlock();}}
}public class SaleTicketDemo {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(() -> {for (int i = 0; i < 22; i++) {ticket.sale();}}, "a").start();new Thread(() -> {for (int i = 0; i < 22; i++) {ticket.sale();}}, "b").start();new Thread(() -> {for (int i = 0; i < 22; i++) {ticket.sale();}}, "c").start();}}
3.3、为什么会有公平锁和非公平锁的设计?为什么默认非公平?
- 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从cpu的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少cpu空闲状态时间。
- 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
3.4、何时用公平锁?何时用非公平锁?
如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用。
4、可重入锁(又名递归锁)
4.1、概念
- 指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
例如:如果是一个有symchronized修饰得递归调用方法,程序第二次进入被自己阻塞了,这不成自己阻塞自己了吗?显然就不合常规的处理逻辑了。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁。 - 总结:一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。即自己可以获取自己的内部锁。
4.2、隐式锁
隐式锁(即synchronized关键字使用的锁)默认是可重入锁。指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫可重入锁。简单的lai说就是,在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的。
/*** @Description: 隐式锁synchronized,同步代码块* @Author: yangyb* @Date:2022/10/6 19:08* Version: 1.0**/
public class ReEntryLockDemo {public static void main(String[] args) {Object object = new Object();new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "\t-------外层调用");synchronized (object) {System.out.println(Thread.currentThread().getName() + "\t-------中层调用");synchronized (object) {System.out.println(Thread.currentThread().getName() + "\t-------内层调用");}}}}, "t1").start();}}
/*** @Description: 隐式锁synchronized,同步方法* @Author: yangyb* @Date:2022/10/6 19:08* Version: 1.0**/
public class ReEntryLockDemo {public synchronized void m1(){System.out.println("---m1 "+Thread.currentThread().getName()+"\t ------come in!");m2();System.out.println("---m1 "+Thread.currentThread().getName()+"\t ------end m1!");}public synchronized void m2(){System.out.println("---m2 "+Thread.currentThread().getName()+"\t ------come in!");m3();}public synchronized void m3(){System.out.println("---m3 "+Thread.currentThread().getName()+"\t ------come in!");}public static void main(String[] args) {ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();new Thread(reEntryLockDemo::m1,"t1").start();}}
4.2.1、Synchronized可重入的实现原理
4.3、显示锁(ReentrantLock)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @Description: 显示锁ReentrantLock* @Author: yangyb* @Date:2022/10/6 19:08* Version: 1.0**/
public class ReEntryLockDemo {static Lock lock = new ReentrantLock();public static void main(String[] args) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "\t 外层调用lock!");lock.lock();try {System.out.println(Thread.currentThread().getName() + "\t 内层调用lock!");} finally {// 这里故意注释掉,让实现加锁次数和释放锁的次数不一样// 由于枷锁次数和释放锁次数不一样,第二个线程始终无法获取到锁,导致一直在等待//lock.unlock();}} finally {lock.unlock();}}, "t1").start();new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "\t 调用lock!");} finally {lock.unlock();}}, "t2").start();}
}
5、死锁及排查
5.1、死锁
5.1.1、概念
5.1.2、代码示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @Description: 死锁* @Author: yangyb* @Date:2022/10/6 20:19* Version: 1.0**/
public class DeadlockDemo {static Lock lockA = new ReentrantLock();static Lock lockB = new ReentrantLock();public static void main(String[] args) {new Thread(() -> {lockA.lock();try {System.out.println(Thread.currentThread().getName() + "\t 线程持有lockA锁,去尝试持有lockB锁");lockB.lock();try {System.out.println(Thread.currentThread().getName() + "\t 尝试获取并持有了lockB锁");} finally {lockB.unlock();}} finally {lockA.unlock();}}, "A").start();new Thread(() -> {lockB.lock();try {System.out.println(Thread.currentThread().getName() + "\t 线程持有lockB锁,去尝试持有lockA锁");lockA.lock();try {System.out.println(Thread.currentThread().getName() + "\t 尝试获取并持有了lockA锁");} finally {lockA.unlock();}} finally {lockB.unlock();}}, "B").start();}
}
5.1.3、产生死锁的主要原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
5.2、死锁排查
5.2.1、在终端使用命令行
-
jps -l
-
jstack 17988
5.2.2、图形化
6、为什么任何一个对象都可以成为锁?
7、LockSupport与线程中断
7.1、线程中断
7.1.1、什么是中断机制?
首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了。
其次,在java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断,即中断标识协商机制。
中断只是一种协作协商机制,Java中没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true,此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示 线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
7.1.2、中断线程API常见的三大方法
- 如何停止中断允许中的线程?
(1)同过一个volatile 变量实现
/*** @Description: 线程中断* @Author: yangyb* @Date:2022/10/7 1:40* Version: 1.0**/
public class InterruptedDemo {static volatile boolean isStop = false;public static void main(String[] args) {new Thread(() -> {while (true) {if (isStop) {System.out.println(Thread.currentThread().getName() + "\t isStop被修改为" + isStop + "线程终止!");break;}System.out.println(Thread.currentThread().getName() + "\t 线程正在执行!");}}, ":t1").start();try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {isStop = true;}, "t2").start();}}
(2)同过AtomicBoolean 实现
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;/*** @Description: 线程中断* @Author: yangyb* @Date:2022/10/7 1:40* Version: 1.0**/
public class InterruptedDemo {static AtomicBoolean atomicBoolean=new AtomicBoolean(false);public static void main(String[] args) {new Thread(() -> {while (true) {if (atomicBoolean.get()) {System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为" + atomicBoolean.get() + "线程终止!");break;}System.out.println(Thread.currentThread().getName() + "\t 线程正在执行!");}}, ":t1").start();try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {atomicBoolean.set(true);}, "t2").start();}}
(3)通过Thread类自带的pai来实现
import java.util.concurrent.TimeUnit;/*** @Description: 线程中断* @Author: yangyb* @Date:2022/10/7 1:40* Version: 1.0**/
public class InterruptedDemo {public static void main(String[] args) {Thread t1=new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为" + true + "线程终止!");break;}System.out.println(Thread.currentThread().getName() + "\t 线程正在执行!");}}, ":t1");t1.start();try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(t1::interrupt, "t2").start();}}
源码分析:
2. 当前线程的中断标识为true,是不是线程就立刻停止?
import java.util.concurrent.TimeUnit;/*** @Description: 线程中断* @Author: yangyb* @Date:2022/10/7 1:40* Version: 1.0**/
public class InterruptedDemo {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 300; i++) {System.out.println("i=" + i);}System.out.println(Thread.currentThread().getName() + "\t 线程调用t1.interrupt()方法后的中断标识为" + Thread.currentThread().isInterrupted());}, ":t1");t1.start();System.out.println(t1.getName() + "\t 线程中默认的中断标识为" + t1.isInterrupted());//falsetry {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}t1.interrupt();//trueSystem.out.println(Thread.currentThread().getName() + "\t 线程调用t1.interrupt()方法后的第一次中断标识为" + t1.isInterrupted());try {TimeUnit.MILLISECONDS.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 中断标识重置为false,中断不活动的线程不会产生任何影响System.out.println(Thread.currentThread().getName() + "\t 线程调用t1.interrupt()方法,并等待t1线程执行完所有的逻辑后中断标识为" + t1.isInterrupted());}}
此时t1线程的中断标识已经置为true,然而并没有立即中断线程,而是继续执行了所有逻辑
注意:
总结:
中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断。
- 静态方法Thread.interrupted()谈谈你的理解?
示例:
结论:
静态方法interrupted将会清除中断状态(传入的参数ClearInterrupted为true),实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)。
4.三大方法对比总结
7.2、LockSupport
7.2.1、概念
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。归根结底,LockSupport调用的Unsafe中的native代码。
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。
park() 和 unpark()不会有 Thread.suspend 和 Thread.resume 所可能引发的死锁问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。
如果调用线程被中断,则park方法会返回。同时park也拥有可以设置超时时间的版本。
7.2.2、线程等待唤醒机制
- 线程等待和唤醒的方法
(1)Object的wait和notify
(2)Condition的await()和signal()方法
(3)LockSupport的park()和unpark()方法
park()和unpark()顺序执行
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;/*** @Description: LockSupport* @Author: yangyb* @Date:2022/10/7 7:38* Version: 1.0**/
public class LockSupportDemo {public static void main(String[] args){Thread t1=new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t 线程---come in!");LockSupport.park();System.out.println(Thread.currentThread().getName()+"\t 线程---被唤醒");},"t1");t1.start();try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName()+"\t 线程发出通知给t1线程");},"t2").start();}
}
先执行unpark(),再执行unpark()
/*** @Description: LockSupport* @Author: yangyb* @Date:2022/10/7 7:38* Version: 1.0**/
public class LockSupportDemo {public static void main(String[] args) {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t 线程---come in!");LockSupport.park();System.out.println(Thread.currentThread().getName() + "\t 线程---被唤醒");}, "t1");t1.start();new Thread(() -> {LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + "\t 线程发出通知给t1线程");}, "t2").start();}
}
7.2.3、总结
7.2.3、面试题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;/*** @Description: LockSupport* @Author: yangyb* @Date:2022/10/7 7:38* Version: 1.0**/
public class LockSupportDemo {public static void main(String[] args) {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t 线程---come in!");LockSupport.park();LockSupport.park();System.out.println(Thread.currentThread().getName() + "\t 线程---被唤醒");}, "t1");t1.start();new Thread(() -> {LockSupport.unpark(t1);LockSupport.unpark(t1);LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + "\t 线程发出通知给t1线程");}, "t2").start();}
}
8、Java内存模型即JMM
8.1、计算机硬件存储体系
8.2、JMM
8.2.1、JMM简介
8.2.2、JMM规范下,三大特性
(1)可见性
指当一个线程修改了某一个共享变量的值,其他线程能否立即知道该变更。JMM规定所有的变量都存储在主内存中。
(2)原子性
指一个操作是不可打断的,即多线程环境下,操作不能被其他线程干扰。
(3)有序性
8.2.3、JMM规范下,多线程对变量的读写过程
8.2.3、JMM规范下,多线程先行发生原则之happens-before
(1)简介
(2)happens-before总原则
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行最终的结果一致,那么这种重排序并不非法。
(2)happens-before的8条规则
- 次序规则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
- 锁定规则:一个unlock操作先行发生于后面(这里的“后面”是指时间上的先后)对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出来操作A先行发生于操作C。
- 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
- 线程中断规则(Thread Interruption Rule):
- 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否已经终止执行。
- 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
(4)总结
9、volatile
9.1、volatile之两大特性
被volatile修饰的变量有两大特点:可见性和有序性
- 可见性:保证不同线程对某个变量完成操作后的结果对其它线程及时可见,即该共享变量一旦改变所有线程立即可见。
9.1.1、volatile内存语义
9.1.2、vlolatile如何保证可见性和有序性?
内存屏障(Memory Barrier)
9.2、内存屏障(Memory Barrier)
9.3、读写屏障之插入策略
- 读屏障
- 写屏障
- volatile变量的读写过程:JAVA内存模型中定义的8种每个线程自己的工作内存与主物理内存之间的原子操作。
9.4、volatile无原子性
9.5、指令禁重排
9.6、volatile使用场景
- 单一赋值可以,但是含复合运算赋值不可以(i++)类
volatile int a=10;
volatile boolean flag=false;
- 状态标志,判断业务是否结束
import java.util.concurrent.TimeUnit;/*** @Description: volatile使用:作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,列如完成初始化或任务结束* 理由:状态标志并不依赖于程序内任何其他状态,且通常只有一种状态装换* 例子:判断业务是否结束* @Author: yangyb* @Date:2022/10/7 17:22* Version: 1.0**/
public class UseVolatileDemo {private volatile static boolean flog=true;public static void main(String[] args){new Thread(()->{while (flog){System.out.println("do something");}},"t1").start();try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{flog=false;},"t2").start();}}
- 开销较低的读、写锁策略
/** 使用:当读远多于写,结合使用内部锁和volatile变量来减少同步的开销* 理由:利用volatile保证读取操作的可见性,利用synchronized保证复合操作的原子性* */public class Counter{private volatile int value;public int getValue(){return value; // 利用volatile保证读取操作的可见性}public synchronized int increment(){return value++; // 利用synchronized保证复合操作的原子性}}
- DCL双检锁的发布
单线程:
多线程:
解决方法:
9.7、总结
10、CAS
10.1、CAS简介
10.1.1、说明
- compare and swap的缩写,中文翻译成比较并交换,实现并发算法时场用到的一种技术。它包含三个操作数——内存位置、预期原值及更新值。
- 执行CAS操作的时候,将内存位置的值与预期原值比较:如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
10.1.2、原理
CAS有三个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来。
10.1.2、硬件级别保证
源码分析
/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
问题:Unsafe类是什么?
10.2、CAS底层原理-Unsafe类
10.2.1、源码分析
10.2.2、总结
10.3、CAS之原子引用 AtomicReference
import java.util.concurrent.atomic.AtomicReference;/*** @Description: 原子引用* @Author: yangyb* @Date:2022/10/7 21:17* Version: 1.0**/class User{private int age;private String name;public User(int age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return "User{" +"age=" + age +", name='" + name + '\'' +'}';}
}public class AtomicReferenceDemo {public static void main(String[] args){AtomicReference<User> userAtomicReference = new AtomicReference<>();User zhangSan = new User(10, "张三");User liSi = new User(12, "李四");userAtomicReference.set(zhangSan);System.out.println(userAtomicReference.compareAndSet(zhangSan,liSi)+"\t"+userAtomicReference.get().toString());System.out.println(userAtomicReference.compareAndSet(zhangSan,liSi)+"\t"+userAtomicReference.get().toString());}}
10.4、CAS与自旋锁
10.4.1、简介
10.4.2、手写自旋锁,借鉴CAS思想
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;/*** @Description: 自旋锁实现* 目标:实现一个自旋锁,复习CAS思想* 自旋锁的好处:循环比较获取没有类似wait的阻塞* 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B线程随后进来发现当前线程持有锁,* 所以只能通过自旋等待,直到A释放锁后B随后抢到* @Author: yangyb* @Date:2022/10/8 20:50* Version: 1.0**/
public class SpinLockDemo {AtomicReference<Thread> atomicReference = new AtomicReference<>();public void lock() {// 获取当前线程Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName() + "\t 线程come in!");while (!atomicReference.compareAndSet(null, thread)) {System.out.println(Thread.currentThread().getName() + "\t 线程 自旋中!,等待A线程释放锁");}System.out.println(Thread.currentThread().getName() + "\t 线程 自旋结束");}public void unlock() {// 获取当前线程Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread, null);System.out.println(Thread.currentThread().getName() + "\t task over,unlock!");}public static void main(String[] args) {SpinLockDemo spinLockDemo = new SpinLockDemo();new Thread(() -> {spinLockDemo.lock();// 休息5秒钟try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.unlock();}, "A").start();// 休息1秒钟,保证A线程先启动try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {spinLockDemo.lock();// 休息5秒钟try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.unlock();}, "B").start();}}
10.5、CAS两大缺点
10.5.1、循环时间长,CPU开销大
10.5.2、ABA问题的产生
10.6、邮戳AtomicStampedReference
(1)没有用AtomicStampedReference,会发生ABA问题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** @Description: ABADemo* @Author: yangyb* @Date:2022/10/8 21:48* Version: 1.0**/
public class ABADemo {static AtomicInteger atomicInteger = new AtomicInteger(100);public static void main(String[] args) {new Thread(() -> {atomicInteger.compareAndSet(100, 101);try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}atomicInteger.compareAndSet(101, 100);}, "A").start();new Thread(() -> {try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicInteger.compareAndSet(100, 2022) + "\t" + atomicInteger.get());}, "B").start();}
}
A线程中间悄无声息的替换了数据,B没有
(2)加上AtomicStampedReference,解决ABA问题
/*** @Description: ABADemo* @Author: yangyb* @Date:2022/10/8 21:48* Version: 1.0**/
public class ABADemo {static AtomicInteger atomicInteger = new AtomicInteger(100);static AtomicStampedReference stampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {new Thread(() -> {int stamp = stampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);//暂停一下,保证后面的B线程拿到的版本号和我一样try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t" + "2次版本号:" + stampedReference.getStamp());stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t" + "3次版本号:" + stampedReference.getStamp());}, "A").start();new Thread(() -> {int stamp = stampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);// 暂停一秒钟,等待上面的t3线程,发生ABA问题try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}boolean b = stampedReference.compareAndSet(100, 202, stamp, stampedReference.getStamp() + 1);System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());}, "B").start();}
}
11、原子类
11.1、基本类型原子类
- AtomicInterger
- AtomicBoolean
- AtomicLong
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;/*** @Description: TODO* @Author: yangyb* @Date:2022/10/9 22:00* Version: 1.0**/class MyNumber {AtomicInteger atomicInteger = new AtomicInteger();public void addPlusPlus() {atomicInteger.getAndIncrement();}
}public class AtomicIntegerDemo {public static final int SIZE = 50;public static void main(String[] args) throws InterruptedException {MyNumber myNumber = new MyNumber();CountDownLatch countDownLatch = new CountDownLatch(SIZE);for (int i = 1; i < SIZE; i++) {new Thread(() -> {try {for (int j = 1; j < 1000; j++) {myNumber.addPlusPlus();}} finally {countDownLatch.countDown();}}, String.valueOf(i)).start();}countDownLatch.await();System.out.println(Thread.currentThread().getName() + "\t" + "result: " + myNumber.atomicInteger.get());}
}
11.2、数组类型原子类
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
11.3、引用类型原子类
-
AtomicReference
-
AtomicStampedReference
-
AtomicMarkableReference
11.4、对象的属性修改原子类
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicReferenceFieldUpdater
11.5、原子操作增强类
12、ThreadLocal
12.1、简介
ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实列的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(列入,用户ID或事物ID)与线程关联起来。
/*** @Description: ThreadLocal* @Author: yangyb* @Date:2022/10/10 21:58* Version: 1.0**/
// 资源类
class House{int saleCount =0;public synchronized void saleHouse(){++saleCount;}ThreadLocal<Integer> saleVolume=ThreadLocal.withInitial(()->0);public void saleVolumeByThreadLocal(){saleVolume.set(1+saleVolume.get());}
}
public class ThreadLocalDemo {public static void main(String[] args){House house = new House();for(int i=1;i<=5;i++){new Thread(()->{try {int size=new Random().nextInt(5)+1;for(int j=1;j<=size;j++){house.saleHouse();house.saleVolumeByThreadLocal();}System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套:"+house.saleVolume.get());}finally {house.saleVolume.remove();}},String.valueOf(i)).start();}try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套:"+house.saleCount);}}
12.2、ThreadLocal规范要求
/*** @Description: ThreadLocal规范* @Author: yangyb* @Date:2022/10/10 22:37* Version: 1.0**/
class MyData{ThreadLocal<Integer> threadLocalField=ThreadLocal.withInitial(()->0);public void add(){threadLocalField.set(1+threadLocalField.get());}
}
public class ThreadLocalDemoTwo {public static void main(String[] args){MyData myData = new MyData();ExecutorService threadPool = Executors.newFixedThreadPool(3);try {for(int i=0;i<10;i++){threadPool.submit(()->{Integer beforeInteger = myData.threadLocalField.get();myData.add();Integer afterInteger = myData.threadLocalField.get();System.out.println(Thread.currentThread().getName()+"\t"+"beforeInteger:"+beforeInteger+"\t"+"afterInteger:"+afterInteger);});}}finally {threadPool.shutdown();}}}
使用remove()方法以后:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @Description: ThreadLocal规范* @Author: yangyb* @Date:2022/10/10 22:37* Version: 1.0**/
class MyData{ThreadLocal<Integer> threadLocalField=ThreadLocal.withInitial(()->0);public void add(){threadLocalField.set(1+threadLocalField.get());}
}
public class ThreadLocalDemoTwo {public static void main(String[] args){MyData myData = new MyData();ExecutorService threadPool = Executors.newFixedThreadPool(3);try {for(int i=0;i<10;i++){threadPool.submit(()->{try {Integer beforeInteger = myData.threadLocalField.get();myData.add();Integer afterInteger = myData.threadLocalField.get();System.out.println(Thread.currentThread().getName()+"\t"+"beforeInteger:"+beforeInteger+"\t"+"afterInteger:"+afterInteger);}finally {myData.threadLocalField.remove();}});}}finally {threadPool.shutdown();}}}
12.3、总结
12.4、Thread、ThreadLocal、ThreadLocalMap三者的关系
12.5、弱引用的引出
12.6、强引用
12.7、软引用
12.8、弱引用
12.9、虚引用
12.10、四中引用总结
12.11、ThreadLocal为何用弱引用
12.12、总结
13、对象内存布局和对象头
13.1、简介
13.2、对象头
1、对象标记Mark Word
总结:
2、类元信息(又叫类型指针)
总结:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
3、对象头有多大?
13.3、实例数据和对齐填充
1、实例数据
2、对齐填充
13.4、对象分代年龄
14、synchronized与锁升级
14.1、简介
synchronized锁:
14.2、synchronized锁升级流程
14.3、无锁
14.4、偏向锁
理论实现:
案例说明:
重要参数说明:
代码示例:
14.5、偏向锁撤销
偏向锁废弃:
14.6、轻量级锁
轻量级锁的获取:
补充:
14.7、重锁
14.8、锁升级后和hashCode的关系
14.9、各种锁优缺点总结
14.10、锁消除
14.11、锁粗化
14.12、总结
15、AQS
15.1、简介
15.2、AQS作用
15.2.1、源码查看
15.3、AQS之state和LCH队列
15.4、AQS自身属性和Node节点
15.5、AQS源码分析
15.5.1、非公平锁
15.6、AQS源码总结
。。。。。。。。。。。。。。。。。。。。。。。。。。。
16、读写锁
16.1、简介
16.2、锁演化
16.2.1、读写锁的意义和特点
16.3、锁降级
16.4、不可锁升级
结论:
17、stampedLock
17.1、简介
17.2、stampedLock锁特点
缺点:
相关文章
- 电子拣货标签13代系统简介
CK_Label_v13一、产品参数 1. 电池供电版 产品型号 CK_Label_v13 尺寸 70x34.7x13.6mm 屏幕尺寸 2.1 inch 分辨率 250*122 像素密度 130dpi 显示技术 电子墨水屏显示 显示颜色 黑/白 外观颜色 黑色 按键 1 指示灯 1 RGB灯 灯光颜色 7种(红/绿/蓝/黄/紫/白…...
2023/3/28 15:49:24 - Ubuntu下安装和编译onnxruntime
onnxruntime是一种用于onnx格式的机器学习模型的高性能推理引擎,适用于Linux,Windows、Mac及嵌入式设备。这篇博客记录了编译onnxruntime的步骤及在此过程种遇到的问题及解决方法。1 下载git clone --depth 1 --branch v1.12.1 --recursive https://gith…...
2023/3/28 15:47:54 - Asp.net core 依赖注入 (带案例以及注释理解)
1.很多朋友不知道什么是依赖注入,接下来我用比较通俗易懂的话语 来帮助大家理解 依赖注入(Dependency Injection,简称DI)是一种设计模式,用于减少组件之间的耦合度。它的核心思想是,将组件之间的依赖关系从…...
2023/3/28 15:47:43 - Java分布式锁面试题
1.为什么需要分布式锁? public synchronized void test() {System.out.println("获取到锁"); } public void test2() {synchronized (Test.class) {System.out.println("获取到锁");} }假设我们把上述代码部署到多台服务器上,这个互斥锁还能生…...
2023/3/28 15:46:29 - 微信小程序-微信小程序登录流程(一)
微信小程序,小程序的一种,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用 冷启动: 小程序首次打开或销毁后再次被打开&…...
2023/3/28 15:43:29 - C++初级教程(三)
一、C的一维数组 C 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量,而是声明一个数组变量,比如 …...
2023/3/28 15:42:13 - Github上得分最高的20个项目
freeCodeCamp:一个流行的编程教育平台,目标是让任何人都能够免费学习编程。 Vscode:一个流行的跨平台文本编辑器,支持各种语言和框架,可通过扩展进行自定义。 React:一个流行的 JavaScript 库,…...
2023/3/28 15:41:28 - 【java缓存、redis缓存、guava缓存】java中实现缓存的几种方式
一、通过HashMap实现缓存 这种方式可以简单实现本地缓存,但是实际开发中不推荐使用,下面我们来实现一下这种方式。 首先创建一个管理缓存的类 public class LocalCache {public static HashMap<String,String> cache new HashMap<>();sta…...
2023/3/28 15:37:22 - 从应用火到股市 AIGC概念股连涨仨月
AI有变革生产力之势,证券市场闻风而动。同花顺数据显示,AIGC概念指数年内上涨近60%,涨幅是上证指数的10倍多。 具体到个股,软通动力、远大智能等搭上AI风口的企业服务商“老树发新芽”,直接涨停。游戏板块霸榜产品回报…...
2023/3/28 15:37:03 - linux突破git clone下载限制
前言 本人要在linux下载一个2.5G的文件,但只有20kb/s,实在忍受不了,最终利用工具实现了800kb/s的速度,除了利用工具的方法,本人都尝试了,但是几乎没有效果,我也会罗列出来,可自行尝试…...
2023/3/28 15:36:50 - x86架构利用docker去编译arm64的应用程序
文章目录1. 交叉编译:toolchain2. 隔离挂载的方式:3. QEMU 或其他模拟器来实际运行dockerx86架构实现多平台系统代码的编译,实现方式有多种:交叉编译:toolchain 【新的第三方库不好处理】隔离挂载的方式 【速度慢&…...
2023/3/28 15:33:40 - Apache Camel
目录儿一、简介二、核心总结一、简介 Camel is an Open Source integration framework that empowers you to quickly and easily integrate various systems consuming or producing data. Camel 是一个开源的集成框架,能够让开发者快速、轻松地整合/集成不同的应…...
2023/3/28 15:33:26 - Java学习记录
阅读前请看一下:我是一个热衷于记录的人,每次写博客会反复研读,尽量不断提升博客质量。文章设置为仅粉丝可见,是因为写博客确实花了不少精力。希望互相进步谢谢!! 文章目录阅读前请看一下:我是一…...
2023/3/28 15:32:34 - 「数据架构」TOGAF建模之数据架构:数据迁移图
数据迁移图的目的是显示从源应用程序到目标应用程序的数据流。该图将提供源/目标分布的可视化表示,并作为数据审核和建立可追溯性的工具。该图可以根据需要进行细化或增强。例如,该图可以只包含迁移环境的总体布局,也可以包含单个应用程序元数…...
2023/3/28 15:29:54 - MySQL数据库之——高级[进阶]SQL语句(一)SQL高级语句、函数等
文章目录一、SQL高级语句1、 SELECT2、DISTINCT3、WHER条件查询4 、AND OR5、IN6、BETWEEN7、通配符8、LIKE9、ORDER BY二、SQL函数1、数学函数:2、聚合函数:3、字符串函数:4、GROUP BY5、HAVING6、别名7、连接查询8、子查询9、EXISTS一、SQL…...
2023/3/28 15:24:30 - Java实现一个简单的东南西北中的面板
目录 一、前言 二、代码部分 1.代码 三、程序运行结果(面板弹出) 四、涉及到的知识点代码 一、前言 1.本代码是我在上学时写的,有一些地方没能完美实现,请包涵也请多赐教! 2.本弹窗界面可以根据简单的要求进行…...
2023/3/28 15:24:15 - 阿里十年资深程序员吐血总结之Java代理模式
阿里十年资深程序员吐血总结之Java代理模式 文章目录阿里十年资深程序员吐血总结之Java代理模式1.接口代理2.类代理3.动态代理都是通过反射实现的吗4.jdk动态代理和cglib动态代理的区别Java代理模式是怎么实现的 Java 代理模式是一种常见的设计模式,它可以在不改变…...
2023/3/28 15:23:34 - ServletAPI详解(一)
目录 1.Tomcat简介 2.Servlet 是什么? 3.编写Servlet 程序步骤 1.创建一个Maven项目 2.引入依赖 3.创建目录 4.编写代码 5.打包程序 6.部署程序 7.验证程序 4.使用SmartTomcat插件部署 1.Tomcat简介 Tomcat是一个开源的,基于java实现的被广泛使用的web服务器.web服…...
2023/3/28 15:22:50 - 全球第三机械制造商徐工集团表示与 Conflux、Zen Spark Technology 建立合作伙伴关系公司
【ConfluxNews】2023.3.28 ----------------------------- 1.【网络状态】当前版本V2.2.2,全网算力≈10T,昨日交易次数58K,昨日新增账户0.69K,昨日新增合约13个; 2.【POS参数】总锁仓244(-3)&a…...
2023/3/28 15:20:26 - 为什么所谓的“自律”一定要跟坚持挂钩呢?懂一点“行为设计学”,升级对“意义”的认知
为什么所谓的“自律”一定要跟坚持挂钩呢?懂一点“行为设计学”,升级对“意义”的认知00 导读01 懂一点“行为设计学”02 升级你对“意义”的认知03 小结00 导读 世界上最成功的人之一,风险投资家雷达利欧(Ray Dalio)…...
2023/3/28 15:19:52 - 中国各省份研究随笔
一.陕西 1.陕北榆林 黄土荒原,萧瑟荒凉,生活艰难,亩产60斤小麦。2002年开始榆林富裕,因为煤炭资源,易开采、质量好 2002年之前煤炭没有人开采,因为价格只有14元一吨,2001年加入WTO࿰…...
2023/3/28 15:19:31 - 配置JRebel热部署
1:此对应设置的是IntelliJ IDEA 2022.1.3 (Ultimate Edition)的热部署设置 2:激活插件失败,提示【LS client not configued】原因是版本过高,解决办法:手动下载jRebel,官网去找到过去版本的jRebel,地址&am…...
2023/3/28 15:16:22 - OpenGL | 渲染带透明通道的2D精灵
一、Alpha测试 Alpha 测试的基本原理为:当绘制一个片元时,首先检测其 Alpha 值,若 Alpha 值满足要求,就通过测试,绘制此片元;否则丢弃此片元,不进行绘制。 glEnable(GL_ALPHA_TEST);…...
2023/3/28 15:15:20 - PCIE中断发送与接收
1. EndPoint模式下中断的发送 在EP模式下,PCIE会根据配置信息只产生MSI或者INTA中断中的一种,然后传送到RC端。 1.1 INTA中断的发送 INTA中断以带内消息(Assert_INTA/Deassert_INTA)的形式传递中断,其作用相当于传统PCI总线中使用边带中断…...
2023/3/28 15:15:04 - SQL Server底层架构技术对比
背景 数据库是信息化的基石,支撑着整个业务系统,发挥着非常重要的作用,被喻为“IT的心脏”。因此,让数据库安全、稳定、高效地运行已经成为IT管理者必须要面对的问题。数据库在底层架构层面需要满足以下几点建设要求: …...
2023/3/28 15:14:36 - 你真的了解变量吗?
变量是编程语言中最基本的术语,用来代指计算机中存放的可变数据——如整数、小数、字符或被调用的一片内存空间。 变量可以保存不同的变量类型,比如整型、布尔型、对象、数组等等,每一个变量在内存中对应着一个内存地址,但是如果…...
2023/3/28 15:13:20 - 一文彻底搞懂为什么OpenCV用GPU/cuda跑得比用CPU慢?
一、原因总结 最近项目需要,发现了这个问题。网上找原因,汇总起来,有以下几点原因: 1、首先对于任何一个CUDA程序,在调用它的第一个CUDA API时后都要花费秒级的时间去初始化运行环境,后续还要分配显存&am…...
2023/3/28 15:11:34 - 数仓必备概念
目录 数据仓库 三范式建模 维度建模 数据仓库 是一个面向主题的(Subject)、集成的(Integrated)、非易失(Non-Volatile)、时变性(Time Variant)的数据集合,用于支持管理…...
2023/3/28 15:09:12 - Spring Security 6.0系列【5】源码篇之认证组件
有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 前言认证机制认证组件SecurityContextHolderSecurityContextAuthentication…...
2023/3/28 15:08:48 - webpack 配置介绍
一、webpack webpack 仅是一个打包工具,不会处理打包过程的东西例如 es6等新特性,如果需要处理这些新特性需要配置单独的加载bebblle-loader 等。 "devDependencies": {"webpack": "^5.76.3", // 打包工具"…...
2023/3/28 15:06:40
最新文章
- 电子拣货标签13代系统简介
CK_Label_v13一、产品参数 1. 电池供电版 产品型号 CK_Label_v13 尺寸 70x34.7x13.6mm 屏幕尺寸 2.1 inch 分辨率 250*122 像素密度 130dpi 显示技术 电子墨水屏显示 显示颜色 黑/白 外观颜色 黑色 按键 1 指示灯 1 RGB灯 灯光颜色 7种(红/绿/蓝/黄/紫/白…...
2023/3/28 15:49:24 - Ubuntu下安装和编译onnxruntime
onnxruntime是一种用于onnx格式的机器学习模型的高性能推理引擎,适用于Linux,Windows、Mac及嵌入式设备。这篇博客记录了编译onnxruntime的步骤及在此过程种遇到的问题及解决方法。1 下载git clone --depth 1 --branch v1.12.1 --recursive https://gith…...
2023/3/28 15:47:54 - Asp.net core 依赖注入 (带案例以及注释理解)
1.很多朋友不知道什么是依赖注入,接下来我用比较通俗易懂的话语 来帮助大家理解 依赖注入(Dependency Injection,简称DI)是一种设计模式,用于减少组件之间的耦合度。它的核心思想是,将组件之间的依赖关系从…...
2023/3/28 15:47:43 - Java分布式锁面试题
1.为什么需要分布式锁? public synchronized void test() {System.out.println("获取到锁"); } public void test2() {synchronized (Test.class) {System.out.println("获取到锁");} }假设我们把上述代码部署到多台服务器上,这个互斥锁还能生…...
2023/3/28 15:46:29 - 微信小程序-微信小程序登录流程(一)
微信小程序,小程序的一种,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用 冷启动: 小程序首次打开或销毁后再次被打开&…...
2023/3/28 15:43:29 - C++初级教程(三)
一、C的一维数组 C 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量,而是声明一个数组变量,比如 …...
2023/3/28 15:42:13 - Github上得分最高的20个项目
freeCodeCamp:一个流行的编程教育平台,目标是让任何人都能够免费学习编程。 Vscode:一个流行的跨平台文本编辑器,支持各种语言和框架,可通过扩展进行自定义。 React:一个流行的 JavaScript 库,…...
2023/3/28 15:41:28 - 【java缓存、redis缓存、guava缓存】java中实现缓存的几种方式
一、通过HashMap实现缓存 这种方式可以简单实现本地缓存,但是实际开发中不推荐使用,下面我们来实现一下这种方式。 首先创建一个管理缓存的类 public class LocalCache {public static HashMap<String,String> cache new HashMap<>();sta…...
2023/3/28 15:37:22 - 从应用火到股市 AIGC概念股连涨仨月
AI有变革生产力之势,证券市场闻风而动。同花顺数据显示,AIGC概念指数年内上涨近60%,涨幅是上证指数的10倍多。 具体到个股,软通动力、远大智能等搭上AI风口的企业服务商“老树发新芽”,直接涨停。游戏板块霸榜产品回报…...
2023/3/28 15:37:03 - linux突破git clone下载限制
前言 本人要在linux下载一个2.5G的文件,但只有20kb/s,实在忍受不了,最终利用工具实现了800kb/s的速度,除了利用工具的方法,本人都尝试了,但是几乎没有效果,我也会罗列出来,可自行尝试…...
2023/3/28 15:36:50 - x86架构利用docker去编译arm64的应用程序
文章目录1. 交叉编译:toolchain2. 隔离挂载的方式:3. QEMU 或其他模拟器来实际运行dockerx86架构实现多平台系统代码的编译,实现方式有多种:交叉编译:toolchain 【新的第三方库不好处理】隔离挂载的方式 【速度慢&…...
2023/3/28 15:33:40 - Apache Camel
目录儿一、简介二、核心总结一、简介 Camel is an Open Source integration framework that empowers you to quickly and easily integrate various systems consuming or producing data. Camel 是一个开源的集成框架,能够让开发者快速、轻松地整合/集成不同的应…...
2023/3/28 15:33:26 - Java学习记录
阅读前请看一下:我是一个热衷于记录的人,每次写博客会反复研读,尽量不断提升博客质量。文章设置为仅粉丝可见,是因为写博客确实花了不少精力。希望互相进步谢谢!! 文章目录阅读前请看一下:我是一…...
2023/3/28 15:32:34 - 「数据架构」TOGAF建模之数据架构:数据迁移图
数据迁移图的目的是显示从源应用程序到目标应用程序的数据流。该图将提供源/目标分布的可视化表示,并作为数据审核和建立可追溯性的工具。该图可以根据需要进行细化或增强。例如,该图可以只包含迁移环境的总体布局,也可以包含单个应用程序元数…...
2023/3/28 15:29:54 - MySQL数据库之——高级[进阶]SQL语句(一)SQL高级语句、函数等
文章目录一、SQL高级语句1、 SELECT2、DISTINCT3、WHER条件查询4 、AND OR5、IN6、BETWEEN7、通配符8、LIKE9、ORDER BY二、SQL函数1、数学函数:2、聚合函数:3、字符串函数:4、GROUP BY5、HAVING6、别名7、连接查询8、子查询9、EXISTS一、SQL…...
2023/3/28 15:24:30 - Java实现一个简单的东南西北中的面板
目录 一、前言 二、代码部分 1.代码 三、程序运行结果(面板弹出) 四、涉及到的知识点代码 一、前言 1.本代码是我在上学时写的,有一些地方没能完美实现,请包涵也请多赐教! 2.本弹窗界面可以根据简单的要求进行…...
2023/3/28 15:24:15 - 阿里十年资深程序员吐血总结之Java代理模式
阿里十年资深程序员吐血总结之Java代理模式 文章目录阿里十年资深程序员吐血总结之Java代理模式1.接口代理2.类代理3.动态代理都是通过反射实现的吗4.jdk动态代理和cglib动态代理的区别Java代理模式是怎么实现的 Java 代理模式是一种常见的设计模式,它可以在不改变…...
2023/3/28 15:23:34 - ServletAPI详解(一)
目录 1.Tomcat简介 2.Servlet 是什么? 3.编写Servlet 程序步骤 1.创建一个Maven项目 2.引入依赖 3.创建目录 4.编写代码 5.打包程序 6.部署程序 7.验证程序 4.使用SmartTomcat插件部署 1.Tomcat简介 Tomcat是一个开源的,基于java实现的被广泛使用的web服务器.web服…...
2023/3/28 15:22:50 - 全球第三机械制造商徐工集团表示与 Conflux、Zen Spark Technology 建立合作伙伴关系公司
【ConfluxNews】2023.3.28 ----------------------------- 1.【网络状态】当前版本V2.2.2,全网算力≈10T,昨日交易次数58K,昨日新增账户0.69K,昨日新增合约13个; 2.【POS参数】总锁仓244(-3)&a…...
2023/3/28 15:20:26 - 为什么所谓的“自律”一定要跟坚持挂钩呢?懂一点“行为设计学”,升级对“意义”的认知
为什么所谓的“自律”一定要跟坚持挂钩呢?懂一点“行为设计学”,升级对“意义”的认知00 导读01 懂一点“行为设计学”02 升级你对“意义”的认知03 小结00 导读 世界上最成功的人之一,风险投资家雷达利欧(Ray Dalio)…...
2023/3/28 15:19:52 - 中国各省份研究随笔
一.陕西 1.陕北榆林 黄土荒原,萧瑟荒凉,生活艰难,亩产60斤小麦。2002年开始榆林富裕,因为煤炭资源,易开采、质量好 2002年之前煤炭没有人开采,因为价格只有14元一吨,2001年加入WTO࿰…...
2023/3/28 15:19:31 - 配置JRebel热部署
1:此对应设置的是IntelliJ IDEA 2022.1.3 (Ultimate Edition)的热部署设置 2:激活插件失败,提示【LS client not configued】原因是版本过高,解决办法:手动下载jRebel,官网去找到过去版本的jRebel,地址&am…...
2023/3/28 15:16:22 - OpenGL | 渲染带透明通道的2D精灵
一、Alpha测试 Alpha 测试的基本原理为:当绘制一个片元时,首先检测其 Alpha 值,若 Alpha 值满足要求,就通过测试,绘制此片元;否则丢弃此片元,不进行绘制。 glEnable(GL_ALPHA_TEST);…...
2023/3/28 15:15:20 - PCIE中断发送与接收
1. EndPoint模式下中断的发送 在EP模式下,PCIE会根据配置信息只产生MSI或者INTA中断中的一种,然后传送到RC端。 1.1 INTA中断的发送 INTA中断以带内消息(Assert_INTA/Deassert_INTA)的形式传递中断,其作用相当于传统PCI总线中使用边带中断…...
2023/3/28 15:15:04 - SQL Server底层架构技术对比
背景 数据库是信息化的基石,支撑着整个业务系统,发挥着非常重要的作用,被喻为“IT的心脏”。因此,让数据库安全、稳定、高效地运行已经成为IT管理者必须要面对的问题。数据库在底层架构层面需要满足以下几点建设要求: …...
2023/3/28 15:14:36 - 你真的了解变量吗?
变量是编程语言中最基本的术语,用来代指计算机中存放的可变数据——如整数、小数、字符或被调用的一片内存空间。 变量可以保存不同的变量类型,比如整型、布尔型、对象、数组等等,每一个变量在内存中对应着一个内存地址,但是如果…...
2023/3/28 15:13:20 - 一文彻底搞懂为什么OpenCV用GPU/cuda跑得比用CPU慢?
一、原因总结 最近项目需要,发现了这个问题。网上找原因,汇总起来,有以下几点原因: 1、首先对于任何一个CUDA程序,在调用它的第一个CUDA API时后都要花费秒级的时间去初始化运行环境,后续还要分配显存&am…...
2023/3/28 15:11:34 - 数仓必备概念
目录 数据仓库 三范式建模 维度建模 数据仓库 是一个面向主题的(Subject)、集成的(Integrated)、非易失(Non-Volatile)、时变性(Time Variant)的数据集合,用于支持管理…...
2023/3/28 15:09:12 - Spring Security 6.0系列【5】源码篇之认证组件
有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 前言认证机制认证组件SecurityContextHolderSecurityContextAuthentication…...
2023/3/28 15:08:48 - webpack 配置介绍
一、webpack webpack 仅是一个打包工具,不会处理打包过程的东西例如 es6等新特性,如果需要处理这些新特性需要配置单独的加载bebblle-loader 等。 "devDependencies": {"webpack": "^5.76.3", // 打包工具"…...
2023/3/28 15:06:40