守护线程
- java语言中分为两大类,一类是用户线程,一类是守护线程(后台线程),其中最具代表的就是垃圾回收线程。
- 特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
- 注意:主线程main是一个用户线程
- 守护线程用在什么地方?
- 每天零点的时候系统数据自动备份,需要用到定时器,并且我们可以将定时器设置成守护线程,一直在那看着,每到零点的时候自动备份一次,所有的用户线程结束的时候,守护线程自动退出,没有必要进行数据备份了。
示例:setDaemon()
package threadSafe;
/**
* 守护线程
*
*/
public class ThreadTest01 {
public static void main(String[] args) {
Thread t1 = new BakDataThread();
t1.setName("备份数据线程");
//启动线程之前,将线程设置为守护线程
t1.setDaemon(true);
t1.start();
// 主线程:主线程是用户线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//数据备份
class BakDataThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
int i = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
定时器
- 作用:间隔特定的时间,执行特定程序
- 例如:每周要进行银行账户的总账操作,每天要进行数据的备份操作。
- 在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,可以实现定时的方法有:
- 可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器
- 利用java中已经写好的定时器:java.util.Timer,可以直接用。不过这种方式在实际开发中也很少用,因为很多高级的框架都支持定时服务。
- 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
实现定时器:
package threadSafe;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.crypto.Data;
/**
* 实现定时器
*
*/
public class ImplementTimer {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
// 守护线程的方式
// Timer timer = new Timer(true);
// 指定定时任务
// timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = simpleDateFormat.parse("2022-03-27 20:36:00");
// 十秒完成一次数据备份
timer.schedule(new LogTimertask(), firstTime, 1000 * 10);
}
}
//编写一个定时任务类,假设这是一个记录日志的定时任务类
class LogTimertask extends TimerTask {
@Override
public void run() {
// TODO Auto-generated method stub
// 编写你需要执行的任务就行了
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = simpleDateFormat.format(new Date());
System.out.println(strTime + " : 成功完成一次数据备份");
}
}
实现线程的第三种方式:实现Callable接口( JDK8新特性。)
- 这种方式实现的线程可以获取线程的返回值。之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void.
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
//JUC包下的,属于java的并发包,
import java.util.concurrent.FutureTask;
/**
* 实现线程的第三种方式,实现Callable接口
* 优点:可以获取到线程的执行结果
* 缺点:在获取thread线程执行结果时,当前线程受到阻塞,效率较低
*
*/
public class CreateThread {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建一个“未来任务类”对象,,参数非常重要,需要给一个Callable接口的实现类对象
@SuppressWarnings("unchecked")
FutureTask task = new FutureTask(new Callable() {
@Override
// call方法就相当于run方法,只不过这个有返回值
public Object call() throws Exception {
// TODO Auto-generated method stub
// 模拟执行
System.out.println("call mothod begin!");
Thread.sleep(1000 * 5);
System.out.println("call mothod end!");
int a = 100;
int b = 200;
return a + b;
}
});
// 创建线程对象
Thread thread = new Thread(task);
thread.start();
// 这里是main方法,这是在主线程中,在主线程中,怎么获取thread的返回执行结果
Object object = task.get();
System.out.println("线程执行结果:" + object);
// 这个get方法的执行会不会导致主线程阻塞?
// 会,main方法执行需要等待get方法执行结束,而get方法为了另一个线程执行
System.out.println("主线程执行结束!");
}
}
关于Object类中的wait方法和notify方法(生产者消费者模式)
- 第一: wai t和notifty方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是object类中自带的。wait方法和notify方法不是通过线程对象调用。
- wait()方法作用
Objeat o = new Objeot() ; o. wait() ;
表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。o.wait() ;方法的调用,会让"当前线程(正在o对象上活动的线程) "进入等待状态。
- notify()方法作用
Objeat o = new Objeot() ; o. notify() ;
- 表示:唤醒正在o对象上等待的线程。
- notifyAll()方法:唤醒o对象上处于等待的所有进程。
- 生产者消费者模式
实现生产者消费者模式
package threadSafe;
import java.util.ArrayList;
import java.util.List;
/*
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、模拟这样一个需求:
仓库为list集合,list集合中只能存储一个元素,1个元素即为满,0个元素即为空
保证list元素永远最多存储一个元素,生产一个就必须消费一个
*/
public class ThreadTest02 {
public static void main(String[] args) {
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) {
super();
this.list = list;
}
@Override
// 一直生产,死循环模拟一直生产
public void run() {
// TODO Auto-generated method stub
while (true) {
// 给仓库list加锁
synchronized (list) {
if (list.size() > 0) {
// 当前线程进入等待状态,并且释放list集合的锁
try {
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
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) {
super();
this.list = list;
}
@Override
// 一直消费
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (list) {
if(list.size()== 0) {
try {
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//程序能够执行到这里说明可以消费
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName()+"--->"+obj);
//唤醒生产者进行生产
list.notify();
}
}
}
}