JAVA篇:Java 多线程 (二) 线程锁机制和死锁
2、线程锁机制和死锁
关键字:Java锁分类名词、线程死锁、Lock、ReentrantLock、ReadWriteLock、Condition
说到锁的话,总是会提到很多,其分类与理论部分应该会参考别人的描述,反正自己讲也不会比别人好。
-
公平锁/非公平锁
-
可重入锁
-
独享锁/共享锁
-
互斥锁/读写锁
-
乐观锁/悲观锁
-
分段锁
-
偏向锁/轻量级锁/重量级锁
-
自旋锁
还有一部分则是Java中锁的实现与应用。
-
synchronized
-
Lock相关类
-
Condition相关类
2.1 锁的分类名词
前面所说的锁的分类名词,有的是指锁的状态、有的指锁的特性、有的指锁的设计。这部分主要是参考
2.1.1 公平锁/非公平锁
公平锁是指多个线程按照申请所的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能是后申请的线程比先申请的线程有限获取锁。有可能,会造成优先级反转或者饥饿现象。
非公平锁的优点在于吞吐量比公平锁大。
在Java中,synchronized
是一种非公平锁。
ReentrantLock
则可以通过构造函数指定该锁是否公平锁,默认是非公平锁。ReentrantLock
通过AQS来实现线程调度,实现公平锁。
2.1.2 可重入锁
可重入锁又名递归锁,是指在同一个线程在持有锁的前提下,再遇到需要申请同一个锁的情况时可自动获取锁。而非可重入锁遇到这种情况会形成死锁,也就是“我申请我已经持有的锁,我不会释放锁也申请不到锁,所以形成死锁。”
Java中,
synchronized
在JDK 1.6优化后,属于可重入锁。
ReentrantLock
,即Re entrant Lock
,可重入锁。
synchronized void A(){ System.out.println("A获取锁!"); B(); } synchronized void B(){ System.out.println("B锁重入成功!"); }
2.1.3 独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有,共享锁是指该锁可被多个线程所持有。
在Java中,
synchronized
属于独享锁。
ReentrantLock
也属于独享锁。
而Lock的另一个实现类ReadWriteLock
,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证高效的并发读,但是读写、写读、写写的过程是互斥的,防止脏读、数据丢失。独享锁和共享锁也是通过AQS实现的。
2.1.4 互斥锁/读写锁
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentraLock
读写锁在Java中的具体实现就是ReadWriteLock
2.1.5 乐观锁/悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁认为对同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式,悲观地认为,不加锁的并发操作一定会出现问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断更新的方式更新数据,乐观地认为,不加锁的并发操作是没有事情的。
从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
悲观锁在Java中的使用就是利用各种锁。
乐观锁在Java中的使用就是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
2.1.6 分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap
而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
我们以ConcurrentHashMap
来说一下分段锁的含义以及设计思想,ConcurrentHashMap
中的分段锁称为segment,它类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道它要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不死放在一个酚酸中,就实现了真正的并行插入。
但是在统计size的时候,即获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的就是细化锁的粒度,当操作不需要更新整个数组的时候,就针对数据的一项进行加锁操作。
2.1.7 偏向锁/轻量级锁/重量级锁
这三种所是指锁的状态,并且是针对synchronized
。在 java 6通过引入锁的升级机制来实现高效synchronized
。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
2.1.8 自旋锁
在Java中。自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
2.2 线程死锁
死锁是一个经典的多线程问题。避免死锁重要吗?一旦一组Java线程发生死锁,那么这组线程及锁涉及其已经持有锁的资源区将不再可用--除非重启应用。
死锁是设计上的bug,它并不一定发生,但它有可能发生,而且发生的情况一般出现在极端的高负载的情况下。
那么有什么办法为了避免死锁?
-
让程序每次至多只能获得一个锁。但这个在多线程环境下通常不现实。
-
设计时考虑清楚锁的顺序,尽量减少潜在的加锁交互数量
-
避免使用synchronized,为线程等待设置等待上限,避免无限等待。
2.3 Lock和Condition
JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信。在jdk1.5以后,Java提供了Lock类来提供更加丰富的锁功能,并且还提供了Condition来实现线程间通信。
Lock和Condition都属java.util.concurrent.locks
下的接口,如下图所示。其中Lock的实现类包含ReentrantLock
和ReadWriteLock
,而Condition对象是通过lock对象创建的。
其中的AbstractOwnableSynchronizer(AQS),即抽象的队列式的同步器,定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如ReentrantLock/Semaphore/CountDownLatch...
在这里对AQS暂不做展开。
2.4 ReentrantLock
之前有说过ReentrantLock是一个可重入互斥的锁,在其构造函数中可以设置其是否是公平锁。
/* 创建一个 ReentrantLock的实例。*/ ReentrantLock() /* 根据给定的公平政策创建一个 ReentrantLock的实例。。*/ ReentrantLock(boolean fair)
相比于synchronized,ReentrantLock提供了更加丰富灵活的功能。
-
void lock():获得锁。
-
void unlock():尝试释放此锁。
-
boolean tryLock():只有在调用时它不被另一个线程占用才能获取锁。
-
boolean tryLock(long timeout, TimeUnit unit):有限制超时的请求锁,如果线程没有被中断且没有超时则可以获得锁。
-
void lockInterruptibly() : 获取锁,除非被中断。
-
Condition newCondition():返回一个Condition实例。
-
boolean isFair():该锁是否为公平锁。
-
boolean isHeldByCurrentThread():查询此锁是否由当前线程持有。
-
boolean isLocked():查询此锁是否由任何线程持有。
-
int getHoldCount():查询当前线程对此锁的阻塞数量。
-
protected Thread getOwner():返回当前拥有此锁的线程,如果不拥有,则返回 null 。
-
protected Collection<Thread> getQueuedThreads():返回可能正在等待获取此锁的线程的集合。
-
int getQueueLength():返回等待获取此锁的线程数的估计。
-
boolean hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁。
-
boolean hasQueuedThreads():查询是否有线程正在等待获取此锁。
-
protected Collection<Thread> getWaitingThreads(Condition condition):返回正在等待(wait)给定Condition的线程集合,传入的Condition必须与锁相关联。
-
int getWaitQueueLength(Condition condition):返回正在等待(wait)给定Condition的线程数量估计,传入的Condition必须与锁相关联。
-
boolean hasWaiters(Condition condition):查询是否有任何线程等待(wait)给定Condition,传入的Condition必须与锁相关联。
2.4.1 请求锁释放锁
ReentrantLock除了提供与synchronized锁功能相似的无限阻塞的锁请求lock()/unlock(),还提供了可限制阻塞超时的tryLock()/tryLock(timeout),可以更加灵活地处理锁相关的操作,防止线程死锁。
同时还可以查看当前锁持有、阻塞的情况。
测试代码如下:
/* 测试ReentrantLock锁请求与释放 */ public void test1(){ /* 创建锁实例ReentrantLock */ Lock rlock = new ReentrantLock(); // System.out.println(rlock.toString()); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); /* 线程1从头到尾持有锁 */ Runnable r1 = new Runnable() { @Override public void run() { rlock.lock(); System.out.println(df.format(new Date())+" part11:子线程1先持有锁rlock,然后休眠5S"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(df.format(new Date())+" part12:子线程1释放锁rlock"); rlock.unlock(); } }; Thread t1 = new Thread(r1); t1.start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } /* 线程2使用lock申请锁 */ Runnable r2 = new Runnable() { @Override public void run() { System.out.println(df.format(new Date())+" part21:子线程2使用lock()申请锁rlock,会阻塞"); rlock.lock(); System.out.println(df.format(new Date())+" part22:子线程2获得了锁rlock,然后释放锁"); rlock.unlock(); } }; Thread t2 = new Thread(r2); t2.start(); /* 线程3使用trylock\trylock(timeout)申请锁 */ Runnable r3 = new Runnable() { @Override public void run() { System.out.println(df.format(new Date())+" part31:子线程3使用trylock()申请锁rlock"); if(rlock.tryLock()){ System.out.println(df.format(new Date())+" part32:子线程3获得了锁rlock,然后释放锁"); rlock.unlock(); }else{ System.out.println(df.format(new Date())+" part33:子线程3没有获得锁rlock,使用带1S超时的trylock申请锁"); try { if(rlock.tryLock(1000, TimeUnit.MILLISECONDS)){ System.out.println(df.format(new Date())+" part34:子线程3获得了锁rlock,然后释放锁"); rlock.unlock(); }else{ System.out.println(df.format(new Date())+" part35:子线程3没有获得锁rlock,超时退出"); } } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t3 = new Thread(r3); t3.start(); /* 主线程查看当前锁与线程的情况 */ System.out.println(df.format(new Date())+" 主线程查看-rlock是否为公平锁:"+((ReentrantLock) rlock).isFair()); System.out.println(df.format(new Date())+" 主线程查看-rlock是否被任意线程持有:"+((ReentrantLock) rlock).isLocked()); System.out.println(df.format(new Date())+" 主线程查看-查询是否有线程等待获取rlock:"+((ReentrantLock) rlock).hasQueuedThreads()); System.out.println(df.format(new Date())+" 主线程查看-查询子线程2是否等待获取锁rlock:"+((ReentrantLock) rlock).hasQueuedThread(t2)); System.out.println(df.format(new Date())+" 主线程查看-查询等待获取锁rlock的线程数估计:"+((ReentrantLock) rlock).getQueueLength()); /* 主线程尝试获取锁 */ rlock.lock(); System.out.println(df.format(new Date())+" part01:主线程获得锁rlock"); System.out.println(df.format(new Date())+" 主线程查看-rlock是否被主线程持有:"+((ReentrantLock) rlock).isHeldByCurrentThread()); System.out.println(df.format(new Date())+" 主线程查看-查询当前线程对rlock的阻塞数量:"+((ReentrantLock) rlock).getHoldCount()); rlock.lock(); System.out.println(df.format(new Date())+" part02:主线程重入锁rlock"); System.out.println(df.format(new Date())+" 主线程查看-rlock是否被主线程持有:"+((ReentrantLock) rlock).isHeldByCurrentThread()); System.out.println(df.format(new Date())+" 主线程查看-查询当前线程对rlock的阻塞数量:"+((ReentrantLock) rlock).getHoldCount()); rlock.unlock(); rlock.unlock(); System.out.println(df.format(new Date())+"测试结束。"); }
输出结果如下:
2021-10-11 09:54:03:122 part11:子线程1先持有锁rlock,然后休眠5S 2021-10-11 09:54:03:124 part21:子线程2使用lock()申请锁rlock,会阻塞 2021-10-11 09:54:03:124 主线程查看-rlock是否为公平锁:false 2021-10-11 09:54:03:125 主线程查看-rlock是否被任意线程持有:true 2021-10-11 09:54:03:125 part31:子线程3使用trylock()申请锁rlock 2021-10-11 09:54:03:125 主线程查看-查询是否有线程等待获取rlock:true 2021-10-11 09:54:03:125 part33:子线程3没有获得锁rlock,使用带1S超时的trylock申请锁 2021-10-11 09:54:03:125 主线程查看-查询子线程2是否等待获取锁rlock:true 2021-10-11 09:54:03:125 主线程查看-查询等待获取锁rlock的线程数估计:1 #这里为什么会是1? 2021-10-11 09:54:04:132 part35:子线程3没有获得锁rlock,超时退出 2021-10-11 09:54:08:123 part12:子线程1释放锁rlock 2021-10-11 09:54:08:123 part22:子线程2获得了锁rlock,然后释放锁 2021-10-11 09:54:08:124 part01:主线程获得锁rlock 2021-10-11 09:54:08:124 主线程查看-rlock是否被主线程持有:true 2021-10-11 09:54:08:124 主线程查看-查询当前线程对rlock的阻塞数量:1 2021-10-11 09:54:08:125 part02:主线程重入锁rlock 2021-10-11 09:54:08:125 主线程查看-rlock是否被主线程持有:true 2021-10-11 09:54:08:125 主线程查看-查询当前线程对rlock的阻塞数量:2 2021-10-11 09:54:08:125测试结束。
对结果有疑惑的地方在于getQueueLength()为什么会在线程2、3都等待着锁的情况下,结果是1?后面经验证,getQueueLength()仅统计lock()后阻塞的线程,trylock(timeout)的等待应该底层有所不同。
2.4.2 中断与锁请求
lockInterruptibly()与lock()都是锁请求方法,不过lockInterruptibly()提供了响应中断请求以及处理中断请求的功能,使得在进行锁请求时,线程可以被中断。
测试代码如下所示:
/* 测试中断与锁请求 */ public void test3() { /* 创建锁实例ReentrantLock */ Lock rlock = new ReentrantLock(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); /* 子线程1使用lock等待 */ Runnable r1 = new Runnable() { @Override public void run() { try { Thread.sleep(100); System.out.println(df.format(new Date()) + " part11:子线程1使用lock请求锁"); rlock.lock(); System.out.println(df.format(new Date()) + " part12:子线程1获得锁"); rlock.unlock(); System.out.println(df.format(new Date()) + " part13:子线程1释放锁"); } catch (InterruptedException e) { System.out.println(df.format(new Date()) + " part19:子线程1被中断!"); } } }; Thread t1 = new Thread(r1); t1.start(); /* 子线程2使用lockInterruptibly() */ Runnable r2 = new Runnable() { @Override public void run() { try { Thread.sleep(100); System.out.println(df.format(new Date()) + " part21:子线程2使用lockInterruptibly请求锁"); rlock.lockInterruptibly(); System.out.println(df.format(new Date()) + " part22:子线程2获得锁"); rlock.unlock(); System.out.println(df.format(new Date()) + " part23:子线程2释放锁"); } catch (InterruptedException e) { System.out.println(df.format(new Date()) + " part29:子线程2被中断!"); } } }; Thread t2 = new Thread(r2); t2.start(); /* 主线程先持有锁使得其他子线程等待,然后在合适的时间中断两个线程 */ System.out.println(" 主线程先持有锁使得其他子线程等待,然后在合适的时间中断两个线程 "); rlock.lock(); System.out.println(df.format(new Date()) + " part01:主线程获得锁后休眠,防止在sleep的时候中断"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(df.format(new Date()) + " part02:主线程中断子线程1"); t1.interrupt(); System.out.println(df.format(new Date()) + " part03:主线程中断子线程2"); t2.interrupt(); System.out.println(df.format(new Date()) + " part04:主线程释放锁"); rlock.unlock(); System.out.println(df.format(new Date()) + " 测试结束"); }
结果如下,可以看出来子线程2成功被中断了,而子线程1中断之后并无反应:
主线程先持有锁使得其他子线程等待,然后在合适的时间中断两个线程 2021-10-11 10:22:12:435 part01:主线程获得锁后休眠,防止在sleep的时候中断 2021-10-11 10:22:12:539 part21:子线程2使用lockInterruptibly请求锁 2021-10-11 10:22:12:539 part11:子线程1使用lock请求锁 2021-10-11 10:22:12:647 part02:主线程中断子线程1 2021-10-11 10:22:12:647 part03:主线程中断子线程2 2021-10-11 10:22:12:647 part04:主线程释放锁 2021-10-11 10:22:12:647 part29:子线程2被中断! 2021-10-11 10:22:12:647 测试结束 2021-10-11 10:22:12:647 part12:子线程1获得锁 2021-10-11 10:22:12:647 part13:子线程1释放锁
2.5 ReadWriteLock
锁会导致线程阻塞,大大降低了多线程处理数据的效率。前面有提到过对于锁的优化,可以将读写分离,当只需要进行并发读的时候,并不需要进行加锁操作。
ReadWriteLock是一个接口,它提供了一个读锁和一个写锁,其实现在 ReentrantReadWriteLock及其内部静态类 ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock。
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading. */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing. */ Lock writeLock(); }
2.5.1 ReentrantReadWriteLock
ReentrantReadWriteLock.ReadLock
和ReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock
都提供了基本的锁操作,lock()、trylock()、tryLock(long timeout, TimeUnit unit)、lockInterruptibly()、unlock()、newCondition()。其中WriteLock
还包含方法isHeldByCurrentThread()和getHoldCount()。
读锁和写锁的实例对象分别由ReentrantReadWriteLock的两个实例方法readLock()和writeLock()返回。两个锁成对,在申请锁的时候有一定的关联。
读锁ReadLock
申请锁、获取锁的前提是,同一ReentrantReadWriteLock
的写锁不被其他线程占有。
写锁WriteLock
申请锁、获取锁的前提是,写锁及同一ReentrantReadWriteLock
的读锁不被其他线程占有。
2.5.2 读写锁测试代码
读写锁主要是为了防止多线程操作文件是发生冲突,导致文件结果与预期不符,同时读锁又保证了并发读取数据时多线程效率问题。这里测试代码主要看锁的情况,有关于多线程读写后面再拓展讨论。
测试代码如下,运行过程中可以看到偶尔会有多个线程同时进行读操作:
/* 测试读写锁 */ public void test4(){ /* 创建读写锁 */ ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readlock = lock.readLock(); ReentrantReadWriteLock.WriteLock writelock = lock.writeLock(); Random random = new Random(); class RThread extends Thread{ private int tag; public RThread(int tag, ReentrantReadWriteLock lock){ this.tag = tag; } @Override public void run(){ int n = 5; while (n>0){ n--; /*随机休眠 try { Thread.sleep(random.nextInt(300)); } catch (InterruptedException e) { e.printStackTrace(); }*/ readlock.lock(); System.out.println(String.format("读取线程%d:当前读锁数量:%d,是否写锁定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked())); System.out.println(String.format("读取线程%d:进行读取......剩余次数:%d",this.tag,n)); readlock.unlock(); } } } class WThread extends Thread{ private int tag; public WThread(int tag,ReentrantReadWriteLock lock){ this.tag = tag; } @Override public void run(){ int n = 5; while (n>0){ n--; writelock.lock(); System.out.println(String.format("写入线程%d:当前读锁数量:%d,是否写锁定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked())); System.out.println(String.format("写入线程%d:进行写入......剩余次数:%d",this.tag,n)); writelock.unlock(); } } } /*创建5个读线程,5个写线程*/ int tlength = 5; Thread[] rthreads = new RThread[5]; Thread[] wthreads = new WThread[5]; for(int i=0;i<tlength;i++){ rthreads[i] = new RThread(i,lock); rthreads[i].start(); wthreads[i] = new WThread(i,lock); wthreads[i].start(); } }
2.6 Condition
Condition将对象监视器方法(wait、notify和notifyAll)分解为不同的对象,Lock取代了synchronized,而Condition取代了对象监视器方法的使用。
Condition与Lock实例绑定,通过newCondition()方法创建。包含如下方法:
-
void await():调用后当前线程进入等待,直到被唤醒或者被中断。
-
void awaitUninterruptibly():使当前线程等待直到被唤醒。该方法不会被中断。
-
boolean await(long time, TimeUnit unit):使当前线程等待直到被唤醒或被中断,或指定的等待时间过去,返回是否超时的判定。
-
long awaitNanos(long nanosTimeout):使当前线程等待直到被唤醒或中断,或指定的等待时间过去,返回剩余等待时间。
-
boolean awaitUntil(Date deadline):使当前线程等待直到发出信号或中断,或者指定的最后期限过去。
-
void signal():唤醒一个等待线程。
-
void signalAll():唤醒所有等待线程。
需要注意的是:除了调用Condition的等待及唤醒方法,包括lock的方法lock.hasWaiters(condition)和lock.getWaitQueueLength(condition),使用condition相关方法前必须已经获得对应的lock,否则会报错“java.lang.IllegalMonitorStateException”。
测试代码如下:
/* 测试Condition */ public void test5(){ /* 创建锁和condition */ ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); /* 创建等待子线程 */ Runnable r1 = new Runnable() { @Override public void run() { lock.lock(); System.out.println(Thread.currentThread().getName()+": 获得锁并进入等待。"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 被唤醒并获得锁,运行结束退出锁。"); lock.unlock(); } }; int tlength = 5; Thread[] threads = new Thread[tlength]; for(int i=0;i<tlength;i++){ threads[i] = new Thread(r1); threads[i].start(); } /* 主线程进行唤醒线程 */ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); System.out.println("主线程:signal()"); condition.signal(); System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); System.out.println("主线程:signal()"); condition.signal(); System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); System.out.println("主线程:signalAll()"); condition.signalAll(); System.out.println(String.format("主线程:等待锁lock的线程数估计:%d, 是否有线程正在等待condition:%b,估计等待线程数:%d", lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition))); lock.unlock(); }
运行结果如下:
Thread-0: 获得锁并进入等待。 Thread-2: 获得锁并进入等待。 Thread-3: 获得锁并进入等待。 Thread-1: 获得锁并进入等待。 Thread-4: 获得锁并进入等待。 主线程:等待锁lock的线程数估计:0, 是否有线程正在等待condition:true,估计等待线程数:5 主线程:signal() 主线程:等待锁lock的线程数估计:1, 是否有线程正在等待condition:true,估计等待线程数:4 主线程:signal() 主线程:等待锁lock的线程数估计:2, 是否有线程正在等待condition:true,估计等待线程数:3 主线程:signalAll() 主线程:等待锁lock的线程数估计:5, 是否有线程正在等待condition:false,估计等待线程数:0 Thread-0: 被唤醒并获得锁,运行结束退出锁。 Thread-2: 被唤醒并获得锁,运行结束退出锁。 Thread-3: 被唤醒并获得锁,运行结束退出锁。 Thread-1: 被唤醒并获得锁,运行结束退出锁。 Thread-4: 被唤醒并获得锁,运行结束退出锁。
2.X 参考
Java中Lock,tryLock,lockInterruptibly有什么区别? - wuxinliulei的回答 - 知乎 https://www.zhihu.com/question/36771163/answer/68974735
0、JAVA多线程编程
Java多线程编程所涉及的知识点包含线程创建、线程同步、线程间通信、线程死锁、线程控制(挂起、停止和恢复)。之前
-
篇0
-
篇1
-
篇2
-
篇3
-
篇4
-
篇5
-
篇6
-
篇7
-
篇8