1. 守护线程
- java语言中线程分为两大类:用户线程和守护线程(后台线程,最具代表性的是垃圾回收线程)
- 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
- 主线程main是一个用户线程
- 守护线程用在什么地方????
比如规定每天0点时,系统数据会自动备份,这时需要定时器,可以将定时器设为守护线程,一直守着,到0点准时备份一次
package 多线程;
/*用户线程只要结束,守护线程自动结束,即使守护线程是死循环*/
public class T3 {
public static void main(String[] args) {
Thread thread = new MyThead1();
thread.setName("备份数据的线程");
//设为守护线程
thread.setDaemon(true);
thread.start();
// 主线程 :是用户线程
for (int i = 1; i < 6; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//线程1
class MyThead1 extends Thread {
int i = 0;
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*main-->1
备份数据的线程--->1
main-->2
备份数据的线程--->2
main-->3
备份数据的线程--->3
main-->4
备份数据的线程--->4
main-->5
备份数据的线程--->5
备份数据的线程--->6*/
2. 定时器
- 就是间隔特定的时间执行特定的程序
- 可以使用:
(1)使用sleep方法,设置睡眠时间
(2)在java的类库中,可以直接使用 java.util.Timer ,不过在实际开发中也很少用,许多高级框架都是支持定时任务的;
在实际开发中,使用较多的是Spring 框架中提供的SpringTask 框架,经过简单配置,就可以实现定时任务。(其底层就是java.util.Timer)
- 了解 java.util.Timer的原理
package 多线程;
/*
* 1. Timer()构造方法,创建一个计时器
* 2. public void schedule(TimerTask task, Date firstTime, long period)
* 此方法实现: 安排指定的任务在指定的时间开始进行重复的固定延迟执行
* 3个参数: firstTime就是任务第一次执行的时间 period延迟多久执行一次,单位毫秒
* public abstract class TimerTask implements Runnable {
* TimerTask实现Runnable接口,就是一个线程
* 注意:TimerTask是一个抽象类,要实现此类,得到任务对象
*3. 也可以将定时器设为守护线程: 就是在构造方法 Timer(true) */
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class T3 {
public static void main(String[] args) throws ParseException {
//创建定时器对象
Timer timer = new Timer();
//指定定时任务
//第一次执行的时间
SimpleDateFormat date = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
Date firstTime = date.parse("2022-04-15 16:15:26");
//定时任务对象,因为是一个抽象类,不可以直接new 对象,单独编写一个定时任务类
timer.schedule(new LogTimer(),firstTime,3000);
}
}
//定时任务类
class LogTimer extends TimerTask{
@Override
public void run() {
//在此编写需要执行的任务
SimpleDateFormat date = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
String string = date.format(new Date());//得到当前时间
System.out.println("当前时间"+string+"完成一次数据备份");
}
}
3. 实现线程的第三种方式:实现Callable接口(JDK8新特性)
这种方式实现的线程可以获取线程的返回值,之前的两种方式:
(1)继承Thread类,实现run方法
(2)实现Runnable接口 ,实现run方法,在将其new 对象放到Thread的构造方法中
中的实现run方法的返回值都是void
package 多线程;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class T3 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 第一步:创建一个未来任务类对象
// 需要一个Callable参数,可以使用匿名内部类实现Callable接口
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {// call方法就相当于run方法,只不过有返回值
System.out.println("call方法开始执行---");
Thread.sleep(3000);
System.out.println("call方法结束执行---");
return 1 + 2;
}
});
// 第二步:创建线程对象
Thread thread = new Thread(task);
// 启动线程
thread.start();
// 第三步:在主线程main中如何获取thread线程的返回值??
Object object = task.get();
// main方法这里的程序要想执行,必须等待get()方法的结束,而此方法是需要另一个线程的执行,可能很久,导致阻塞当前线程(主线程)
System.out.println("main线程end---");
}
}
优点:获取线程的返回值
缺点:效率比较低,在获取某个线程的执行结果时,当前线程受阻塞。
4. 关于Object类中的wait 和 notify 方法
-
wait 和 notify 方法不是线程对象的方法,而是java中任何一个java对象都有的方法,它们是Object类中自带的。
-
wait 方法作用:
Object o = new Object( );
o.wait( );
表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
-
notify 方法作用:
Object o = new Object( );
o. notify( );
表示:让正在o对象上等待的线程被唤醒,进入活动状态 。
而 notifyAll( ) 是唤醒o对象上所有处于等待的线程。
- 生产者和消费者模式:
针对特定需求:
一个线程负责生产,一个线程负责消费,而且达到生产和消费均衡
类比于一个仓库生产和消费产品:
因为仓库是多线程共享的,所以要考虑仓库的线程安全问题; 仓库对象最终调用wait 和 notify 方法 ,而wait 和 notify 方法 建立在synchronized 线程同步的基础上。
o.wait () 方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁。
o. notify( )方法只会通知,不会释放之前占有的o对象的锁。
package 多线程;
//1. 使用Object类中的wait 和 notify 方法实现“生产者和消费者模式”
//2. “生产者和消费者模式”是指一个线程负责生产,一个线程负责消费,而且达到生产和消费均衡
//3. wait 和 notify 方法 建立在synchronized 线程同步的基础上。
/*模拟:
* 仓库使用List集合,并且只能存储一个元素
* 1个元素代表仓库满了 0个就空
* 达到:生产一个消费一个:假设生产线程执行,则消费线程等待
* */
import java.util.ArrayList;
import java.util.List;
public class T3 {
public static void main(String[] args) {
// 创建仓库对象,是共享的
List list = new ArrayList();
// 创建生产线程对象
Thread t1 = new Thread(new Producer(list));
t1.setName("生产者线程");
t1.start();
// 创建消费线程对象
Thread t2 = new Thread(new Consumer(list));
t2.setName("消费者线程");
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) {
// 仓库已经满了,当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。若不释放,另一个线程就无法对仓库的修改数据
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序到此,表示仓库是空的,可以生产
Object object = new Object();
list.add(object);
System.out.println(Thread.currentThread().getName() + "生产" + object);
// 唤醒消费者进行消费
list.notify();
}
//当代码执行到此处,也会释放Producer之前占有的list集合的锁
}
}
}
//消费线程
class Consumer implements Runnable {
// 保证生产和消费线程共享一个仓库,通过构造方法的方式传过来
private List list;// 仓库
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直消费(使用死循环来模拟一直消费)
while (true) {
synchronized (list) {
if (list.size() == 0) {
// 仓库已经空了,当前线程进入等待状态,并且释放Consumer之前占有的list集合的锁。
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序到此,表示仓库是满的,可以消费
Object object = list.remove(0);
System.out.println(Thread.currentThread().getName() + "消费" + object);
// 唤醒生产者进行生产
list.notify();
}
}
}
}
/*消费者线程消费java.lang.Object@2a1ff8be
生产者线程生产java.lang.Object@4318d75f
消费者线程消费java.lang.Object@4318d75f
生产者线程生产java.lang.Object@2d25a011
消费者线程消费java.lang.Object@2d25a011
生产者线程生产java.lang.Object@72f744c6
消费者线程消费java.lang.Object@72f744c6
.....*/