个人复习Java多线程的完整手敲笔记,看完你也就学会多线程了

最近在复习多线程的相关知识,整理了一下笔记,有很多代码案例,给大家分享一下。

目录

1、什么是进程和线程?

2、启动一个Java程序的进程和线程

3、存在问题:主线程结束,其他线程可能还在运行

4、实现线程有三种方式

5、方式一:继承Thread类

run()方法:

start()方法

代码展示

6、方式二:实现Runnable接口

代码展

合并的说明

7、方式三:FutureTask方式

8、采用匿名内部类实现多线程

9、线程的生命周期

10、线程名字、当前线程对象

getName():获取线程对象的名字

setName():修改线程对象的名字

默认的线程名字

currentThread():获取当前线程对象

11、sleep()方法:让线程睡眠

sleep():让线程睡眠

interrupt():唤醒正在睡眠的线程

12、终止线程

stop():强行终止一个线程(过时,不建议使用)

return:合理的方式结束进程

13、线程调度(了解)

14、线程安全(重点)

synchronized (共享的数据){}:线程同步语句块

总结:

开发中解决线程安全问题的方案

15、死锁

16、守护线程

17、定时器

18、wait和otify(生产者和消费者模式)

19、生产者和消费者模式


 

1、什么是进程和线程?

  1. 进程是一个应用程序(一个程序是一个进程)

  2. 线程是一个进程中的执行场景/执行单元(一个进程可以启动多个线程)(一个线程一个栈)

2、启动一个Java程序的进程和线程

  1. 当在DOS窗口中启动一个Java程序时,会先启动JVM,JVM是一个进程;JVM再启动一个主线程调用main方法;同时在启动一个垃圾回收线程负责看护、回收垃圾。

  2. 一个运行中的Java程序中,至少有两个线程并发,一个是垃圾回收机制,一个是执行main方法的主线程

3、存在问题:主线程结束,其他线程可能还在运行

  1.  

4、实现线程有三种方式

  1. 方式一:继承java.lang.Thread类,重写run()方法

  2. 方式二:(建议)编写一个类,实现java.lang.Runnable接口,实现run()方法

  3. 方式三:FutureTask方式,实现Callable接口(jdk8新增)

  4. 注意:建议使用实现接口的方式,因为一个类实现接口,它还可以去继承其他的类,更加灵活

5、方式一:继承Thread类

  1. run()方法:

    1. 该方法在线程创建成功后自动调用

    2. 该方法的内容是线程需要执行的功能

  2. start()方法

    1. 调用start方法启动线程。

    2. start方法的任务是开辟一个栈空间,空间开辟出来该方法就结束了(瞬间结束)。开辟成功后,线程就创建成功了,然后会自动调用run方法在分支栈的栈底部(压栈)

    3. main方法中start方法不执行完毕不会继续执行下去,但是该方法是瞬间结束

  3. 代码展示

package com.bjpowernode.java.thread;
// 实现多线程的第一种方法:编写一个类,直接继承Thread类,并重写run方法
public class ThreadTest02  {
    public static void main(String[] args) {
        // 2、新建一个分支线程对象
        MyThread myThread = new MyThread();
        // 3、调用start方法启动线程。
        myThread.start();
        // 下面的这段代码运行在主线程中
        for (int i=0; i<1000; i++){
            System.out.println("主线程 ----> " + i);
        }
    }
}

// 1、定义线程类“编写一个类,直接继承Thread类,并重写run方法
class MyThread extends Thread {
    @Override
    public void run() {
        // 编写程序。这段程序运行在分支线程(分支栈)中
        for (int i=0; i<1000; i++){
            System.out.println("分支线程 ----> " + i);
        }
    }
}

 

6、方式二:实现Runnable接口

  1. 代码展

package com.bjpowernode.java.thread;

// 实现线程的方式二:编写一个类,实现java.lang.Runnable接口,实现run()方法
public class ThreadTest03 {
    public static void main(String[] args) {
        // 2、3合并
        Thread t = new Thread(new MyRunnable());//先创建一个可运行对象,再封装成一个线程对象
        // 4、调用start方法启动线程。
        t.start();

        for (int i=0; i<100; i++){
            System.out.println("主线程 ----> " + i);
        }
    }
}

// 1、实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0; i<100; i++){
            System.out.println("分支线程 ----> " + i);
        }
    }
}
  1. 合并的说明

    1. 下面两句代码合并成最后那句代码

// 2、新建一个可运行对象
MyRunnable myRunnable = new MyRunnable();
// 3、将可运行的对象封装成一个线程对象
Thread t = new Thread(myRunnable);

 

// 2、3合并
Thread t = new Thread(new MyRunnable());//先创建一个可运行对象,再封装成一个线程对象

7、方式三:FutureTask方式

  1. FutureTask方式实现的线程可以获取线程的返回值(前两种方式是无法获取线程返回值的,因为run方法返回的是null)

  2. 优点:可以拿到返回值;缺点:效率低

  3. 代码实例

package com.bjpowernode.java.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest15 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 第一步:创建一个”未来任务类“对象。FutureTask的参数需要一个Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            // call方法相当于run方法,只不过call方法有返回值。线程执行完任务之后会有一个执行结果
            public Object call() throws Exception {
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end");
                int a = 100;
                int b = 200;
                return a+b;
            }
        });
        // 创建线程对象
        Thread t = new Thread(task);
        // 启动线程
        t.start();
        // 在主线程中获取t线程的返回结果
        /**
         * main方法里的程序要想继续执行必须等待get()方法执行结束,而get方法可能需要很久。
         * 因为get方法想要拿到另一个线程的执行结果,就要等待该线程执行结束,而该线程的执行需要一定的时间
         */
        Object obj = task.get();
        System.out.println("hello world!");
    }
}

8、采用匿名内部类实现多线程

  1. 代码展示

 

package com.bjpowernode.java.thread;
public class ThreadTest04 {
    public static void main(String[] args) {
        // 创建线程对象,采用匿名内部类的方式(匿名内部类没有类名,直接new类)
        Thread t =new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i<100; i++){
                    System.out.println("t线程 ----> " + i);
                }
            }
        });
        //启动线程
        t.start();

        for (int i=0; i<100; i++){
            System.out.println("main线程 ----> " + i);
        }
    }
}

9、线程的生命周期

  1. 五个状态:

    1. 新建状态

    2. 就绪状态

    3. 运行状态

    4. 阻塞状态

    5. 死亡状态

10、线程名字、当前线程对象

  1. getName():获取线程对象的名字

String name = 线程对象.getName();
  1. setName():修改线程对象的名字

线程对象.setName("线程名字");
  1. 默认的线程名字

  2. 主线程:main

  3. 分支现场:Thread-0、Thread-1、Thread-2、Thread-3……

  4. currentThread():获取当前线程对象

    1. currentThread()是一个静态方法

    2. 该代码出现在main方法中,所以当前线程是主线程,主线程的线程名字叫main

    3. 常使用该方法拿到当前线程对象,对当前线程进行操作

 

Thread 对象名 =Thread.currentThread();

 

  1. 代码展示

 

package com.bjpowernode.java.thread;
// 获取线程对象、获取线程对象的名字、修改线程对象的名字
public class ThreadTest05 {
    public static void main(String[] args) {
        // currentThread()获取当前线程对象(当前所运行的线程)
        Thread currentThread =Thread.currentThread();
        System.out.println("分支:" + currentThread.getName());

        // 创建线程对象
        MyThread2 t = new MyThread2();
        // 获取默认的的线程名字
        String tName1 = t.getName(); // Thread-0
        System.out.println(tName1);
        // 设置线程的名字
        t.setName("tttt"); // tttt
        // 获取修改后的线程名字
        String tName2 = t.getName(); // tttt
        System.out.println(tName2);
        // 启动线程
        t.start();
    }
}

class MyThread2 extends Thread {
    public void run(){
        Thread currentThread =Thread.currentThread();
        System.out.println("分支:" + currentThread.getName());
        for (int i=0; i<10; i++){
            System.out.println("分支线程 ----> " + i);
        }
    }
}

 

11、sleep()方法:让线程睡眠

  1. static void sleep(long millis)方法

    1. 静态方法(Thread.sleep(毫秒);)

    2. 参数是毫秒

    3. 作用:让当前线程进入休眠状态,进入阻塞状态,放弃占有的CPU时间片,让其他线程使用

  2. sleep():让线程睡眠

    1. 代码展示

 

public class ThreadTest06 {
    public static void main(String[] args) throws InterruptedException {
        for (int i=0; i<10; i++){
            System.out.println(Thread.currentThread().getName() + "---->" + i);
            // 睡眠一秒
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
  1. 注意:

    1. sleep是静态方法,t线程不会进入睡眠,而是让当前线程进入休眠,也就是main线程,因为运行时会转换为:Thread.sleep(1000*5)

public static void main(String[] args) {
    try {
        t.sleep(1000*5);// sleep是静态方法,t线程不会进入睡眠,而是让当前线程进入休眠,研究生main线程
                                // 因为运行时会转换为:Thread.sleep(1000*5)
    } 
}
  1. interrupt():唤醒正在睡眠的线程

    1. 依靠了异常处理机制,睡眠语句会异常

    2. run()方法中的异常不能throws,只能try catch。因为run()方法在父类中没有抛出如何异常,字类不能比父类抛出更多的异常

 

package com.bjpowernode.java.thread;
// 唤醒一个正在睡眠的线程(不是中断线程的执行,而是中断线程的睡眠)
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread((new MyRunnable2()));
        t.setName("t");
        t.start();

        // 希望5秒之后唤醒t线程
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 中断t线程的睡眠(依靠了异常处理机制,睡眠语句会异常)
        t.interrupt();// 干扰
    }
}

class MyRunnable2 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---->begin");
        try {
            Thread.sleep(1000*60*60*24*365);// 睡一年
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---->end");
    }
}

12、终止线程

  1. stop():强行终止一个线程(过时,不建议使用)

    1. 这种方式存在很大的缺点:容易丢失数据,因为这种方式是直接杀死线程,如果还没有来得及保存数据会造成数据丢失

package com.bjpowernode.java.thread;
// stop()强行终止一个线程的执行,不建议使用
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();
        // 模拟5秒
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 5秒之后强行停止睡眠一年的t线程
        t.stop();// 已过时,不建议使用
    }
}

class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for (int i=0; i<10; i++){
            System.out.println(Thread.currentThread().getName() + "---->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. return:合理的方式结束进程

    1. 在run()方法中,打一个布尔标记,然后通过对布尔标记进行判断,满足终止线程条件时,调用return终止线程

    2. 在调用return之前,通过可以进行一些终止之前的准备,例如保存数据等

package com.bjpowernode.java.thread;
// 合理地终止一个线程的执行(常用)
public class ThreadTest010 {
    public static void main(String[] args) {
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        // 模拟5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终止线程(当想要终止线程时,把run属性值修改为false即可)
        r.run = false;
    }
}

class MyRunable4 implements Runnable{
    // 打一个标记
    boolean run = true;
    
    @Override
    public void run() {
        for (int i=0; i<10; i++){
            if (run){
                System.out.println(Thread.currentThread().getName() + "---->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // 在return之前可以进行一些线程结束的准备工作,如保存数据
                System.out.println(Thread.currentThread().getName() + "线程结束了");
                return;// 终止线程
            }
        }
    }
}

13、线程调度(了解)

  1. 常用线程调度模型:

    1. 抢占式调度模型

      1. 那个线程的优先级比较高,抢到的CPU时间片的概率就更高。Java采用的就是抢占式调度模型

    2. 均分式调度模型

      1. 平均分配CPU时间,每个线程抢占到的CPU时间一样长。有一些编程语言采用这种方式

  2. Java提供的和线程调度有关的方法

    1. 实例方法:

      1. viod setPriority(int newPriority):设置线程的优先级

      2. int getPriority():获取线程优先级

      3. void join():合并线程

        1. 实例:在main方法中调用t.join();,则主线程进入阻塞,t线程执行,t线程执行结束后,主线程才会执行

      4. 说明:优先级是1至10的整数,默认优先级是5(理论上优先级高会获得更多的时间,但也不完全是)

    2. 静态方法:

      1. static void yield():让位方法

        1. 暂停当前正在执行的线程对象,并执行其他线程。

        2. 该方法不是阻塞方法,让当前线程让位,让给其他线程使用。会让当前线程的”运行状态“回到”就绪状态“。回到就绪状态后,所有线程一起抢占,有概率继续抢到时间片

14、线程安全(重点)

  1. 为什么线程安全是重点?

    1. 在项目开发中,我们的项目是运行在服务器中,而服务器已经将线程的定义、线程对象的创建、线程的启动等都实现了,我们不需要编写多线程

    2. 我们编写的程序需要放在一个多线程的换季下运行,需要更需要关注的是数据在多线程并发的环境下是否是安全的

  2. 存在线程安全问题的三个条件(条件均需满足):

    1. 多线程并发

    2. 有共享数据

    3. 共享数据有修改行为

  3. 解决线程安全问题

    1. 当多线程并发,并存在需修改的共享数据时存在线程安全问题

    2. 解决方案:

      1. 线程同步机制:线程排队执行(不并发):用排队执行解决线程安全问题

      2. 线程排队会消耗一定的效率,效率换安全

  4. 线程同步的两个专业术语:

    1. 异步编程模型

      1. 线程1和线程2,个自执行各自的(独立执行),这种编程模型叫作异步编程模型

      2. 说白了就是多线程并发,效率较高

    2. 同步编程模型

      1. 线程1和线程2,在一个线程执行的时候,另一个必须等待正在执行的线程执行结束才会执行,这种编程模型就是同步编程模型

      2. 线程与线程之间有等待关系,效率较低

  5. synchronized (共享的数据){}:线程同步语句块

    1. 语法

synchronized (共享的数据){
    // 要执行的语句
}
  1. 说明:

    1. 同步代码块越小,效率越高

    2. 最好不要嵌套使用,容易导致死锁

  2. 举例:共享数据必须是多线程的数据,才能达到多线程排队当有t1-t5五个线程,123需排队,45不排队,则共享数据要是t1-t3共享的对象,而对于t4t5来说不是共享的

  3. 原理:

    1. 假设t1和t2线程并发,开始执行的时候,肯定会有先后

    2. 假设t1先执行,遇到synchronized语句块,这是会自动找共享对象的对象锁,找到之后占有对象锁,然后执行同步语句块中的代码。在程序执行的过程中,一直都占有对象锁,直到同步语句块执行结束,才归还对象锁(对象锁是一个标记,每个对象都有对象锁)

    3. 假设t1已经占有这把锁,此时t2也遇到synchronized,此时会去找共享对象的对象锁,但此时这把锁被t1占有,t2只能等待t1归还,然后t2占有对象锁,开始执行同步代码块中的程序

  1. 存在线程安全的变量

    1. Java中的三大变量

      1. 实例变量:在堆中(存在线程安全问题)

      2. 静态变量:在方法区中(存在线程安全问题)

      3. 局部变量:在栈中(不存在线程安全问题)

    2. 三大变量中,局部变量永远不会存在线程安全问题,因为局部变量不共享(局部变量在栈中,一个线程一个栈)。实例变量在堆中,静态变量在方法区中,堆和方法区都是只有一个,所有堆和方法区都是多线程共享的,所以可能存在线程安全问题

    3. 如果使用局部变量,建议使用StringBuilder,因为局部变量不存在线程安全问题,所以选择StringBuilder,而StringBuffer效率比较低

  1. synchronized可以出现在实例方法中(不常用)

    1. 但此时锁的一定是this,不能是其他对象,所以这种方式不灵活

    2. synchronized出现在实例方法上,表示整个方法都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低

    3. 优点是:代码量少

    4. 示范

public synchronized void doSome(){
    // 代码块
}
  1. 总结:

    1. synchronized有三种写法:

      1. 第一种:同步代码块

        1. 表示找对象锁,优点:灵活

      2. 第二种:在实例方法中使用synchronized

        1. 表示共享对象一定是this,并且同步代码块是整个方法

      3. 第三种:在静态方法上使用synchronized

        1. 表示找类锁。类锁永远只有一把

  2. 开发中解决线程安全问题的方案

    1. 方案一:

      1. 尽量使用”局部变“量代替”实例变量和静态变量“

    2. 方案二:

      1. 如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存不共享(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有线程安全问题)

    3. 方案三:

      1. 如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized

    4. 注意:

      1. 不要一上来就使用synchronized,synchronized会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差。在不得已的情况下才选择synchronized线程同步机制

15、死锁

  1. 例子:线程t1需要锁对象1和对象2,而线程t2也需要锁对象1和对象2。如果t1锁了对象1,还没来得及锁对象2,t2就把对象2锁了,这样t1锁不到对象2,t2锁不到对象1,场面僵持不下,就造成了死锁

  2. 死锁代码:需要会写,面试时可能会让写

package com.bjpowernode.java.deadlock;
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        // t1和t2共享两个线程的o1和o2
        Thread t1 = new MyThread1(o1, o2);
        Thread t2 = new MyThread2(o1, o2);
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){
            }
        }
    }
}

class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
            }
        }
    }
}

16、守护线程

  1. Java语言中线程分为两大类

    1. 一类是:用户线程

    2. 一类是:守护线程(后台线程)

  2. 守护线程的特点:

    1. 一般守护线程是一个死循环,全部用户线程结束,守护线程会随之结束

  3. setDaemon(true);:将线程设置为守护线程

线程对象.setDaemon(true);

17、定时器

  1. 作用:间隔特定的时间,执行特定的程序

  2. 多种实现方式:

    1. 方式1:可以使用sleep方法,通过设置睡眠时间来定时执行任务(这是最原始的计时器,比较low)

    2. 方式2:在Java的类库中,已经写好了一个定时器(java.util.Timer),可以直接使用。不过这种方式在开发中很少使用,因为很多高级框架都支持定时任务

    3. 方式3(常用):使用高级框架中的定时器。目前使用最多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时任务(SpringTask框架是基于java.util.Timer实现的)

    4. 实例

package com.bjpowernode.java.thread;
import javax.xml.crypto.Data;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SimpleTimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.SimpleFormatter;
// 使用定时器指定定时任务
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        // 创建定时器对象
        Timer timer = new Timer();
        // 可以设置为守护线程
        //Timer timer = new Timer(true);

        // 指定定时任务
        // 语法:timer.schedule(定时任务, 第一次执行时间, 每次执行间隔的时间)
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-09-25 21:23:00");
        timer.schedule(new LogTimerTask(), firstTime, 1000*10);
    }
}
// 编写一个定时任务类(也可以使用匿名内部类)
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {
    @Override
    public void run(){
        // 编写需要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ":成功完成一次数据备份!");
    }
}

18、wait和otify(生产者和消费者模式)

  1. wait和notfy方法不是线程对象的方法,是Java如何一个Java对象都有的方法,因为这两个方法是Object自带的

  2. wait和notfy方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题

  3. 生产者和消费者模式

    1. 生产线程负责生产,消费者线程负责消费。生产线程和消费线程要达到均衡。这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和ontify方法

  4. wait()方法

Object o = new Object();
o.wait();
  1. 作用:表示让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒(调用otify方法),会释放掉该线程之前占有的o对象上占有的锁

  1. otify()方法

Object o = new Object();
o.otify();
  1. 作用:表示唤醒正在o对象上等待的线程。只是通知,不会释放o对象上之前占有的锁

  1. notifyAll()方法

    1. 作用:notifyAll()方法可以唤醒o对象上处于等待的所有线程

  2. 图示

19、生产者和消费者模式

  1. 代码实例

package com.bjpowernode.java.thread;
import java.util.ArrayList;
import java.util.List;
// 使用wait方法和notify方法实现生产者和消费者模式
// 模拟需求:仓库是一个List集合,List集合中假设只能存储一个元素,只存一个元素仓库就满了。
// 如果List集合中元素个数是0,就表示仓库为空
// 保证List集合中永远都是最多存储一个元素
public class ThreadTest16 {
    public static void main(String[] args) {
        // 创建1个仓库对象,这是共享的
        List list = new ArrayList();
        // 创建生产者线程
        Thread t1 = new Thread(new Producer(list));
        // 创建消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}

// 生产线程
class Producer implements Runnable{
    //仓库
    private List list;

    public Producer(List list){
        this.list = list;
    }

    @Override
    public void run() {
        // 一直生产
        while (true){
            // 给仓库对象list加锁
            synchronized (list){
                if (list.size() > 0){
                    // 当前线程进入等待状态,并且释放list集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序执行到此,说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "---->" + obj);
                // 唤醒消费者进行消费
                list.notify();
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable{
    //仓库
    private List list;

    public Consumer(List list){
        this.list = list;
    }

    @Override
    public void run() {
        // 一直消费
        while (true){
            // 给仓库对象list加锁
            synchronized (list){
                if (list.size() == 0){
                    // 当前线程进入等待状态,并且释放list集合的锁
                    try {
                        // 仓库空了,消费者线程等待,释放掉list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明仓库中有数据,进行消费
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "---->" + obj);
                // 唤醒生产者生产
                list.notify();
            }
        }
    }
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值