JUC并发编程系列(一) 会写四篇多线程和JUC相关的知识,干活满满,手把手调试,保姆级待遇!
1.什么是JUC? 有着多线程的基础,就进一步学习Java关于并发操作封装的工具类,查看JDK1.8 帮助文档可知:
2.回顾多线程知识 创建线程的方法
1.继承Thread类,重写run()方法,调用start()
通过继承Thread实现的线程类,多个线程间无法共享线程类的实例变量。(需要创建不同Thread对象,自然不共享)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /\*\* \*创建线程的方法:1. 继承Thread类,重写run()方法,调用start() \* @author 路飞 \* @create 2021 /1 /23 \*/public class Demo01 extends Thread {@Override public void run () {for (int i = 0 ; i < 20 ; i++) { System.out.println("我在写博客----->" +i); } }public static void main (String[] args) {Demo01 demo01 = new Demo01 (); demo01.start(); for (int i = 0 ; i < 20 ; i++) { System.out.println("我在复习多线程--->" +i); } } }
2.实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
此Thread对象才是真正的线程对象。通过实现Runnable接口的线程类,是互相共享资源的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /\*\* \* 创建线程的方法2 :实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法 \* @author 路飞 \* @create 2021 /1 /23 \*/public class Demo02 implements Runnable {@Override public void run () {for (int i = 0 ; i < 20 ; i++) { System.out.println("我还是在写代码---->" +i); } }public static void main (String[] args) {Demo02 demo02 = new Demo02 ();Thread thread = new Thread (demo02); thread.start();for (int i = 0 ; i < 20 ; i++) { System.out.println("我还是在写博客--->" +i); } } }
3.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值 ,而Runnable的run()函数不能将结果返回给客户程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /\*\* \* 创建线程的方法3 :创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 \* @author 路飞 \* @create 2021 /1 /18 \*/public class CallableTets {public static void main (String[] args) throws ExecutionException, InterruptedException {MyThread thread = new MyThread ();FutureTask futureTask = new FutureTask (thread);for (int i = 0 ; i < 20 ; i++) {new Thread (futureTask,String.valueOf(i)).start(); System.out.println(Thread.currentThread().getName()+"---->进来了" ); }Integer o = (Integer) futureTask.get(); System.out.println(o); } }class MyThread implements Callable <Integer> {@Override public Integer call () throws Exception { System.out.println("call()" );return 1024 ; } }
创建线程的三种方式的对比:
1、采用实现Runnable、Callable接口的方式创建多线程时,
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类的方式创建多线程时,
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
3.线程和进程
**进程:**进程在操作系统中可以独立运行,作为资源分配的基本单位。表示运行中的程序。
**线程:**线程是进程中的一个实例,作为系统调度和分派的基本单位。是进程中的一段序列,能够完成进程中的一个功能。
进程和线程的区别:
(1)同一个进程可以包含多个线程,一个进程中至少包含一个线程,一个线程只能存在于一个进程中。
(2)同一个进程下的所有线程能够共享该进程下的资源。(系统运行时会为每个进程分配不同的内存区域,但不会为线程分配内存。线程只能共享它所属进程的资源。)
(3)进程结束后,该进程下的所有线程将销毁,而一个线程的结束不会影响同一进程下的其他线程。
(4)线程是轻量级的进程,它的创建和销毁所需要的时间比进程小得多,所有操作系统的执行功能都是通过创建线程去完成的。
(5)线程在执行时是同步和互斥的,因为他们共享同一个进程下的资源。
(6)在操作系统中,进程是拥有系统资源的独立单元,它可以拥有自己的资源。一般而言,线程不能拥有自己的资源,但是它能够访问其隶属进程的资源。
举个栗子:
**进程:**一个程序,QQ,QQ游戏,lol等程序的集合
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程?2个 Main、GC
线程:QQ发信息,传文件
对于Java而言:Thread、Runnable、Callable
Java是不能操作线程的,底层调用C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public synchronized void start () { /\*\* \* This method is not invoked for the main method thread or "system" \* group threads created/set up by the VM. Any new functionality added \* to this method in the future may have to also be added to the VM. \* \* A zero status value corresponds to state "NEW" . \*/if (threadStatus != 0 )throw new IllegalThreadStateException (); /\* Notify the group that this thread is about to be started \* so that it can be added to the group's list of threads \* and the group' s unstarted count can be decremented. \*/ group.add(this );boolean started = false ;try { start0(); started = true ; } finally {try {if (!started) { group.threadStartFailed(this ); } } catch (Throwable ignore) { /\* do nothing. If start0 threw a Throwable then it will be passed up the call stack \*/ } } }private native void start0 () ;
并行和并发
并发编程:并发、并行
并发(多个线程操作同一个资源)
老师让两个同学去办公室谈话。如果这两同学(进程)是并列跨过办公室门(CPU)的,那么就是并行。如果同学A先进同学B后进入(或者先B后A),或者两人并列同时进入,但是在办公室外的路人甲(用户)看来,同学A和同学B同时都在办公室内,这是并发。
如果举例要精确一点,那么大概是这样的:进办公室有两个门(两CPU),如果两同学分别从不同的门进入,不管先后性,两者互相独立,那么是并行;如果两同学不管以什么方式进入,在路人甲看来,他两同时都在办公室内,就是并发。
这篇博客讲的很清楚
并发编程的本质:充分利用CPU资源
线程的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public enum State { /\*\* \* Thread state for a thread which has not yet started. \*/ NEW, /\*\* \* Thread state for a runnable thread. A thread in the runnable \* state is executing in the Java virtual machine but it may \* be waiting for other resources from the operating system \* such as processor. \*/ RUNNABLE, /\*\* \* Thread state for a thread blocked waiting for a monitor lock. \* A thread in the blocked state is waiting for a monitor lock \* to enter a synchronized block/method or \* reenter a synchronized block/method after calling \* {@link Object#wait() Object.wait}. \*/ BLOCKED, /\*\* \* Thread state for a waiting thread. \* A thread is in the waiting state due to calling one of the \* following methods: \* <ul> \* <li>{@link Object#wait() Object.wait} with no timeout</li> \* <li>{@link #join() Thread.join} with no timeout</li> \* <li>{@link LockSupport#park() LockSupport.park}</li> \* </ul> \* \* <p>A thread in the waiting state is waiting for another thread to \* perform a particular action. \* \* For example, a thread that has called <tt>Object.wait()</tt> \* on an object is waiting for another thread to call \* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on \* that object. A thread that has called <tt>Thread.join()</tt> \* is waiting for a specified thread to terminate. \*/ WAITING, /\*\* \* Thread state for a waiting thread with a specified waiting time. \* A thread is in the timed waiting state due to calling one of \* the following methods with a specified positive waiting time: \* <ul> \* <li>{@link #sleep Thread.sleep}</li> \* <li>{@link Object#wait(long ) Object.wait} with timeout</li> \* <li>{@link #join(long ) Thread.join} with timeout</li> \* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> \* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> \* </ul> \*/ TIMED\_WAITING, /\*\* \* Thread state for a terminated thread. \* The thread has completed execution. \*/ TERMINATED; }
多线程编程中常用的函数比较
sleep 和 wait 的区别:
sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。
wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁 之后才会进入可运行状态。睡眠时,会释放互斥锁。
join方法:当前线程调用,则其它线程全部停止,等待当前线程执行完毕,接着执行。
yield方法:该方法使得线程放弃当前分得的 CPU 时间。但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。
主要考察: sleep和wait方法所处的类是哪个,并且考察其在休眠的时候对于互斥锁 的处理。
上面是牛客的面试题总结,下面自己分析两个方法的区别:
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放:
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
3、使用的范围不同
wait必须在同步代码块中,在sync(){}中
4.Lock锁
传统Synchronized (后面为简化书写,统一写为sync)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /\*\* \* 卖票 \* \* @author 路飞 \* @create 2021 /1 /17 \*/public class SaleTicketDemo01 {public static void main (String[] args) {Ticket ticket = new Ticket ();new Thread (() -> {for (int i = 0 ; i < 40 ; i++) { ticket.saleTicket(); } }, "A" ).start();new Thread (() -> {for (int i = 0 ; i < 40 ; i++) { ticket.saleTicket(); } }, "B" ).start();new Thread (() -> {for (int i = 0 ; i < 40 ; i++) { ticket.saleTicket(); } }, "C" ).start(); } }class Ticket {private Integer tikcketNums = 50 ;public synchronized void saleTicket () {if (tikcketNums > 0 ) { System.out.println(Thread.currentThread().getName() + "卖出了" + (tikcketNums--) + "还剩" + tikcketNums); } } }
两个地方需要注意:
Lock接口
JDK1.8文档对它的定义:
Interface Lock
所有已知实现类:
ReentrantLock ReentrantReadWriteLock.WriteRead ReentrantReadWriteLock.WriteLock
public interface Lock
Lock
实现提供比使用synchronized
方法和语句可以获得的更广泛的锁定操作。
它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。
锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock 的读锁。
使用synchronized
方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内 。
虽然synchronized
方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁 。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock
接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。
随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用synchronized
方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语 :
1 Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。
Lock
实现提供了使用synchronized
方法和语句的附加功能,通过提供非阻塞尝试来获取锁 tryLock(),尝试获取可被中断的锁lockInterruptibly() ,以及尝试获取可以超时(tryLock(long, TimeUnit)。
1 一个`Lock`类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。 如果一个实现提供了这样的专门的语义,那么实现必须记录这些语义。
请注意, Lock
实例只是普通对象,它们本身可以用作synchronized
语句中的目标。 获取Lock
实例的监视器锁与调用该实例的任何lock()方法没有特定关系。 建议为避免混淆,您不要以这种方式使用Lock
实例,除了在自己的实现中。
除非另有说明,传递任何参数的null
值将导致NullPointerException被抛出。
文档已经说的相当清楚了,sync在获取锁时是获取所有锁以及释放以块的形式释放,并且当多个锁需要被释放时,是以相反的顺序,且都要被sync的代码块{}包裹。
Lock相比sync更灵活,手动加锁和释放锁,并且可以提供更多操作等等,Lock锁的模板也已经给出,下面直接测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 /\*\* \* @author 路飞 \* @create 2021 /1 /17 \*/public class SaleTicketDemo02 {public static void main (String[] args) {Ticket2 ticket = new Ticket2 ();new Thread (() -> {for (int i = 0 ; i < 40 ; i++) { ticket.saleTicket(); } }, "A" ).start();new Thread (() -> {for (int i = 0 ; i < 40 ; i++) { ticket.saleTicket(); } }, "B" ).start();new Thread (() -> {for (int i = 0 ; i < 40 ; i++) { ticket.saleTicket(); } }, "C" ).start(); } }class Ticket2 {private Integer tikcketNums = 50 ;Lock lock = new ReentrantLock ();public void saleTicket () { lock.lock(); try {if (tikcketNums > 0 ) { System.out.println(Thread.currentThread().getName() + "卖出了" + (tikcketNums--) + "还剩" + tikcketNums); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
可以看到创建Lock锁是先new了一个ReentrantLock() 可重入锁,翻阅源码有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /\*\* \* Creates an instance of {@code ReentrantLock}. \* This is equivalent to using {@code ReentrantLock(false )}. \*/public ReentrantLock () { sync = new NonfairSync (); } /\*\* \* Creates an instance of {@code ReentrantLock} with the \* given fairness policy. \* \* @param fair {@code true } if this lock should use a fair ordering policy \*/public ReentrantLock (boolean fair) { sync = fair ? new FairSync () : new NonfairSync (); }
可以看到new ReentrantLock() ,不传参数,走的无参构造,默认是NonfairSync() 非公平锁,若要指定为公平锁需要new ReentrantLock()的有参构造,传入true即可。
问题来了,什么是公平锁和非公平锁?
公平锁:十分公平,遵守先来后到
非公平锁:十分不公平,线程可以插队(默认)
可重入锁涉及的知识很多,后面再讲。
sync 和 Lock区别
1、Synchronized 内置的Java关键字, Lock 是一个Java类 2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁 3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁 4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下 去; 5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以 自己设置); 6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
5.生产者和消费者问题
生产者和消费者问题 sync版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 /\*\* \* 线程之间的通信问题:生产者和消费者问题 \* 线程交替执行 A B 操作同一变量 num = 0 \*A num+1 \*B num-1 \* \* @author 路飞 \* @create 2021 /1 /18 \*/public class A {public static void main (String[] args) {Data data = new Data ();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D" ).start(); } }class Data {private int number = 0 ;public synchronized void increment () throws InterruptedException {if (number != 0 ) {this .wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number);this .notifyAll(); }public synchronized void decrement () throws InterruptedException {if (number == 0 ) {this .wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number);this .notifyAll(); } }
这段代码的意思是定义了两个方法,一个负责num+1,一个负责num-1,由于加入sync,并且加了num是否为0的判断,所以按道理应该是线程进来,一个生成+1,一个消费-1,但运行结合出现:
D=>0 C=>1 A=>2 C=>3
很明显还是出现线程不安全的问题,查阅JDK帮助文档的wait方法,发现:
wait存在虚假唤醒,需要用把if改成while ,实现类似自旋锁的功能,始终去判断!
故原来的代码可以改成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Data {private int number = 0 ;public synchronized void increment () throws InterruptedException {while (number != 0 ) {this .wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number);this .notifyAll(); }public synchronized void decrement () throws InterruptedException {while (number == 0 ) {this .wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number);this .notifyAll(); } }
测试通过!
图解生产者和消费者
通过Lock找到Codition
可以发现,Lock版的生产者和消费者问题可以new 一个ReentrantLock实例,用它的实例去创建condition,然后再去实现线程之间的通信(生产者和消费者问题的本质)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 /\*\* \* JUC的Lock替代Sync \* \* @author 路飞 \* @create 2021 /1 /18 \*/public class B {public static void main (String[] args) {Data2 data = new Data2 ();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) {try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D" ).start(); } }class Data2 {private int number = 0 ;Lock lock = new ReentrantLock ();Condition condition = lock.newCondition();public void increment () throws InterruptedException { lock.lock();try {while (number != 0 ) { condition.await(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }public void decrement () throws InterruptedException { lock.lock();try {while (number == 0 ) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
那么到这,可能你会问?sync就可以实现生产者和消费者问题,为啥还要用Lock呢?代码明显增多了
Condition 实现精准的通知和唤醒线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 /\*\* \* JUC的精准通知和唤醒线程 \* \* @author 路飞 \* @create 2021 /1 /18 \*/public class C {public static void main (String[] args) {Data3 data = new Data3 ();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) { data.printA(); } }, "A" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) { data.printB(); } }, "B" ).start();new Thread (() -> {for (int i = 0 ; i < 10 ; i++) { data.printC(); } }, "C" ).start(); } }class Data3 {private Lock lock = new ReentrantLock ();private Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();private int number = 1 ;public void printA () { lock.lock();try {while (number != 1 ) { condition1.await(); } number = 2 ; System.out.println(Thread.currentThread().getName() + "BBBBB" ); condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }public void printB () { lock.lock();try {while (number != 2 ) { condition2.await(); } number = 3 ; System.out.println(Thread.currentThread().getName() + "CCCCCC" ); condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }public void printC () { lock.lock();try {while (number != 3 ) { condition3.await(); } number = 1 ; System.out.println(Thread.currentThread().getName() + "AAAAAA" ); condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }