写点什么

两万字带你了解 Java 多线程(详细大总结)

作者:Java快了!
  • 2022 年 9 月 20 日
    湖南
  • 本文字数:12136 字

    阅读完需:约 40 分钟

目录

多线程概念

什么是进程?进程是一个应用程序(1 个进程是一个软件)。

什么是线程?线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

对于 java 程序来说,当在 DOS 命令窗口中输入:java HelloWorld 回车之后。会先启动 JVM,而 JVM 就是一个进程。 JVM 再启动一个主线程调用 main 方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的 java 程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行 main 方法的主线程。

进程可以看做是现实生活当中的公司,线程可以看做是公司当中的某个员工。注意:进程 A 和进程 B 的内存独立不共享。

线程 A 和线程 B 呢?在 java 语言中:线程 A 和线程 B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。

假设启动 10 个线程,会有 10 个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

什么是真正的多线程并发?t1 线程执行 t1 的。 t2 线程执行 t 的。t1 不会影响 t2,t2 也不会影响 t1。这叫做真正的多线程并发。

举例火车站,可以看做是一个进程。火车站中的每一个售票窗口可以看做是一个线程。我在窗口 1 购票,你可以在窗口 2 购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。java 中之所以有多线程机制,目的就是为了提高程序的处理效率。

实现线程的第一种方式

实现线程的第一种方式:编写一个类,直接继承 java.lang.Thread,重写 run 方法。怎么创建线程对象?new 就行了。怎么启动线程呢?调用线程对象的 start()方法。注意:亘古不变的道理:方法体当中的代码永远都是自上而下的顺序依次逐行执行的。


代码示例

public class 实现线程的第一种方式 {     public static void main(String[] args) {         // 这里是main方法,这里的代码属于主线程,在主栈中进行        // 新建一个分支对象        MyThresd myThresd = new MyThresd();
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成后,瞬间就结束了。 //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动了。 //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。 //run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。 myThresd.start();
//这里的代码还是运行在主线程中 for (int i = 0; i < 1000; i++) { System.out.println("主线程-->"+i); } }}class MyThresd extends Thread{ @Override public void run() { //编写程序,这段程序运行在分支路线(分支栈) for (int i = 0; i < 1000; i++) { System.out.println("分支线程-->"+i); } }}
复制代码

输出结果

实现线程的第二种方式

第二种方式以接口的方式:编写一个类,实现 Runnable 接口,里面实现 run 方法。new 一个 Thread 对象,里面传递的参数是 Runnable 对象,编写的那个类实现了 Runnable,所以直接 new 这个类的对象传进去。

代码示例

public class 实现线程的第二种方式 {     public static void main(String[] args) {         //创建一个可运行的对象        //MyRunnable r = new MyRunnable();        //将可运行的对象封装成一个线程对象        //Thread t = new Thread(r);
//上面两步合并到一起的写法 //Thread构造方法有一个是传递一个Runnable,MyRunnable实现了Runnable,所以传MyRunnable也ok Thread thread = new Thread(new MyRunnable());
//启动线程 thread.start();
for (int i = 0; i < 100; i++) { System.out.println("主线程-->"+i); } /*//采用匿名内部类的方式 Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("分支线程-->"+i); } } });*/ }}
// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("分支线程-->"+i); } }}
复制代码

输出结果

采用匿名内部类方式

public class 实现线程的第二种方式 {     public static void main(String[] args) {                 //采用匿名内部类的方式        Thread t = new Thread(new Runnable() {             @Override            public void run() {                 for (int i = 0; i < 100; i++) {                     System.out.println("分支线程-->"+i);                }            }        });
//启动线程 t.start();
for (int i = 0; i < 100; i++) { System.out.println("主线程-->"+i); } }}
复制代码


实现线程的第三种方式

实现线程的第三种方式:实现 Callable 接口。(JDK8 新特性。)这种方式实现的线程可以获取线程的返回值。之前讲解的那两种方式是无法获取线程返回值的,因为 run 方法返回 void 思考:系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?使用第三种方式:实现 callable 接口方式。

实现线程的第三种方式:实现 Callable 接口这种方式的优点:可以获取到线程的执行结果。这种方式的缺点:效率比较低,在获取 t 线程执行结果的时候,当前线程受阻塞,效率较低

public class 实现线程的第三种方式 {     public static void main(String[] args) throws ExecutionException, InterruptedException {         //第一步:创建一个“未来任务类”对象。        //参数非常重要,需要给一个Callable接口实现类对象。这里使用的是匿名内部类的方式。        FutureTask task = new FutureTask(new Callable() {             @Override            public Object call() throws Exception {  //call()方法相当于run方法,只不过这个有返回值                //线程执行一个任务,执行之后可能会有一个执行结果                //模拟执行            System.out.println("call method begin!");            Thread.sleep(1000*5);            System.out.println("call method end!");            int a = 100;            int b = 200;            return a+b; //自动装箱(300结果变成Integer)            }        });
//创建线程对象 Thread t = new Thread(task);
//启动线程 t.start();
//这里是main方法,这里是在主线程中。 //在主线程中怎么获取t线程的返回结果呢? //get()方法的执行会导致“当前线程阻塞” Object o = task.get(); System.out.println("线程执行结果是:"+o);
//main方法这里的程序想要执行必须要等get方法结束 //而get方法可能需要很久。因为get()方法是为了拿到另一个线程的执行结果 //另一个线程执行是需要时间的。 System.out.println("hello world!"); }}
复制代码

线程的生命周期

关于线程对象的生命周期

新建状态

就绪状态

运行状态

阻塞状态

这个要记住,面试可能会问。



线程对象的方法


代码演示

public class 线程对象的方法 {     public static void main(String[] args) {         //currentThread:获取当前线程对象        //thread就是当前线程对象        //这个代码出现在main方法中,所以当前线程就是主线程        Thread thread = Thread.currentThread();  // 静态方法        System.out.println(thread.getName());//main
//创建线程对象 MyThread2 t1 = new MyThread2(); //getName:获取线程名字 String a = t1.getName(); System.out.println(a);//Thread-0 默认的 //setName:修改线程名字 t1.setName("t1"); System.out.println(t1.getName()); //t1
MyThread2 t2 = new MyThread2(); t2.setName("t2");
t2.start(); t1.start(); }}class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { //thread就是当前线程对象。那么当前线程是谁呢? //当t1线程执行run方法,那么这个当前线程就是t1 //当t2线程执行run方法,那么这个当前线程就是t2。 类似于this 谁调用我 我就是谁 Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"-->"+i); } }}
复制代码

执行结果



Sleep 方法


举例

public class sleep方法 {     public static void main(String[] args) { 
//让当前线程进入休眠 失眠五秒 //这个代码出现在main方法中,当前线程就是主线程!! try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); }
//5秒之后执行这里的代码 System.out.println("hello world!");
for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"--->"+i); //休眠一秒 //这个方法还是出现在主线程中,所以主线程受阻 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
复制代码


Sleep 面试题

public class sleep方法面试题 {     public static void main(String[] args) {         //创建线程对象        Thread t = new MyThread3();        t.setName("t");        t.start();
//调用sleep方法 try { //问题:这行代码会让线程t进入休眠状态吗? t.sleep(1000*5); //在执行的时候还是会转成Thread.sleep(1000*5); //这行代码的作用是:让当前线程进入休眠。也就是说main'线程进入休眠 //这样代码出现在main方法,main线程睡眠 //5秒后就如就绪状态 抢到执行权 执行hello world } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello World!");
}
}class MyThread3 extends Thread{ @Override public void run() {
for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } }}
复制代码


终止线程的睡眠

sleep 睡眠太久了,如果希望半道上醒来,你应该怎么办呢?也就是说怎么叫醒一个正在睡眠的线程??注意:这个不是终断线程的执行,是终断线程的睡眠。

/*sleep睡眠太久 如果希望半道醒来,就要终止睡眠 */public class 终止线程的睡眠 {     public static void main(String[] args) {         Thread t = new Thread(new MyRunnable2());        t.setName("t");        t.start();
//希望五秒之后,t线程醒来(五秒之后主线程手里的话干完了,不用争执行权了) try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //终止t线程的睡眠( 这种终端睡眠的方法依靠java的异常处理机制) t.interrupt();//干扰 一盆冷水过去 }}class MyRunnable2 implements Runnable{
// 重点: run()当中的异常不能throws,只能try catch // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。 @Override public void run() { System.out.println(Thread.currentThread().getName()+"--->"+"big"); try { //睡眠一年 Thread.sleep(1000L *60*60*24*365); } catch (InterruptedException e) { //打印异常信息 e.printStackTrace(); } //一年之后才会执行到这里 System.out.println(Thread.currentThread().getName()+"--->"+"end"); }}
复制代码


终止一个线程的执行

public class 终止一个线程的执行 {     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();        }
//终止线程 //你想要什么时候终止t的执行,那么把标记修改为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就结束了 //结束前可以保存呀 //save...
//终止当前线程 return; } } }}
复制代码


线程调度(了解)

常见的线程调度模型有哪些?抢占式调度模型:那个线程的优先级比较高,抢到的 CPU 时间片的概率就高一些/多一些。 java 采用的就是抢占式调度模型。

均分式调度模型:平均分配 CPU 时间片。每个线程占有的 CPU 时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。

java 中提供了哪些方法是和线程调度有关系的呢

线程优先级

/*关于线程的优先级setPriority(): 设置优先级getPriority(): 获取当前线程对象优先级默认优先级是5最低是1最高是10 */public class 线程优先级 {     public static void main(String[] args) {         //main线程的默认优先级是:5        System.out.println(Thread.currentThread().getName()+"线程的默认优先级是:"+Thread.currentThread().getPriority());
//设置主线程优先级为1 Thread.currentThread().setPriority(1);
Thread t = new Thread(new MyRunnable5()); t.setPriority(10); t.setName("t"); t.start(); //优先级较高的,只是抢到的CPU时间片多一些。 //大概率方向更偏向于优先级比较高的。 for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } }}class MyRunnable5 implements Runnable{
@Override public void run() { for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } }}
复制代码


线程让位

/*让位,当前线程暂停,回到就绪状态,让给其它线程。静态方法:Thread.yield() */
public class 线程让位 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable6()); t.setName("t"); t.start(); for (int i = 1; i <= 10000; i++) { System.out.println(Thread.currentThread().getName()+"--->"+i); } }}class MyRunnable6 implements Runnable{
@Override public void run() { for (int i = 1; i <= 10000; i++) { if (i%100==0){ Thread.yield(); //当前线程暂停一下,让给主线程 } System.out.println(Thread.currentThread().getName()+"--->"+i); } }}
复制代码


线程合并

public class 线程合并 {     public static void main(String[] args) {         System.out.println("main begin");
Thread t = new Thread(new MyRunnable7()); t.setName("t"); t.start();
//线程合并 try { t.join(); //t合并到当前线程中,当前线程受阻塞,t线程执行直到结束,当前线程才执行。 } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("main over"); }}class MyRunnable7 implements Runnable{
@Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } }}
复制代码


线程安全(重点)

关于多线程并发环境下,数据的安全问题。为什么这个是重点?以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点)

什么时候数据在多线程并发的环境下会存在安全问题呢?三个条件:条件 1:多线程并发。条件 2:有共享数据。条件 3:共享数据有修改的行为。满足以上 3 个条件之后,就会存在线程安全问题。

怎么解决线程安全问题呢?当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?线程排队执行。(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制。专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

怎么解决线程安全问题?使用“线程同步机制"。线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

说到线程同步这块,涉及到这两个专业术语:异步编程模型:线程 t1 和线程 t2,各自执行各自的,t1 不管 t2,t2 不管 t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)异步就是并发。同步编程模型:线程 t1 和线程 t2,在线程 t 执行的时候,必须等待 t2 线程执行结束,或者说在 t2 线程执行的时候,必须等待 t1 线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。同步就是排队。

Java 中有三大变量?【重要的内容。】实例变量:在堆中。静态变量:在方法区。局部变量:在栈中。以上三大变量中:局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。实例变量在堆中,堆只有 1 个,静态变量在方法区中,方法区只有 1 个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。局部变量+常量:不会有线程安全问题。成员变量:可能会有线程安全问题。

如果使用局部变量的话:建议使用:StringBuilder.因为局部变量不存在线程安全问题。选择 stringBuildere stringBuffer 效率比较低。ArrayList 是非线程安全的。 Vector 是线程安全的。HashMap Hashset 是非线程安全的。 Hashtable 是线程安全的。

总结:synchronized有三种写法:第一种:同步代码块。 灵活synchronized(线程共享对象){ 	同步代码块;}第二种:在实例方法上使用synchronized表示共享对象一定是this并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized表示找类锁。类锁永远只有1把。就算创建了100个对象,那类锁也只有一把,所以这个类new出来的对象都要排队对象锁:1个对象1把锁,100个对象100把锁。类锁:100个对象,也可能只是1把类锁。
复制代码


聊一聊,我们以后开发中应该怎么解决线程安全问题?是一上来就选择线程同步吗?synchronized 不是,synchronized 会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。第一种方案:尽量使用局部变量代替"实例变量和静态变量"。第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应 1 个对象,100 个线程对应 100 个对象,对象不共享,就没有数据安全问题了。)第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择 synchronized 了。线程同步机制。

同步代码块 synchronized

线程同步机制的语法是:synchronized(){ 	// 线程同步代码块。}synchronized后面小括号中传的这个“数据”是相当关键的。这个数据必须是多线程共享的数据。才能达到多线程排队。()中写什么?那要看你想让哪些线程同步。假设t1、t2、t3、t4、t5,有5个线程,你只希望t1t2t3排队,t4t5不需要排队。怎么办?你一定要在()中写一个t1t2t3共享的对象。而这个对象对于t4t5来说不是共享的。
复制代码

在 java 语言中,任何一个对象都有"一把锁”,其实这把便就是标记。(只是把它叫做锁。100 个对象,100 把锁。1 个对象 1 把锁。以下代码的执行原理?1、假设 t1 和 t2 线程并发,开始执行代码的时候,肯定有一个先一个后。2、线设 t1 先执行了,遇到了 synchronized,这个时候自动找“后面共享对亲”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。3、假设 t1 已经占有这把锁,此时 t2 也遇到 synchronized 关键字,也会去占有后同共享对象的这把锁,结果这把锁被 t1 占有,t2 只能在同步代码块外面等待的结束,直到 t1 把同步代码快执行结束了,t1 会归还这把锁,此时 t2 终于等到这把锁,然后 t2 占有这把锁之后,进入同步代码块执行程序。这样就达到了线程排队执行。这里需要注意的是,这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的

实例方法上和静态方法上使用 synchronized

在实例方法上可以使用 synchronized 吗?可以的。synchronized 出现在实例方法上,一定锁的是 this 没得挑。只能是 this。不能是其他的对象了。所以这种方式不灵活。另外还有一个缺点:synchronized 出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用。

synchronized 使用在实例方法上有什么优点?代码写的少了。节俭了。如果共享的对象就是 this,并且需要同步的代码块是整个方法体建议使用这种方式。

synchronized 出现在静态方法上是找类锁,因为静态方法是类锁,不管创建几个对象类锁只有一把。



死锁代码要会写,一般面试官要求你会写,只有会写的,才会在以后的开发中注意这个事,因为死锁很难调试

实例

public class 死锁 {     public static void main(String[] args) {         Object a = new Object();        Object b = new Object();
//t1 和t2两个线程共享a,b Thread t1 = new aa1(a,b); Thread t2 = new aa2(a,b); t1.start(); t2.start(); }}class aa1 extends Thread{ Object o1; Object o2; public aa1(Object o1,Object o2){ this.o1 = o1; this.o2 = o2 ; } //t1执行时占o1锁,t2执行时占o2锁; // t1执行完o1,要执行o1里面的o2,但是o2已经被t2占了; // t2执行完o2,要执行o2里面的o1,但是o1已经被t1占了; //这就屎机了 //所以尽量少循环嵌套 public void run() { synchronized (o1){ //o1嵌套o2 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2){ } } }}class aa2 extends Thread{ Object o1; Object o2; public aa2(Object o1,Object o2){ this.o1 = o1; this.o2 = o2 ; } @Override public void run() { synchronized (o2){ //o2嵌套o1 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ } } }}
复制代码


守护线程

守护线程 java 语言中线程分为两大类:一类是:用户线程一类是:守护线程(后台线程)其中具有代表性的就是:垃圾回收线程(守护线程)。

守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。注意:主线程 main 方法是一个用户线程。

守护线程用在什么地方呢?每天 00:00 的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到 00:00 的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

实例

/*所有的用户线程结束,守护线程自动结束守护守护的意思是:用户线程在,你有必要守护。用户线程没了,你就没必要守护了。 */public class 线程守护 {     public static void main(String[] args) {         Thread t = new mm();
//启动线程前,将线程设置为守护线程 t.setDaemon(true);
t.start();
//主线程 :主线程是用户线程 for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() +"-->"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }
}}class mm extends Thread{ @Override public void run() { int i = 0 ; //即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动终止。 while (true){ System.out.println(Thread.currentThread().getName() +"-->"+(++i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
复制代码


定时器的作用:间隔特定的时间,执行特定的程序。每周要进行银行账户的总账操作,每天要进行数据的备份操作…。

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在 java 中其实可以采用多种方式实现:可以使用 sleep 方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较 low)在 java 的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际的开发中,目前使用较多的是 Spring 框架中提供的 SpringTask 框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

实例

/*使用定时器指定任务 */public class 定时器 {     public static void main(String[] args) throws Exception {         // 创建定时器对象        Timer timer = new Timer();
//Timer timer = new Timer(true); 守护线程方式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date parse = sdf.parse("2022-9-19 11:52:30"); //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次); timer.schedule(new LogTimerTask(),parse,1000*5); //也可以使用匿名内部类,直接new抽象类
}}class LogTimerTask extends TimerTask{ //子类继承抽象类,要重写抽象方法
@Override public void run() { //编写需要执行的任务 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String format = sdf.format(new Date()); System.out.println(format + ": 成功完成一次数据备份"); }}
复制代码

生产者和消费者模式

wait 和 notify 方法

关于 object 类中的 wait 和 notify 方法。(生产者和消费者模式!)第一:wait 和 notify 方法不是线程对象的方法,是 java 中任何一个 java 对象都有的方法,因为这两个方式是 Object 类中自带的。wait 方法和 notify 方法不是通过线程对象调用,不是这样的:t.wait(),也不是这样的:t.notify()…不对。第二:wait()方法作用?Objecto=newobject(); o.wait();表示:让正在 o 对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。o.wait();方法的调用,会让"当前线程(正在 o 对象上活动的线程)“进入等待状态。第三:notify()方法作用?Objecto-newbject(); o.notify();表示:唤醒正在 o 对象上等待的线程。还有一个 notifyAll 方法:这个方法是唤醒 o 对象上处于等待的所有线程。



使用 wait 和 notify 方法实现生产者和消费者模式

什么是“生产者和消费者模式”?生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡。这是一种特殊的业务需求,在这种特殊的情况下需要使用 wait 方法和 notify 方法。



用户头像

Java快了!

关注

还未添加个人签名 2022.09.03 加入

还未添加个人简介

评论

发布
暂无评论
两万字带你了解Java多线程(详细大总结)_Java快了!_InfoQ写作社区