irpas技术客

深入理解多线程(第三篇)_@每天都要敲代码

未知 7862

?作者简介:大家好我是@每天都要敲代码,一位材料转码农的选手,希望一起努力,一起进步! 📃个人主页:@每天都要敲代码的个人主页

🔥系列专栏:JavaSE从入门到精通 💬推荐一款模拟面试、刷题神器,从基础到大厂面试题👉点击跳转刷题网站进行注册学习

目录

线程内容补充

1. 守护线程

2. 定时器

3. 实现线程的第三种方式:实现Callable接口

4. wait和notify(生产者和消费者模式)

结束语


线程内容补充

1、守护线程 2、定时器 3、实现线程的第三种方式:FutureTask方式,实现Callable接口。(JDK8新特性) 4、关于Object类中的wait和notify方法。(生产者和消费者模式)

1. 守护线程

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

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

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

模拟守护线程

主线程用户线程结束;守护线程也会自动终止,即使死循环也不行!

例如:下面主线程main运行完,备份数据的线程也会自动终止

package com.bjpowernode.java.thread; // 守护线程 public class ThreadTest014 { public static void main(String[] args) { Thread t = new BackThread(); t.setName("备份数据的线程"); // 启动线程之前,将线程设置为守护线程 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 BackThread extends Thread{ public void run() { int i = 0; // 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。 while(true){ System.out.println(Thread.currentThread().getName()+"--->"+ (++i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } 2. 定时器

(1)定时器的作用:?间隔特定的时间,执行特定的程序。

????????例如:每周要进行银行账户的总账操作。

???????????????????每天要进行数据的备份操作。

(2)在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:? ? ? ? ? ? ? ?? ? ? ? ? 1)可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。

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

? ? ? ? 3)在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

(3)继承TimerTask类,创建Timer对象,调用schedule方法

????????timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);

package com.bjpowernode.java.thread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; // 使用定时器指定定时对象 public class ThreadTest15 { public static void main(String[] args) throws Exception { // 创建定时器对象 Timer timer = new Timer(); // Timer timer = new Timer(true); // 守护进程的方式 // 指定定时任务 // timer.schedule(定时任务,第一次执行时间,间隔多久执行一次); // 获取第一次执行时间 SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss"); Date firstTime = sdf.parse("2022-7-31 22:19:00"); timer.schedule(new logTimerTask(),firstTime,10*1000); // 间隔10秒执行一次 } } // 定时任务类,继承TimerTask // 假设这是一个记录日志的定时任务 class logTimerTask extends TimerTask{ // TimerTask是一个抽象类 public void run() { // 编写执行的定时任务 SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss"); String strTime = sdf.format(new Date()); System.out.println(strTime+":完成了一次数据备份"); } } 3. 实现线程的第三种方式:实现Callable接口

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

(2)实现线程的第三种方式:实现Callable接口。(JDK8新特性) ?? ??? ?这种方式实现的线程可以获取线程的返回值;之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。

?? ??? ?思考:系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?使用第三种方式:实现Callable接口

(3)这种方式执行效率较低,在获取返回值时有可能造成当前线程阻塞!

import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; // 实现线程的第三种方式,实现callable接口 public class ThreadTest08 { public static void main(String[] args) throws Exception{ //第一步:创建一个“未来任务类” // 参数既可以传Runnable(没有返回值),也可以传Callable(有返回值) FutureTask task = new FutureTask(new MyCallable()); // Callable接口实现类对象 // 创建线程对象 Thread t = new Thread(task); // 启动线程 t.start(); // main方法是在主线程中,在主线程中,怎么获取t线程的返回值 // get()方法的执行会导致“当前线程阻塞”,要等拿到call()方法return的结果 Object obj = task.get(); System.out.println("线程执行结果:"+obj); // main方法这里的程序要想执行必须等待get()方法的结束 // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果 // 另一个线程执行是需要时间的。 System.out.println("Hello World"); } } class MyCallable implements Callable{ // 重写call()方法,ctrl+O; // call()方法就相当于run()方法,只不过run()方法有返回值 public Object call() throws Exception { System.out.println("call method begin"); Thread.sleep(1000*10); System.out.println("call method end"); int a = 10; int b = 20; return a+b; //自动装箱(返回的是Object类型,300结果会变成Integer) } } /*执行结果: call method begin call method end 线程执行结果:30 Hello World */

采用匿名内部类的方式?

package com.bjpowernode.java.thread; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; // 采用匿名内部类的方式 public class ThreadTest16 { public static void main(String[] args) throws Exception, InterruptedException { // 匿名内部类的方式 FutureTask task = new FutureTask(new Callable() { 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; //自动装箱(300结果变成Integer) } }); // 创建线程对象 Thread t = new Thread(task); // 启动线程 t.start(); Object obj = task.get(); System.out.println(obj); System.out.println("Hello World!"); } } 4. wait和notify(生产者和消费者模式)

(1)关于Object类中的wait和notify方法(生产者和消费者模式)

T线程在o对象上活动,T线程是当前线程对象;当调用o.wait()方法后,当前线程进入等待状态。o.notify()方法的调用可以让正在o对象上等待的线程唤醒。例如:我走在大街上(对象o),有人要打我(线程T);我说“等会别打脸“(相当于调用了o.wait());“继续吧”(相当于调用了o.notify())

(2)wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。wait方法和notify方法不是通过线程对象调用!

(3)wait()方法作用? ? ??

Object o = new Object(); o.wait();

表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。

(4)notify()方法作用? ? ? ??

Object o = new Object(); o.notify();

?表示:唤醒正在o对象上等待的线程。还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程。

生产者和消费者模式

生产者和消费者模式是为了专门解决某个特定需求的;

一个线程负责生产,一个线程负责消费;最终要达到生产和消费平衡?

(1)使用wait方法和notify方法实现“生产者和消费者模式” (2)什么是“生产者和消费者模式”? ? ? ?生产线程负责生产,消费线程负责消费。 ? ? ?生产线程和消费线程要达到均衡。 ? ? ?这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。 (3)wait和notify方法不是线程对象的方法,是普通java对象都有的方法 (4)wait方法和notify方法建立在线程同步的基础之上;因为多线程要同时操作一个仓库。有线程安全问题。 (5)wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。 (6)notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。 (7)模拟这样一个需求: ? ? 1)仓库我们采用List集合 ? ? 2)List集合中假设只能存储1个元素 ? ? 3)1个元素就表示仓库满了 ? ? 4)如果List集合中元素个数是0,就表示仓库空了 ? ? 5)保证List集合中永远都是最多存储1个元素

? ? 6)必须做到这种效果:生产1个消费1个

package com.bjpowernode.java.thread; import java.util.ArrayList; import java.util.List; public class ThreadTest17 { public static void main(String[] args) { // 创建1个仓库对象,共享的 List list = new ArrayList(); // 创建2个线程对象 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; } public void run() { // 一直生产(使用死循环) while(true){ // 给仓库对象List加锁 synchronized (list){ if(list.size()>0){ // 大于0,说明仓库中已经有一个元素了 try { // 当前线程进入等待状态,并且释放Producer之前占有的List集合的锁 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; } public void run() { // 一直消费 while(true){ synchronized (list){ if(list.size() == 0){ try { // 仓库已经空了 // 消费者线程等待,释放掉List集合的锁 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 说明仓库中有数据,消费者进行消费 Object obj = list.remove(0); System.out.println(Thread.currentThread().getName()+"--->"+obj); // 唤醒生产者生产 list.notify(); } } } } 结束语

今天的分享就到这里啦!快快通过下方链接注册加入刷题大军吧!各种大厂面试真题在等你哦! 💬刷题神器,从基础到大厂面试题👉点击跳转刷题网站进行注册学习

?


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #深入理解多线程第三篇