目录
为什么要用线程同步?
问题的提出:
1.多个线程执行的不确定性引起执行结果的不确定性。
2. 多个线程对 同一个账户的共享操作(写共享资源时会出现线程安全问题,读不会出现),会造成操作的不完整性,会破坏数据。
当多个用户在同一时刻操作同一个账户的时候,就可能会出现线程安全的问题。
线程同步案例:卖票
例子:创建三个窗口买票,总票数为100张,使用实现Runnable接口的方式
1.问题:买票过程中,出现了重票,错票—>出现了线程安全的问题。
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作票的时候,其他线程不能参与进来。直到线程a操作完票后,其他线程才可以开始操作ticket. 这种情况即使线程a出现了阻塞,也不能被改变。
4.在java中,我们通过同步机制,来解决线程的安全问题。
卖票案例线程安全问题图示:
解决线程安全问题的方式一:同步代码块
synchronized (同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码。–>{}不能包含代码多了,也不能少了。(注意:不能把while(true)代码放到synchronized 中,因为放进去之后,只有一个线程进去自己循环卖票,其他两个线程在外面等,等循环完后,出synchronized 外后,其他两个线程进去发现票都卖完了。)
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要公用同一把锁。好比是火车上的厕所。一个车厢的所有人都看的是同一个厕所那个显示灯,显示有人,无人。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
代码演示(实现Runnable接口的方式多线程卖票):
package com.fan.thread3;
public class TicketTest1 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//注意,这里我们三个窗口t1,t2,t3都是放的同一个对象myRunnable(唯一性),所以可以不用static修饰。
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//使用实现Runnable接口的方式的多线程
class MyRunnable implements Runnable{
private int ticket = 100;//总共100张票
//Object obj = new Object();//监视器,保证是唯一的同一个
public void run() {
while (true){
Object obj = new Object();
//此处开始是操作共享数据的部分
synchronized (this){//此时的this:唯一的MyRunnable对象/*synchronized (obj){//方式二*/
if(ticket > 0){
try {
Thread.sleep(50);//线程休眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + "正在卖第"+ ticket +"张票");
ticket--;
}else{
break;
}
}
}
}
}
同步技术的原理图:
比如现实中,我们去银行门口的自动取款机取钱,取款机的钱就是共享变量,为了保障安全,不可能两个陌生人同时进入同一个取款机内取钱,所以只能一个人进入取钱,然后锁上取款机的门,其他人只能在取款机门口等待。
取款机有多个,里面的钱互不影响,锁也有多个(多个对象锁),取钱人在多个取款机里同时取钱也没有安全问题。
假如每个取钱的陌生人都是线程,当取钱人进入取款机锁了门后(线程获得锁),取到钱后出门(线程释放锁),下一个人竞争到锁来取钱。
同理下图卖票原理也是一样:
代码演示:(继承系统类Thread的方式实现多线程卖票)
补充:在继承Thread类创建多线程的方式中,慎用this充当同步监视器。考虑使用当前类充当同步监视器。
package com.fan.thread4;
public class TicketTest1 {
public static void main(String[] args) {
//创建Thread的子类对象,也是线程。子父类关系:Runnable-->Thread-->Window
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//使用实现Runnable接口的方式的多线程
class Window extends Thread{
//注意,这里ticket就必须是静态共享的。
private static int ticket = 100;//总共100张票,
//监视器(同步锁),保证共享的是唯一的同一个锁
private static Object obj = new Object();
public void run() {
while (true){
//此处开始是操作共享数据的部分
synchronized (obj){
if(ticket > 0){
try {
Thread.sleep(50);//线程休眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + "正在卖第"+ ticket +"张票");
ticket--;
}else{
break;
}
}
}
}
}
解决线程安全问题的方式二:同步方法
在一个方法的前边使用关键字synchronized 修饰,使得这个方法成为一个同步的。
代码演示:(实现Runnable接口的方式多线程卖票)
package com.fan.thread3;
public class TicketTest1 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//注意,这里我们三个窗口t1,t2,t3都是放的同一个对象myRunnable(唯一性),所以可以不用static修饰。
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//使用实现Runnable接口的方式的多线程
class MyRunnable implements Runnable{
private int ticket = 100;//总共100张票
public void run() {
while (true){
show();//在run方法中调用同步方法。
}
}
//自定义一个方法,使其成为同步方法。然后在while(true)中调用
public synchronized void show(){//同步方法中的同步监视器:隐含的this
if(ticket > 0){
try {
Thread.sleep(50);//线程休眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + "正在卖第"+ ticket +"张票");
ticket--;
}
}
}
此同步方法的方式默认是this充当同步监视器。
代码演示:(继承系统类Thread的方式实现多线程卖票)
package com.fan.thread4;
public class TicketTest1 {
public static void main(String[] args) {
//创建Thread的子类对象,也是线程。子父类关系:Runnable-->Thread-->Window
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//使用实现Runnable接口的方式的多线程
class Window extends Thread{
//注意,这里ticket就必须是静态共享的。
private static int ticket = 100;//总共100张票,
public void run() {
while (true){
show();//调用同步方法。
}
}
//自定义仅供本类使用的一个同步方法:
private static synchronized void show (){//此时默认的同步监视器是本类的class,即Window.class,注意加static保证共享一份
// private synchronized void show (){//如果这样,同步监视器是t1,t2,t3,是不能实现线程安全的。
if(ticket > 0){
try {
Thread.sleep(50);//线程休眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + "正在卖第"+ ticket +"张票");
ticket--;
}
}
}
关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只不过不需要我们显式的声明。
2.非静态的同步方法,同步监视器是:this; 静态的同步方法,同步监视器是:当前类本身
卖票案例代码总结:
同步的方式,解决了线程的安全问题。—>好处。
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。—>局限性。
线程同步的锁的演示:
没锁的情况:
有锁的状态:
线程的死锁:
解决线程安全问题的方式三:Lock锁
Synchronized和Lock的区别
1.synchronized内置的关键字,lock是一个java类(Lock是接口)。
2.synchronized会自动释放锁,lock在finally中必须手动释放锁!如果不释放锁,就会造成死锁。
3.synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁。
4.synchronized适合锁少量代码同步,lock适合锁做大量同步代码。
5.synchronized:假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待;lock:Lock有多个锁的获取方式,可以尝试获得锁(boolean tryLock() ),就不一定会等待下去。
6.synchronized可重入锁,不可以中断,,非公平;Lock:可重入锁,可以判断锁,,可公平(两者皆可)。
代码演示lock锁
package com.fan.lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}}
class Window implements Runnable{
private int ticket = 100;
//实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
public void run() {
while(true){
try{
//调用锁定方法lock()
lock.lock();
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":正在卖第"+ticket+"张票");
ticket--;
}
}finally {
//调用解锁方法:unlock()
lock.unlock();
}
}
}
}
实际开发的Lock用法:
真正的多线程开发,公司中的开发,为了降低代码的耦合性,就不用一个类去实现线程的接口(Runnable/Callable)或者继承线程类(Thread),而是仅仅设计成一个纯净的资源类,包含自己的资源类的属性和方法。即
1.线程就是一个单独的资源类,没有任何附属的操作。
2.并发:多线程操作同一个资源类,把资源类放入到线程
3.原先的我们用匿名内部类实现多线程:比较繁琐。我们使用函数式接口,jdk1.8后使用lambda表达式(参数)->{//代码}
公平锁和非公平锁:
公平锁:十分公平,可以先来后到。
非公平锁:十分不公平,可以插队(ReentrantLock默认是非公平锁,可以通过构造方法的参数进行设置公平性,true:公平,false:非公平)
实际开发中代码演示lock锁:
package com.fan.domain.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketTest {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类放入到线程
//(1)这里ticket是同一个资源,保证是共享的资源,以下是三个线程
Ticket ticket = new Ticket();
//(2)多个线程操作同一个资源
new Thread(new Runnable() {
//匿名内部类实现:比较繁琐。我们使用函数式接口,jdk1.8后使用lambda表达式
@Override
public void run() {
for (int i = 0; i < 100; i++) {
ticket.sale();
}
}
},"窗口1").start();
//我们使用函数式接口,jdk1.8后使用lambda表达式(参数)->{//代码}
new Thread(()->{
for (int i = 0; i < 100; i++) {
ticket.sale();
}
},"窗口2").start();
//我们使用函数式接口,jdk1.8后使用lambda表达式(参数)->{//代码}
new Thread(()->{
for (int i = 0; i < 100; i++) {
ticket.sale();
}
},"窗口3").start();
}
}
//资源类OOP
class Ticket {
//lock三步曲:1.new ReentrantLock();//2.加锁;//3.finally-->解锁
//属性
private int ticket =100;//100张票
//1.new ReentrantLock()
Lock lock = new ReentrantLock();//Lock是接口,ReentrantLock是实现类,是可重入锁
//卖票的方法,
public /*synchronized*/ void sale(){//传统额方式是synchronized(本质是排队)
//2.加锁
lock.lock();
try {
//业务代码写在这里
if(ticket > 0){//如果票数大于零
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
}
ticket--;
} catch (Exception e) {
e.printStackTrace();
} finally {
//3.finally-->解锁
lock.unlock();
}
}
}
面试题:
synchronized与Lock的异同?
相同点:两者都可以解决线程安全的问题。
不同:synchronized机制在执行完响应的同步代码后,自动释放同步监视器。Lock需要手动启动同步(lock();),同时需要手动结束同步(unlock();)。
开发中:优先使用顺序
Lock --> 同步代码块 (已经进入了方法体,分配了相应的资源) -->同步方法(在方法体外)
线程通信:wait+notify
涉及到三个方法:
1.wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(线程不再活动,不再参与调度,进入wait set 中,因此不会浪费CPU资源,也不会去竞争锁,这时的线程的状态是WAITING.它还要等着别的线程执行一个特别的动作,也就是“通知notify”,在这个对象上等待的线程从wait set中释放出来,重新进入调度队列ready queue)中 。
2.notify():一旦执行此方法,就会唤醒此对象监视器上等待的(wait)的单个线程,会继续执行wait方法之后的代码,如果有多个线程被wait,就唤醒优先级高的那个。(选取所通知对象的wait set中的一个线程释放。)
3.notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是 同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常。
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中的(因为任何一个类的对象都可以充当同步监视器,同步监视器对象都可以调用这三个方法,即任何对象都可以调用这三个方法,所以将这三个方法定义在Object中)。
代码演示(使用两个线程打印1-100.线程1,线程2交替打印):
package com.fan.thread3;
public class NumberTest {
public static void main(String[] args) {
Number number = new Number();
//创建两个线程
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable {
private int number = 1;
private Object obj =new Object();
public void run() {
while(true){
synchronized (obj){//同一个number//
// 方式二:synchronized (this){
obj.notify();//唤醒一个,一进来同步区就唤醒其他线程//
// 方式二
//this.notify();
if(number <=100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ":"+number);
number++;
try {
obj.wait();//使得调用如下wait方法的使得成进入阻塞状态
//方式二:
// this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}3)
}
}
面试题:sleep()和wait()的异同:
1.相同点:一旦执行方法,都可以使得当前线程进入阻塞状态。
2.不同点:
1)两个方法声明的位置不同:sleep()声明在Thread中,wait()方法声明在Object中。
2)调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步方法中或同步代码块中。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会立即释放锁(同步监视器)。
线程通信之生产者和消费者:
注意:
1.生产者和消费者得保证等待和唤醒只能有一个在执行。
2.同步使用的锁对象必须保证唯一。
3.只有锁对象才能调用wait和notify方法。
4.进入计时等待的两种方式:
1)使用sleep(Long m) 方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态。
2)使用wait(Long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态。
代码演示普通版生产者和消费者(普通synchronized ):
代码如下:
//测试类
package com.fan.domain.goods;
public class ProducerTest {
public static void main(String[] args) {
//保证缓冲区是共享的。生产者和消费者可以是多个
Storage storage = new Storage();
Thread t1 = new Thread(new Producer(storage));
t1.setName("生产者1");
Thread t2 = new Thread(new Producer(storage));
t2.setName("生产者2");
Thread c1 = new Thread(new Consumer(storage));
Thread c2 = new Thread(new Consumer(storage));
c1.setName("消费者1号");
c2.setName("消费者2号");
t1.start();
t2.start();
c1.start();
c2.start();
}
}
//仓库,缓冲区,资源类==》判断等待,业务,通知
class Storage {
private int num = 0;//容量为10,开始为0
private Object obj = new Object();//同步监视器
public void produce() {
synchronized (obj){
if(num < 10){//容量为10
num ++;//如果数量少于最大容量,可以继续生产
System.out.println(Thread.currentThread().getName()+"开始生产第"+num + "个产品");
//生产完事后,唤醒消费者去消费
obj.notify();
}else {
//如果缓存区满了,则等待去消费
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void consume() {
synchronized (obj){
//如果消费者发现产品有
if(num > 0){
System.out.println(Thread.currentThread().getName()+"开始消费第"+num + "个产品");
num--;//先消费,后减一
//消费完事后,唤醒生产者去生产
obj.notify();
}else{
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//生产者线程
class Producer implements Runnable {
//生产者和消费者共享仓库
private Storage storage;
public Producer() {
}
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
//生产者循环生产
while (true){
try {
Thread.sleep(1000);
//核心逻辑代码
storage.produce();//生产者生产产品
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者线程
class Consumer implements Runnable {
//生产者和消费者共享仓库
private Storage storage;//生产者和消费者共享仓库
public Consumer() {
}
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
//消费者循环消费
while (true){
try {
Thread.sleep(2000);
//核心逻辑代码
storage.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
以上代码也可以使用同步方法,将produce方法和consume方法前加synchronized即可,也可以使用继承线程类Thread来实现多线程。
使用lambda表达式等简化开发(必须设置开发环境为1.8):
代码如下:
package com.fan.domain.goods2;
public class ProducerTest {
public static void main(String[] args) {
//使用lambda表达式简化开发
//共享的资源
Storage storage = new Storage();
//使用两个生产者和两个消费者演示
new Thread(()->{
//循环生产
while (true){
try {
Thread.sleep(1000);//sleep为了让演示效果更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.produce();
}
},"生产者1").start();
new Thread(()->{
//循环生产
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.produce();
}
},"生产者2").start();
//循环消费
new Thread(()->{
//循环消费
while (true){
try {
Thread.sleep(2000);//sleep为了让演示效果更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.consume();
}
},"消费者1").start();new Thread(()->{
//循环消费
while (true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.consume();
}
},"消费者2").start();
}
}
//共享的资源类,也是缓冲区
class Storage {
private int num = 0;//容量为10,开始为0
private Object obj = new Object();//同步监视器
public void produce() {
synchronized (obj){
if(num < 10){//容量为10
num ++;//如果数量少于最大容量,可以继续生产
System.out.println(Thread.currentThread().getName()+"开始生产第"+num + "个产品");
//生产完事后,唤醒消费者去消费
obj.notify();
}else {
//如果缓存区满了,则等待去消费
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void consume() {
synchronized (obj){
//如果消费者发现产品有
if(num > 0){
System.out.println(Thread.currentThread().getName()+"开始消费第"+num + "个产品");
num--;//先消费,后减一
//消费完事后,唤醒生产者去生产
obj.notify();
}else{
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
升级版的代码(在资源类中使用while判断,防止虚假唤醒):
代码如下:
package com.fan.domain.goods2;
public class ProducerTest {
public static void main(String[] args) {
//使用lambda表达式简化开发
//共享的资源
Storage storage = new Storage();
//使用两个生产者和两个消费者演示
new Thread(()->{
//循环生产,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
storage.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者1").start();
new Thread(()->{
//循环生产,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
storage.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者2").start();
//循环消费
new Thread(()->{
//循环消费,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(2000);
storage.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者1").start();
new Thread(()->{
//循环消费,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(2000);
storage.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者2").start();
}
}
//共享的资源类,也是缓冲区
class Storage {
private int num = 0;//容量为10,开始为0
private Object obj = new Object();//同步监视器
public void produce() {
synchronized (obj){
while (num >= 10){//容量为10,满了则等待,这里的while判断是为了防止虚假唤醒
try {
obj.wait();//等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//否则
num ++;//如果数量少于最大容量,可以继续生产
System.out.println(Thread.currentThread().getName()+"开始生产第"+num + "个产品");
//生产完事后,唤醒消费者去消费
obj.notifyAll();
}
}
public void consume() {
synchronized (obj){
//如果消费者发现产品没有了,则无限等待
while (num == 0){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//否则
System.out.println(Thread.currentThread().getName()+"开始消费第"+num + "个产品");
num--;//先消费,后减一
//消费完事后,唤醒生产者去生产
obj.notifyAll();
}
}
}
结果展示
JUC版本的生产者和消费者(Lock锁版本的):
代码如下:
package com.fan.domain.goods3;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerTest {
public static void main(String[] args) {
//使用lambda表达式简化开发
//共享的资源
Storage storage = new Storage();
//使用两个生产者和两个消费者演示
new Thread(()->{
//循环生产,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
storage.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者1").start();
new Thread(()->{
//循环生产,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
storage.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者2").start();
//循环消费
new Thread(()->{
//循环消费,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(2000);
storage.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者1").start();
new Thread(()->{
//循环消费,假设循环10次
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(2000);
storage.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者2").start();
}
}
//共享的资源类,也是缓冲区
class Storage {
private int num = 0;//容量为10,开始为0
Lock lock = new ReentrantLock();//创建一个可重入锁
Condition condition = lock.newCondition();//创建一个监视器condition
public void produce() {
lock.lock();
try {
//业务代码
while (num >= 10){
try {
condition.await();//lock版本等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//否则
num ++;//如果数量少于最大容量,可以继续生产
System.out.println(Thread.currentThread().getName()+"开始生产第"+num + "个产品");
//生产完事后,唤醒消费者去消费
condition.signalAll();//lock版本的唤醒
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
//业务代码
//如果消费者发现产品没有了,则无限等待
while (num == 0){
try {
condition.await();//lock版本的等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//否则
System.out.println(Thread.currentThread().getName()+"开始消费第"+num + "个产品");
num--;//先消费,后减一
//消费完事后,唤醒生产者去生产
condition.signalAll();//lock版本的唤醒
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}