java线程同步

为什么要用线程同步?

问题的提出:

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();
        }

    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值