JAVA笔记(20)--- 死锁;如何解决线程安全问题;守护线程;定时器;Callable 实现线程;wait ( ) 和 notify ( ) ;实现生产者和消费者模式;

Posted 猿头猿脑的王狗蛋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA笔记(20)--- 死锁;如何解决线程安全问题;守护线程;定时器;Callable 实现线程;wait ( ) 和 notify ( ) ;实现生产者和消费者模式;相关的知识,希望对你有一定的参考价值。

死锁:


1.特点:由于死锁不会出现异常,也不会出现错误,所以程序会一直僵持在这里,因此这种错误很难调试;

2.死锁模型:

public class ThreadPra1 {
    public static void main(String[] args) {
        //创建object1,object2对象
        Object object1=new Object();
        Object object2=new Object();
        //让thread1线程和thread2线程,共享object1,object2对象
        Thread thread1=new MyThread1(object1,object2);
        Thread thread2=new MyThread2(object1,object2);
        //依次启动thread1线程和thread2线程,“死锁现象”发生
        thread1.start();
        thread2.start();
    }
}
class MyThread1 extends Thread{
    Object object1;
    Object object2;
    public MyThread1(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }
    public void run() {
        //此线程先拿object1的对象锁,再拿object2的对象锁
        synchronized (object1){
            //让thread1线程拿到object1锁之后,先睡1秒,确保thread2线程能拿到object2锁
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object2){

            }
        }
    }
}
class MyThread2 extends Thread{
    Object object1;
    Object object2;
    public MyThread2(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }
    public void run() {
        //此线程先拿object2的对象锁,再拿object1的对象锁
        synchronized (object2){
            synchronized (object1){

            }
        }
    }
}

 

如何解决线程安全问题:、


第一种方案:尽量使用局部变量代替 " 实例变量和静态变量 " ;

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择 ” synchronized 线程同步机制 “ 了;

synchronized 会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制;

 

守护线程:


1.特点:死循环,守护线程一直执行,直到所有的用户线程结束,守护线程自动结束(例如垃圾回收线程);

2.实现守护线程: 调用方法 ---   线程 . setDaemon ( true ) ;  

public class ThreadPra1 {
    public static void main(String[] args) {
        BakDataThread bakDataThread=new BakDataThread();
        bakDataThread.setName("备份数据线程");
        //将bakDataThread线程设置为守护线程
        bakDataThread.setDaemon(true);
        bakDataThread.start();
        for (int i=0;i<10;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("正在下载文件...");
        }
    }
}
class BakDataThread extends Thread{
    public void run() {
        int i=1;
        //死循环
        while (true){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行"+(i++)+"次");
        }
    }
}
运行实例
正在下载文件...
正在下载文件...
正在下载文件...
正在下载文件...
备份数据线程执行1次
正在下载文件...
正在下载文件...
正在下载文件...
正在下载文件...
正在下载文件...
备份数据线程执行2次
正在下载文件...

Process finished with exit code 0

 

定时器:


1.作用:间隔特定的时间,执行特定的程序;例如:每周要进行银行账户的总账操作;每天要进行数据的备份操作;

2.实现方式:

1)使用sleep方法,设置睡眠时间,醒来自动继续执行任务,这种方式是最原始的定时器(现在很少使用);

2)java. util . Timer //专门的一个定时器类

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ThreadPra1 {
    public static void main(String[] args) throws ParseException {
        //创建Timer对象
        Timer timer=new Timer();
        //指定时间格式
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
        //获取此时刻时间
        Date date=sdf.parse(sdf.format(new Date()));
        //执行定时器任务,开始时间为现在,间隔时间为5秒
        timer.schedule(new LogTimerTask(),date,5000);
        //执行定时器任务,开始时间为现在,间隔时间为1秒
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("正在导入数据...");
            }
        }, date, 1000);
    }
}
//自定义的定时器任务类,继承 TimerTask,并重写run方法
class LogTimerTask extends TimerTask{
    public void run() {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
        System.out.println(sdf.format(new Date())+":成功备份数据");
    }
}
运行实例
2021-12-04 00-35-54:成功备份数据
正在导入数据...
正在导入数据...
正在导入数据...
正在导入数据...
正在导入数据...
2021-12-04 00-35-59:成功备份数据
正在导入数据...
正在导入数据...
正在导入数据...
正在导入数据...
正在导入数据...
2021-12-04 00-36-04:成功备份数据
正在导入数据...
正在导入数据...
正在导入数据...
正在导入数据...
正在导入数据...
2021-12-04 00-36-09:成功备份数据
......

#总结 Timer 的使用:

第一,自定义定时器任务类(继承TimerTask),重写 run 方法;

第二,创建Timer对象;

第三,调用  Timer. schedule ( 定时器任务对象 , Date对象,间隔多长毫秒执行一次 )  // Date 对象对应时间为,定时器任务第一次执行的时间

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

 

实现线程的第三种方式:


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

2.Callable 的使用: 

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadPra1 {
    public static void main(String[] args) throws Exception{
        //创建FutureTask对象,同时利用匿名内部类实现 Callable接口
        FutureTask task=new FutureTask(new Callable() {
            //call方法就相当于run方法,只不过call方法有返回值
            public Object call() throws Exception {
                int a=100;
                int b=100;
                //模拟分支线程执行三秒后,结束run方法
                Thread.sleep(3000);
                //自动装箱机制
                return a+b;
            }
        });
        Thread thread=new Thread(task);
        thread.start();
        System.out.println("分支线程执行结果:"+task.get());
        System.out.println("分支线程真墨迹,主线程表示我终于能从阻塞状态中解放了!");
    }
}
运行实例
分支线程执行结果:200
分支线程真墨迹,主线程表示我终于能从阻塞状态解放了!

Process finished with exit code 0

#总结 Callable 的使用:

第一,创建 FutureTask 对象,同时往构造方法里传匿名内部类(实现了Callable 接口,重写call方法)

第二,创建 Thread 对象,同时往构造方法里传上面创建的 FutureTask 对象;

第三,启动 Thread 对象线程;

3.这种实现线程方式的优缺点:

优点:可以获取线程的执行结果;

缺点:效率较低,在获取线程执行结果的时候,当前线程受阻塞,导致效率降低;

 

Object 类中的 wait ( ) 和 notify ( ) :


1.wait 方法和 notify 方法的调用:

Object 对象 . wait ( ) ; // 使正在Object 对象上活动的当前线程进入无期限等待,直到被唤醒为止;

Object 对象 . notify( ) ; // 唤醒一个正在 Object 对象上等待的线程;

Object 对象 . notifyAll ( ) // 唤醒 Object 对象上处于等待的所有线程;

2.通过 wait ( ) 和 notify ( ) 实现 ” 生产者和消费者模式 “:

import java.util.ArrayList;
import java.util.List;
public class ThreadPra1 {
    public static void main(String[] args) throws Exception{
        List list=new ArrayList();
        Thread thread1=new Thread(new Producer(list));
        Thread thread2=new Thread(new Consumer(list));
        thread1.setName("生产者线程");
        thread2.setName("消费者线程");
        thread1.start();
        thread2.start();
    }
}
class Producer implements Runnable{
    private List list;
    public Producer(List list) {
        this.list = list;
    }
    public void run() {
        //锁list对象,使list对象成为生产者和消费者的共有资源
        synchronized (list){
            //死循环
            while (true){
                //仓库满仓时,使生产者线程陷入等待
                if (list.size()>0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //储备仓库
                list.add(new Object());
                System.out.println(Thread.currentThread().getName()+"--->"+list.get(0));
                //唤醒消费者线程进行消费
                list.notify();
            }
        }
    }
}
class Consumer implements Runnable{
    private List list;
    public Consumer(List list) {
        this.list = list;
    }
    public void run() {
        //list对象为共有资源
        synchronized (list){
            //死循环
            while (true){
                //仓库空仓时,使消费者线程陷入等待
                if (list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"--->"+list.get(0));
                //清空仓库
                list.remove(0);
                //唤醒生产者线程进行生产
                list.notify();
                //list.notifyAll();把生产者和消费者都唤醒也行,因为消费者即使被唤醒,也会被if条件句拦截而再次陷入等待
            }
        }
    }
}
运行实例
生产者线程--->java.lang.Object@7941b718
消费者线程--->java.lang.Object@7941b718
生产者线程--->java.lang.Object@77865640
消费者线程--->java.lang.Object@77865640
生产者线程--->java.lang.Object@73b9dc68
消费者线程--->java.lang.Object@73b9dc68
生产者线程--->java.lang.Object@36bb5c7b
消费者线程--->java.lang.Object@36bb5c7b
生产者线程--->java.lang.Object@42b6d4c3
消费者线程--->java.lang.Object@42b6d4c3
......

重点:

Object . wait 方法会让正在 Object 对象上活动的当前线程进入等待状态,并且释放之前占有的 Object 对象的锁;

Object . notify 方法只会通知,不会释放之前占有的o对象的锁,并继续执行线程,直到 run 方法结束;

wait方法和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,存在线程安全问题;


ps:由于博主目前只是一只猿宝宝,所以有些地方可能说的有些片面,若前辈们能够指点一二就更好了      (~ ̄(OO) ̄)ブ

 

 

以上是关于JAVA笔记(20)--- 死锁;如何解决线程安全问题;守护线程;定时器;Callable 实现线程;wait ( ) 和 notify ( ) ;实现生产者和消费者模式;的主要内容,如果未能解决你的问题,请参考以下文章

怎么处理JAVA多线程死锁问题?

多线程(五):解决线程不安全方案

多线程(五):解决线程不安全方案

如何解决多线程造成的数据库死锁

Java中各种死锁详细讲述及其解决方案(图文并茂,浅显易懂)

Java中各种死锁详细讲述及其解决方案(图文并茂,浅显易懂)