美文网首页
JavaSE进阶十 线程二

JavaSE进阶十 线程二

作者: SimonLike | 来源:发表于2021-06-17 10:34 被阅读0次

    死锁

    什么是死锁

    • 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,
      若无外力作用,它们都将无法推进下去。
    • 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。

    产生死锁的四个必要条件:

    • 互斥条件:一个资源每次只能被一个线程使用
    • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
    • 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系
    代码示例

    写一个死锁代码,只有会写,才会在以后的开发中注意这个事。

    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框架,这个框架只有进行简单
        的配置,就可以完成定时器任务。
    代码示例
    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();
                }
            }
        }
    }
    

    上篇:JavaSE进阶十 线程一
    下篇:JavaSE进阶十一 反射机制一

    相关文章

      网友评论

          本文标题:JavaSE进阶十 线程二

          本文链接:https://www.haomeiwen.com/subject/jiexyltx.html