死锁
什么是死锁
- 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,
若无外力作用,它们都将无法推进下去。 - 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系
代码示例
写一个死锁代码,只有会写,才会在以后的开发中注意这个事。
public class DeadLockTest01 {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
MyThread1 thread1 = new MyThread1(o1,o2);
MyThread2 thread2 = new MyThread2(o1,o2);
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread{
Object obj1;
Object obj2;
public void run(){
synchronized (obj1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
}
}
}
public MyThread1(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
}
class MyThread2 extends Thread{
Object obj1;
Object obj2;
public void run(){
synchronized (obj2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
}
}
}
public MyThread2(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
}
守护线程
-
java语言中线程分为两大类
- 用户线程:
- 主线程main方法就是一个用户线程
- 守护线程:
- 最具有代表性的:垃圾回收线程(守护线程)。
- 用户线程:
-
守护线程的特点:
- 一般守护线程是一个死循环,所有的用户线程结束了,守护线程才会自动结束。
-
守护线程用在什么地方?
每天00:00的时候系统数据自动备份。 这个需要用到定时器,并且我们可以将定时器设置为守护线程。一直在那守着, 每到00:00的时候就备份一次;所有的用户线程结束了,守护线程自动退出,没 必要继续数据备份了。
代码示例
public class DaemonThreadTest01 {
public static void main(String[] args) {
BakDataThread bakDataThread = new BakDataThread();
bakDataThread.setName("守护线程");
// 启动线程之前,将该线程设置为守护线程
bakDataThread.setDaemon(true);
bakDataThread.start();
for (int i = 0;i < 10; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class BakDataThread extends Thread{
public void run(){
int i= 0;
// 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i++);
}
}
}
定时器
-
定时器的作用:
- 间隔特定的时间,执行特定的程序。
- 在实际开发中,没隔多久执行一段特定的程序,这是常见的。
-
java中实现定时器有多种方式:
- 1,可以使用sleep方法,睡眠,设置睡眠时间;每到这个时间点醒来,执行任务;
这种方式是最原始的定时器。(比较low) - 2,使用java类库中写好的定时器,java.util.Timer,可以直接拿来用。这种方式
在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。 - 3,例如:现在常用的spring框架中提供的springTask框架,这个框架只有进行简单
的配置,就可以完成定时器任务。
- 1,可以使用sleep方法,睡眠,设置睡眠时间;每到这个时间点醒来,执行任务;
代码示例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerThreadTest01 {
public static void main(String[] args) {
// 创建定时器对象
Timer timer = new Timer();
// Timer timer = new Timer(true); //守护线程写法
//指定定时任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = null;
try {
firstTime = sdf.parse("2021-06-03 17:53:15");
} catch (ParseException e) {
e.printStackTrace();
}
// schedule(定时任务,开始执行时间,间隔多久执行一次);
timer.schedule(new LogTaskTimer(),firstTime,1000 * 10);
}
}
// 编写一个定时任务
class LogTaskTimer extends TimerTask{
@Override
public void run() {
// 这里编写需要执行的任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
System.out.println(time + "定时任务开始执行了");
}
}
实现线程的第三种方式:
-
实现Callable接口。(JDK8新特性)
- 在java.util.concurrent包下,属于java并发包,老版JDK没有。
- 优点:这种方式实现的线程可以获取线程执行的返回值。
- 之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回的是void。
- 缺点:效率比较低,在获取某个线程执行结果的时候,当前线程受阻塞,效率较低。
代码示例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest09 {
public static void main(String[] args) {
// 第一步:创建一个"未来任务类"对象
MyThreadCallable callable = new MyThreadCallable();
FutureTask task = new FutureTask(callable);
//创建线程对象
Thread t = new Thread(task);
t.setName("task");
t.start();// 启动线程
// 在main方法中 获取t线程的返回结果
// task.get()方法的执行会阻塞当前线程
try {
Object obj = task.get();
System.out.println(Thread.currentThread().getName() + "线程返回值是" + obj);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// task.get()方法的执行会阻塞main方法下面程序的执行
// main方法下面的程序想要执行,必须等待get()方法执行结束,
// get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果。
System.out.println("hello world");
}
}
class MyThreadCallable implements 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 ;
}
}
关于Object类中的wait和notify方法(生产者和消费者模式!)
-
什么是"生产者和消费者模式"?
- 生产线程负责生产,消费线程负责消费。
- 生产线程和消费线程要达到均衡。
- 这是一种特殊的业务需要,在这种特殊的情况下需要使用wait方法和notify方法。
wait方法和notify方法不是线程对象的方法,是java对象都有的方法,是Object类自带的。
wait方法和notify方法是建立在synchronized线程同步的基础之上,因为多线程要同时操作一个厂库,有线程安全问题。
-
wait方法的作用:
Object o = new Object(); o.wait();
- 表示让正在o对象上活动的线程进入等待状态,并且释放掉线程之前占用o对象的锁;直到被唤醒为止。
-
notify方法的作用:
- notify方法的调用可以让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
- notifyAll方法:唤醒o对象上处于等待的所有线程。
代码示例
模拟需求:
仓库采用list集合,list集合中假设只能存储一个元素;一个元素表示仓库满了,
如果list集合中元素个数是0,就表示仓库空了;保证list集合永远都是最多存储一个元素。
做到:生产一个消费一个。
import java.util.ArrayList;
import java.util.List;
public class ThreadTest10 {
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) {
this.list = list;
}
@Override
public void run() { //一直生产
while (true){
// 给仓库对象list加锁
synchronized (list){
if (list.size() > 0){// 仓库已满
try {
list.wait(); // 当前线程进入等待状态,并释放Producer之前占有list集合的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序执行到这里说明仓库是空的,可以生产
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "-->" + obj);
// 唤醒消费者进行消费
list.notify();
// list.notifyAll();
}
}
}
}
// 消费线程
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) {// 仓库空了
try {
list.wait(); // 消费者线程等待,释放掉list集合的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序执行到此处说明仓库中有数据,进行消费。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "-->" + obj);
//唤醒生产者生产
list.notify();
// list.notifyAll();
}
}
}
}