【java进阶11:多线程】实现多线程的方式、线程的生命周期、线程调度、多线程并发中数据的安全问题、死锁、守护线程、定时器、wait和notify方法、生产者和消费者模式

目录

多线程

线程调度(了解)

多线程并发环境下,数据的安全问题


多线程

  1. 什么是进程?什么是线程?

    进程是一个应用程序(可以理解为是一个软件)。

    线程是一个进程中的执行场景/执行单元。

    一个进程可以启动多个线程。

  2. 对于java程序来说,当在DOS命令窗口中输入:java HelloWorld 回车之后

    先启动JVM,而JVM就是一个进程。JVM先启动一个主线程调用main方法;

    同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,

    一个是执行main方法的主线程,一个是垃圾回收线程。

  3. 进程和线程的关系

    • 阿里巴巴:进程

      • 马云:阿里巴巴的一个线程
      • 童文红:阿里巴巴的一个线程
    • 京东:进程

      • 刘强东:京东的一个线程
      • 妹妹:京东的一个线程

      进程可以看作:现实生活中的公司、线程可以看作:公司中的某个员工

      d

      注意:

      • 进程A和进程B的内存独立不共享(阿里巴巴和京东资源不会共享的)

        魔兽游戏是一个进程、酷狗音乐是一个进程。这两个进程都是独立的,不共享资源。

      • 线程A和线程B呢?在java语言中:

        线程A线程B:堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。

        假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。

      • 火车站:可以看作是一个进程。

        火车站中的每一个售票窗口可以看作是一个线程。我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。

      • java中之所以有多线程机制,目的就是为了提高程序的处理效率

  4. 思考一个问题:

    使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。main方法结束只是主线程结束了,主栈空了,其他的 栈/线程 可能还在压栈弹栈。

    在这里插入图片描述

  5. 分析一个问题:对于单核的CPU来说,针对可以做到真正的多线程并发吗?

    对于多核的CPU来说,真正的多线程并发是没问题的。

    ​ 四核表示同一个时间点上,可以真正的有四个进程并发执行。

    什么是真正的多线程并发?

    t1线程执行t1的,t2线程执行t2的。t1不会影响t2、t2也不会影响t1。这叫做:真正的多线程并发。

    单核的CPU表示只有一个大脑:

    ​ 不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发“的感觉,对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:同时在执行多个线程。

    ​ 线程A:播放音乐、线程B:运行魔兽游戏。

    ​ 线程A和线程B频繁切换执行,人会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是在同时并发的。

    电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人的眼睛残生了错觉:感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要”很长的“时间的,在这个期间计算机可以进行亿万次的循环,所以计算机的执行速度很快

  6. 分析以下程序有几个线程

    package thread;
    /*
        分析以下程序,除垃圾回收线程之外,有几个线程?
            1个线程(因为程序只有一个栈)
                main begin
                m1 begin
                m2 begin
                m3 execute
                m2 over
                m1 over
                main over
            一个栈中,自上而下的顺序一次逐行执行。
     */
    public class ThreadText01 {
        public static void main(String[] args) {
            System.out.println("main begin");
            m1();
            System.out.println("main over");
    
        }
    
        private static void m1() {
            System.out.println("m1 begin");
            m2();
            System.out.println("m1 over");
        }
    
        private static void m2() {
            System.out.println("m2 begin");
            m3();
            System.out.println("m2 over");
        }
    
        private static void m3() {
            System.out.println("m3 execute");
        }
    }
    
  7. java语言中,实现线程有两种方式:

    java支持多线程机制,并且java已经将多线程实现了,我们之需要继承就行了

    • 第一种方式:编写一个类,直接继承Java.lang.Thread,重写run方法

      //定义线程类
      public class MyThread extends Thread{
      	public void run{
      	
      	}
      }
      
      //创建线程对象
      MyThread t = new MyThread();
      //启动线程
      t.start();
      
      package thread;
      
      /*
          实现线程的第一种方式:
              编写一个类,直接继承java.lang.Thread,重写run方法。
      
              怎么创建线程对象?new就行了
              如何启动栈?  调用线程对象的start()方法
      
          注意:
              亘古不变的道理:方法体中的代码永远都是自上而下的顺序依次逐行执行的。
      
          t.start()启动分支线程后这个程序的输出结果有这样的特点:
              有先有后、有多有少
                      主线程 >> 80
                      分支线程------>27
                      主线程 >> 81
                      分支线程------>28
                      分支线程------>29
                      主线程 >> 82
                      分支线程------>30
                      主线程 >> 83
      
       */
      public class ThreadText02 {
          public static void main(String[] args) {
              //这里是main方法,这里的代码属于主线程,在主栈中运行。
              //新建一个分支线程对象
              MyThread t = new MyThread();
      
              //启动线程
              //t.run();//与mt.start()的区别:t.run()不会分配新的分支栈/不会启动线程(这种方式就是单线程。)
              //t.run()执行完成之后,才会执行剩余main方法的代码。不是线程并发,只是普通的调用方法
      
              //start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
              //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了
              //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
              //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
              t.start();
              /*  输出结果:
                  主线程 >> 80
                  分支线程------>27
                  主线程 >> 81
                  分支线程------>28
                  分支线程------>29
                  主线程 >> 82
                  分支线程------>30
                  主线程 >> 83
               */
      
              //这里的代码还是运行到主线程中
              for(int i = 0; i < 100 ; i++){
                  System.out.println("主线程 >> "+i);
              }
      
          }
      }
      
      class MyThread extends Thread{
          @Override
          public void run() {
              //编写程序,这段程序运行在分支线程中(分支栈)
              for(int i = 0; i<1000 ; i++){
                  System.out.println("分支线程------>"+i);
              }
          }
      }
      
      

      线程的run内存图

      在这里插入图片描述

      线程的start内存图

      在这里插入图片描述

    • 第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法

      //定义一个可运行的类
      public class MyRunnable implements{
      	public void run(){
      	
      	}
      }
      //创建线程对象
      Thread t = new Thread(new MyRunnable());
      //启动线程
      t.start();
      
      package thread;
      /*
          实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
       */
      public class ThreadText03 {
          public static void main(String[] args) {
              //创建一个可运行的对象
      //        MyRunnable r= new MyRunnable();
              //将可运行的对象封装成一个线程对象
      //        Thread t = new Thread(r);
              Thread t = new Thread(new MyRunnable());
              //启动线程
              t.start();
              for(int i = 0;i<1000;i++){
                  System.out.println("主线程--->"+i);
              }
      
          }
      }
      
      //者并不是一个线程类,是一个可运行的类。它还不是一个线程
      class  MyRunnable implements Runnable{
      
          @Override
          public void run() {
              for(int i = 0;i<1000;i++){
                  System.out.println("分支线程--->"+i);
              }
          }
      }
      

      采用匿名内部类的方式new

      package thread;
      
      /*
          采用匿名内部类
       */
      public class ThreadText04 {
          public static void main(String[] args) {
      
              //创建线程对象,采用匿名内部类的方式,虽然不能直接new一个接口对象,
              // 但在这里是创建了一个类继承了这个接口,且这个类没有名字,同时这个方法写在了方法里。所以叫:匿名内部类
              //通过一个没有名字的类,new出来的对象。
              Thread t = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      for(int i =0;i<1000;i++){
                          System.out.println("分支线程---->"+i);
                      }
                  }
              });
      
              //启动线程
              t.start();
      
              for(int i =0;i<1000;i++){
                  System.out.println("主线程 >> "+i);
              }
          }
      }
      
    • 注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。

  8. 线程的生命周期

    开始状态、就绪状态、运行状态、阻塞状态、死亡状态

    在这里插入图片描述

  9. 怎么获取当前线程对象、获取线程名字、修改线程名字、默认线程名字

    package thread;
    
    /*
        1、怎么获取当前线程对象?
            static Thread currentThread();
            Thread t = Thread.currentThread();  返回值t就是当前线程
    
        2、获取线程的名字
            线程对象.getName();
    
        3、修改线程对象的名字
            线程对象.setName("线程名字");
    
        4、当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
            Thread-0
            Thread-1
            Thread-2
            Thread-3
             ······
     */
    public class ThreadText05 {
        public static void main(String[] args) {
    
            //currentThread就是当前线程对象。这个代码吹现在main方法中,所以当前线程就是主线程
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName());    //main
    
            //创建线程对象
            MyThread2 t = new MyThread2();
            //设置线程的名字
            //t.setName("mtmt");
            //获取线程的名字
            System.out.println(t.getName());    //默认的线程名字:Thread-0
    
            //启动线程
            t.start();
    
            MyThread2 t2 = new MyThread2();
            System.out.println(t2.getName());   //Thread-1
        }
    }
    
    class MyThread2 extends Thread{
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                //System.out.println("分支线程---->"+i);
    
                //currentThread就是当前线程对象。当前线程是谁呢?
                //t1线程启动,执行run方法,当前线程t1,当前线程的名字:t1线程的名字
                //t2线程启动,执行run方法,当前线程t2,当前线程的名字:t2线程的名字
                Thread currentThread = Thread.currentThread();
                System.out.println(currentThread.getName()+"分支线程--->"+i);
            }
        }
    }
    
    
  10. 关于线程的sleep(long millis)

    package thread;
    
    /*
        关于线程的sleep(long millis);
        1、静态方法
    
        2、参数是毫秒
    
        3、作用:让当前线程进入休眠,进入“阻塞状态”:放弃占用的CPU时间片,让给其他线程使用
            这行代码出现在A线程中,A线程就会进入休眠;这行代码出现在B线程,B线程就会进入休眠
    
        4、Thread.sleep()方法,可以做到这种效果:
            间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
    
     */
    public class ThreadText06 {
        public static void main(String[] args) {
    
            try {
                //让当前线程进入休眠:睡眠5秒。当前线程:主线程
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //5秒之后执行这里
            System.out.println("Hello World");
    
            for(int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"---->"+i);
                //睡眠1秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  11. sleep方法的一个面试题

    package thread;
    
    /*
        关于sleep方法的面试题
     */
    public class ThreadText07 {
        public static void main(String[] args) {
            //创建线程对象
            Thread t = new MyThread3();
            t.setName("t");
            t.start();
    
            //调用sleep方法
            try {
                //问:该代码会让线程t进入休眠吗?   不会
                t.sleep(1000);//在执行的时候还是会转换成:Thread.sleep(1000);
                //这行代码的作用是:让当前线程进入休眠,也就是说main进入休眠。因为这行代码是出现在main方法中,所以main线程睡眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i=0 ;i<10000;i++){
                System.out.println("-------------Hello world");
            }
        }
    }
    
    class MyThread3 extends Thread{
        @Override
        public void run() {
            for(int i = 0; i<10000;i++){
                System.out.println(Thread.currentThread().getName()+"---->"+i);
            }
        }
    }
    
  12. sleep如果睡太久了,终止线程睡眠的方法:

    t.interrupt();方法

    package thread;
    
    /*
        sleep睡眠太久了,如果希望线程在睡眠中醒来,如何叫醒这个线程。
            这个不是终止线程的执行,而是终止线程的睡眠
     */
    public class ThreadText08 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable2());
            t.setName("t");
            t.start();
    
            //希望5秒之后,t线程醒来工作。(5秒之后,主线程空闲下来了。)
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //终止t线程的睡眠(这种终止睡眠的方法依靠了java的异常处理机制。)
            t.interrupt();  //干扰,一盆冷水破过去
        }
    }
    
    class MyRunnable2 implements Runnable{
    
        //重点:run()当中的异常不能throws,只能try..catch
        //因为run()方法在父类中没有抛出异常,子类不能比父类抛出更多的异常
        //其他的方法可以通过抛出的方式
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"---->begin");
            //睡眠一年
            try {
                Thread.sleep(1000*60*60*24*365);
            } catch (InterruptedException e) {
                //打印异常信息
                e.printStackTrace();
            }
            //一年之后才会执行这里
            System.out.println(Thread.currentThread().getName()+"---->end");
    
    
    
            //如果在run方法里调用需要解决异常的方法,只能try..catch。因为父类的run方法没有抛出异常,子类重写此方法时也不能抛出异常。
    /*        try {
                doOther();
            } catch (Exception e) {
                e.printStackTrace();
            }*/
        }
        public void doSome() throws Exception {//在这里则可以上抛异常。
            doOther();
        }
    
    
    
        //其他方法可以throws
        public void doOther() throws Exception{
    
        }
    
    }
    

    强行终止线程的方法(已过时,不建议使用)

    package thread;
    
    /*
        在java中怎么强行终止一个线程的执行
            线程对象.stop();
    
        stop方法的缺点:
            容易丢失数据。因为这种方式是直接将线程杀死了。线程里没有保存到数据将会丢失,不建议使用。
    
     */
    public class ThreadText09 {
        public static void main(String[] args){
            Thread t = new Thread(new MyRunnable3());
            t.setName("t");
            t.start();
    
            //模拟5秒
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //5秒后强行终止t线程
            t.stop();   //已过时(不建议使用)
        }
    }
    
    class MyRunnable3 implements Runnable{
    
        @Override
        public void run() {
            for(int i =0 ;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"---->"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    合理终止线程执行的常用方法

    package thread;
    
    /*
        如何合理的终止一个线程的执行,这种方式是很常用的。
     */
    public class ThreadText10 {
        public static void main(String[] args) {
            MyRunnable4 r =new MyRunnable4();
            Thread t = new Thread(r);
            t.setName("t");
            t.start();
    
            //模拟5秒
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //5秒后终止t线程
            //想要什么时候终止t的执行,那么把标记修改为false,就结束了。
            r.run =false;
        }
    }
    
    class MyRunnable4 implements Runnable{
    
        //打一个布尔标记
        boolean run = true;
        @Override
        public void run() {
    
            for(int i =0 ;i<10;i++){
                if(run){
                    System.out.println(Thread.currentThread().getName()+"---->"+i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    //return就结束了,在结束之前还有什么没保存的,在这里可以保存
                    //save...
    
                    //终止当前线程。
                    return;
                }
    
            }
        }
    }
    

线程调度(了解)

  1. 常见的线程调度模式有哪些?

    • 抢占式调度模型:

      哪个线程的优先级比较高,抢到的CPU时间片概率就 高/多 一些。

      (java采用的就是抢占式调度模型)

    • 均分式调度模型

      平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。

      有一些编程语言,线程调度模型采用的是这种方式

  2. java中提供了哪些方法是与线程调度有关系的呢?

    • 实例方法

      • void setPriority(int newPriority) 设置线程的优先级

      • int getPriority() 获取线程优先级

        最低优先级:1 、默认优先级:5 、最高优先级:10

        优先级比较高的线程获取的CPU时间片可能会多一些。(但也不完全是,大概率是多的)

    • 静态方法

      • static void yield() 让位方法。暂停当前正在执行的线程对象,并执行其他线程

        yield()方法不是阻塞方法。只是让当前线程让位,让给其他线程使用。

        yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"。

        注意:回到"就绪状态"之后,有可能还会再次抢到

    • 实力方法

      • void join() 合并线程

        class MyThread1 extends Thread{
        	public void doSome(){
        		MyThread t = new MyThread();
        		t.join();//当前线程进入阻塞,t线程执行。直到t线程结束,当前线程才可以继续
        	}
        }
        
        class MyThread2 extends Thread{
        
        }
        
  3. 关于线程的优先级

    package thread;
    
    /*
        关于线程的优先级
     */
    public class ThreadText11 {
        public static void main(String[] args) {
            //设置主线程的优先级:1
            Thread.currentThread().setPriority(1);
    
    /*        System.out.println("最低优先级"+Thread.MIN_PRIORITY);//1
            System.out.println("默认优先级"+Thread.NORM_PRIORITY);//5
            System.out.println("最高优先级"+Thread.MAX_PRIORITY);//10*/
    
            //获取当前线程对象,获取当前线程的优先级
    /*        Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName()+"线程的默认优先级是:"+currentThread.getPriority());*/
            //main线程的默认优先级是:5
    
            Thread t = new Thread(new MyRunnable5());
            t.setName("t");
            t.start();  //t线程的默认优先级是:5
    
            //优先级较高的,只是抢到的CPU时间片较多一些。(是指处于运行状态的时间多一些)
            //大概率方向更偏向于优先级比较高的
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName()+"------>"+i);
            }
    
    
    
        }
    }
    
    class MyRunnable5 implements Runnable{
    
        @Override
        public void run() {
            //获取线程的优先级
            //System.out.println(Thread.currentThread().getName()+"线程的默认优先级是:"+Thread.currentThread().getPriority());
            for(int i=0;i<10000;i++){
                System.out.println(Thread.currentThread().getName()+"------>"+i);
            }
        }
    }
    
  4. 线程的让位方法

    package thread;
    
    /*
        让位,当前线程暂停,回到就绪状态,让给其他线程,让了之后还是会抢夺CPU时间片。
        静态方法:Thread.yield();
     */
    public class ThreadText12 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable6());
            t.setName("t");
            t.start();
    
            for(int i = 1 ; i <= 10000 ;i++){
                System.out.println(Thread.currentThread().getName()+"---->"+i);
            }
        }
    }
    
    class MyRunnable6 implements Runnable{
    
        @Override
        public void run() {
            for(int i = 1 ; i <= 10000 ;i++){
                //每100个让位一次。
                if(i %100 == 0){
                    Thread.yield();//当前线程暂停一下,让位给主线程
                }
                System.out.println(Thread.currentThread().getName()+"---->"+i);
            }
        }
    }
    
  5. 线程的合并

    package thread;
    /*
        线程合并
            线程对象t.join();
            t线程合并到当前线程中,当前线程受到阻塞,t线程执行结束之后,当前线程才会执行
    
        在内存上体现为:
            两个栈之间发生了等待关系,而不是两个栈合并。
    
     */
    public class ThreadText13 {
        public static void main(String[] args) {
            System.out.println("main begin");
    
            Thread t = new Thread(new MyRunnable7());
            t.setName("t");
            t.start();
    
            //合并线程
            try {
                t.join();   //t线程合并到当前线程中,当前线程受到阻塞,t线程执行结束之后,当前线程(即主线程)才会执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("main over");
        }
    }
    
    class MyRunnable7 implements Runnable{
    
        @Override
        public void run() {
            for(int i =0;i<100000;i++){
                System.out.println(Thread.currentThread().getName()+"---->"+i);
            }
        }
    }
    

多线程并发环境下,数据的安全问题

  1. 为什么这个是重点?

    以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义、线程对象的创建、线程的启动等,都已经实现完了。这些代码我们都不需要编写。

    最总要的是:你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(※※※重点※※※)

  2. 什么时候,数据在多线程并发的情况下会出现安全问题呢?

    多线程并发、有共享数据、共享数据有修改的行为

    满足以上三个条件之后,就会存在线程安全问题

  3. 怎么解决线程安全的问题呢?

    当多线程并发的情况下,有共享数据,并且这个数据会被修改,此时就存在线程安全问题。如何解决?

    ​ 线程排队执行(不能并发),用排队执行解决线程安全问题。这种机制被称为:线程同步机制。

    ​ 专业术语叫:线程同步。实际上就是线程不能并发了,线程必须排队执行

    怎么解决线程安全问题?

    ​ 使用”线程同步机制“

    线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率,数据不安全,没有效率的事。

  4. 说到线程同步这里,涉及到两个专业术语:

    • 异步编程模型:

      线程:t1、t2。t1与t2的执行互不干扰,各自执行各自的,不发生等待

      这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高)

      异步就是并发

    • 同步编程模型:

      线程:t1、t2。t1执行时,必须等待t2执行结束才执行。或t2执行时,必须等t1执行结束才会执行。

      两个线程之间发生了等待关系,这就是同步变成模型。(效率较低、线程排队执行)

      同步就是排队

  5. java中三大变量【重要】

    • 实例变量:在堆中

    • 静态变量:在方法区

    • 局部变量:在栈中

      以上三大变量中:

      ​ 局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈)

      ​ 局部变量在栈中,所以局部变量永远不会共享

      实例变量在堆中,堆只有一个;静态变量在方法区中,方法区只有一个。

      堆和方法区都是多线程共享的,所以可能存在线程安全问题。

      局部变量+常量:不会有线程安全问题

      成员变量:可能会有线程安全问题。

  6. 如果使用局部便的话:

    建议使用StringBuilder。因为局部变量不存在线程安全问题。选择StringBuilder。

    StringBuffer效率比较低

    非线程安全:ArrayList、HashMap、HashSet

    线程安全:Vector、Hashtable

  7. 总结:synchronized的三种使用方法:

    • 同步代码块 灵活

      synchronized(线程共享对象){
      	同步代码块
      }
      
    • 在实例方法上使用synchronized

      表示共享对象一定是this,并且同步代码块是整个方法体

    • 在静态方法上使用synchronized。

      表示找类锁,类锁永远只有一把。就算创建了100个对象,类锁也只有一把。

      对象锁:1个对象对应一把锁。

  8. 银行账户:不使用线程同步机制,出现的线程安全问题

    在这里插入图片描述

    • 账户类

      package threadsafe;
      
      /*
          银行账户
              不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题
       */
      public class Account {
          //账号
          private String actNo;
          //余额
          private double balance;
      
          public String getActNo() {
              return actNo;
          }
      
          public void setActNo(String actNo) {
              this.actNo = actNo;
          }
      
          public double getBalance() {
              return balance;
          }
      
          public void setBalance(double balance) {
              this.balance = balance;
          }
      
          public Account() {
          }
      
          public Account(String actNo, double balance) {
              this.actNo = actNo;
              this.balance = balance;
          }
      
          //取款的方法
          public void withdraw(double balance){
              //t1和t2并发这个方法。。(t1和t2是两个栈,两个栈操作堆中同意给对象)
      
              //取款之前的余额
              double before = this.balance;
              //取款之后的余额:
              double after = before - balance;
      
              //在这里模拟一下网络延迟,100%会出问题
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              //更新余额
              //思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了,此时一定出问题
              this.setBalance(after);
          }
      }
      
    • 线程类

      package threadsafe;
      
      public class AccountThread extends Thread{
          //两个线程必须共享同一个账户
          private Account act;
      
          //通过构造方法传递过来的账户对象
          public AccountThread(Account act){
              this.act = act;
          }
      
          @Override
          public void run() {
              //run方法的执行表示取款操作
              //假设取款5000
              double money = 5000;
              //取款
              act.withdraw(money);
      
              System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"账户取款"+money+"成功,余额:"+act.getBalance());
          }
      }
      
    • 测试类

      package threadsafe;
      
      public class AccountText {
          public static void main(String[] args) {
              //创建账户对象,只建一个
              Account act = new Account("act-001",10000);
              //创建两个线程,共享这个账户
              Thread t1 = new AccountThread(act);
              Thread t2 = new AccountThread(act);
      
              //设置name
              t1.setName("t1");
              t2.setName("t2");
      
              //启动线程取款
              t1.start();
              t2.start();
      
              /*
                  出现此问题:
                      t1对act-001账户取款5000.0成功,余额:5000.0
                      t2对act-001账户取款5000.0成功,余额:5000.0
               */
          }
      }
      
  9. 使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题。

    使用synchronized同步代码块解决

    • 账户类

      package threadsafe2;
      
      /*
          银行账户
              使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题
       */
      public class Account {
          //账号
          private String actNo;
          //余额
          private double balance;
          //对象
          Object obj = new Object();  //实例变量。(Account对象是多线程共享的,Account对象中的实力变量obj也是共享的)
      
          public String getActNo() {
              return actNo;
          }
      
          public void setActNo(String actNo) {
              this.actNo = actNo;
          }
      
          public double getBalance() {
              return balance;
          }
      
          public void setBalance(double balance) {
              this.balance = balance;
          }
      
          public Account() {
          }
      
          public Account(String actNo, double balance) {
              this.actNo = actNo;
              this.balance = balance;
          }
      
          //取款的方法
          public void withdraw(double balance){
              //以下这几行代码必须时线程排队的,不能并发。一个线程把这里的代码全部执行结束之后,另一个线程才可以进来
              /*
                  线程同步机制的语法是:
                      synchronized(需要某几个线程排队,这几个线程的共享对象){
                          //线程同步代码块
                      }
                  synchronized后面小括号中传的这个“数据”是相当关键的。这个数据必须是多线程共享的数据,才能达到多县城排队。
      
                  ()中写什么?
                      那要看你想让哪些线程同步。
                      假设t1、t2、t3、t4、t5,有5个线程,你只希望t1、t2、t3排队,t4、t5不需要排队。怎么办?
                      你一定要在()中写一个t1 t2 t3共享的对象,而这个对象对于t4 t5来说不是共享的
      
                  这里的共享对象是:账户对象。账户对象是共享的,那么this就是共享对象吧!不一定是this,这只要是多线程共享的那个对象就行
      
                  在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁)。一个对象对应一把锁
                  以下代码的执行原理:
                      1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
      
                      2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,占有这把锁。
                      然后执行同步代码块中的代码,执行过程中保持占有,直到同步代码块中的代码执行完,才会归还这把锁
      
                      3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有“共享对象”的这把锁,如果此时这把锁被t1占有
                      t2只能在同步代码块前等待,直到t1执行结束,t1会归还这把锁,此时t2占有这把锁,进入同步代码块执行程序
      
                      这样就达到了线程排队执行。
                      注意:这个共享对象一定要选好,共享对象一定是需要排队执行的这些线程对象所共享的。
               */
              Object obj2 = new Object();
      /*        synchronized(this){//安全
              //synchronized(obj){//安全
              //synchronized("abc"){//"abc"在字符串常量池中 //安全,但所有线程都会同步
              //synchronized(obj2){ //这样写就不安全了,因为obj2不是共享对象,是局部变量,每个线程执行过来都会new一个,不是共享的
                  double before = this.balance;
                  double after = before - balance;
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  this.setBalance(after);
              }*/
      
                  double before = this.balance;
                  double after = before - balance;
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  this.setBalance(after);
      
      
          }
      }
      
      
    • 线程类

      package threadsafe2;
      
      public class AccountThread extends Thread{
          //两个线程必须共享同一个账户
          private Account act;
      
          //通过构造方法传递过来的账户对象
          public AccountThread(Account act){
              this.act = act;
          }
      
          @Override
          public void run() {
              //run方法的执行表示取款操作
              //假设取款5000
              double money = 5000;
              //取款
      
              //多线程同步这个方法
              //这种方式也可以,只不过扩大了同步范围,效率更低了
              //synchronized(this){//这里的this是指AccountThread对象,这个对象new了两次,AccountThread对象不共享,共享的是act对象
              synchronized(act){
                  act.withdraw(money);
              }
      
      
              System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"账户取款"+money+"成功,余额:"+act.getBalance());
          }
      }
      
    • 测试类

      package threadsafe2;
      
      public class AccountText {
          public static void main(String[] args) {
              //创建账户对象,只建一个
              Account act = new Account("act-001",10000);
              //创建两个线程,共享这个账户
              Thread t1 = new AccountThread(act);
              Thread t2 = new AccountThread(act);
      
              //设置name
              t1.setName("t1");
              t2.setName("t2");
      
              //启动线程取款
              t1.start();
              t2.start();
          }
      }
      
  10. 使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题。

    在实例方法上使用synchronized

    • 账户类

      package threadsafe3;
      
      /*
          银行账户
              使用线程同步机制,多线程对同一个账户进行取款,解决所出现的线程安全问题
       */
      public class Account {
          //账号
          private String actNo;
          //余额
          private double balance;
          //对象
          Object obj = new Object();  //实例变量。(Account对象是多线程共享的,Account对象中的实力变量obj也是共享的)
      
          public String getActNo() {
              return actNo;
          }
      
          public void setActNo(String actNo) {
              this.actNo = actNo;
          }
      
          public double getBalance() {
              return balance;
          }
      
          public void setBalance(double balance) {
              this.balance = balance;
          }
      
          public Account() {
          }
      
          public Account(String actNo, double balance) {
              this.actNo = actNo;
              this.balance = balance;
          }
      
          //取款的方法
          /*
              在实例方法上可以使用synchronized
      
                  缺点:
                      synchronized出现在实例方法上,一定锁的是this。没得挑,只能是this,不能是其他对象了。
                      所以这种方式不灵活
                      synchronized出现在实例方法上,表示整个方法体都要同步,扩大了同步范围
                      会导致执行效率降低,所以这种方式不常用。
      
                  优点:
                      代码写的少了,简洁了。
                      如果共享的对象就是this、并且需要同步的代码块就是整个方法体,建议使用这种方式
           */
          public synchronized void withdraw(double balance){
                  double before = this.balance;
                  double after = before - balance;
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  this.setBalance(after);
      
      
          }
      }
      
    • 线程类

      package threadsafe3;
      
      public class AccountThread extends Thread{
          //两个线程必须共享同一个账户
          private Account act;
      
          //通过构造方法传递过来的账户对象
          public AccountThread(Account act){
              this.act = act;
          }
      
          @Override
          public void run() {
              //run方法的执行表示取款操作
              //假设取款5000
              double money = 5000;
              //取款
                  act.withdraw(money);
      
      
             	 System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"账户取款"+money+"成功,余额:"+act.getBalance());
          }
      }
      
    • 测试类

      package threadsafe3;
      
      public class AccountText {
          public static void main(String[] args) {
              //创建账户对象,只建一个
              Account act = new Account("act-001",10000);
              //创建两个线程,共享这个账户
              Thread t1 = new AccountThread(act);
              Thread t2 = new AccountThread(act);
      
              //设置name
              t1.setName("t1");
              t2.setName("t2");
      
              //启动线程取款
              t1.start();
              t2.start();
          }
      }
      
  11. synchronized总结

    synchronized在实例方法中时,锁住的是这个对象。
    	当锁被占用时,另有线程想要执行此对象中带有synchronized关键字的方法时,需要等待;但如果是执行不带有synchronized的方法时,可以执行,因为调用不带synchronized的方法时,不需要锁就可以执行。
    	如果某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。
    synchronized在静态方法中时,是类锁。
    	类锁被占用时,方法中带有synchronized和static的方法需要等占用类锁的线程结束之后才可以调用。当使用的是同一个对象时,synchronized的方法只能同时执行一个,需要对象锁。当一个方法只有static时,这个方法执行时不需要锁。
    即:对象锁中:只有带有synchronized的方法执行时需要锁才能执行,其他的方法不受锁的影响。
    类锁中:同时带有synchronized和static关键字的需要类锁才能执行;只带有synchronized关键字的方法执行需要对象锁,其他方法的执行不需要等待锁的归还便可以执行。
    
    总结:
    A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 
    B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 
    C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
    
  12. synchronized的面试题1

    package exam01;
    
    //面试题:  doOther方法执行的时候需要等待doSome方法的结束吗?
        /*
            不需要,
            虽然doSome方法有synchronized,锁住的是同一个对象,但doOther方法没有synchronized。
            只是需要执行执行带有synchronized的方法时才需要锁,执行doOther方法时不需要锁,
            所以不需要等待doSome的执行。
    
         */
    
    public class Exam01 {
        public static void main(String[] args) {
            MyClass mc = new MyClass();
            Thread t1 = new MyThread(mc);
            Thread t2 = new MyThread(mc);
    
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            try {
                Thread.sleep(1000);//这里保证t1先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
        }
    }
    
    class MyThread extends Thread{
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        }
        public void run(){
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    
    class MyClass {
        public synchronized void doSome(){
            System.out.println("doSome begin");
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
        public void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    }
    
  13. synchronized的面试题2

    package exam02;
    
    //面试题:  doOther方法执行的时候需要等待doSome方法的结束吗?
        /*需要,因为doOther方法待用有synchronized。同一个对象,锁只有一个,被doSome方法占用了。
            所以需要等
         */
    public class Exam02 {
        public static void main(String[] args) {
            MyClass mc = new MyClass();
            Thread t1 = new MyThread(mc);
            Thread t2 = new MyThread(mc);
    
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            try {
                Thread.sleep(1000);//这里保证t1先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
        }
    }
    
    class MyThread extends Thread{
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        }
        public void run(){
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    
    class MyClass {
        public synchronized void doSome(){
            System.out.println("doSome begin");
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
        public synchronized void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    }
    
  14. synchronized的面试题3

    package exam03;
    
    //面试题:  doOther方法执行的时候需要等待doSome方法的结束吗?
        //不需要,因为传进去的对象不是共享的。各自使用各自的锁
    public class Exam03 {
        public static void main(String[] args) {
            MyClass mc1 = new MyClass();
            MyClass mc2 = new MyClass();
            Thread t1 = new MyThread(mc1);
            Thread t2 = new MyThread(mc2);
    
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            try {
                Thread.sleep(1000);//这里保证t1先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
        }
    }
    
    class MyThread extends Thread{
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        }
        public void run(){
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    
    class MyClass {
        public synchronized void doSome(){
            System.out.println("doSome begin");
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doSome over");
        }
        public synchronized void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
    }
    
  15. synchronized的面试题4

    package exam04;
    
    //面试题:  doOther方法执行的时候需要等待doSome方法的结束吗?
        /*
                需要,虽然传进去的对象不是共享的,但synchronized出现在静态方法上是找类锁,
                 一个类只有一个锁,所有对象共用一个锁,doSome和doOther都是静态方法,都带有synchronized关键字
                 都需要锁才可以执行,所以需要等。
    
         */
    public class Exam04 {
        public static void main(String[] args) {
            MyClass mc1 = new MyClass();
            MyClass mc2 = new MyClass();
            Thread t1 = new MyThread(mc1);
            Thread t2 = new MyThread(mc2);
            Thread t3 = new MyThread(mc2);
            Thread t4 = new MyThread(mc2);
    
            t1.setName("t1");
            t2.setName("t2");
            t3.setName("t3");
            t4.setName("t4");
            t1.start();
            try {
                Thread.sleep(1000);//这里保证t1先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
            try {
                Thread.sleep(1000);//这里保证t1先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t3.start();
            try {
                Thread.sleep(1000);//这里保证t1先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t4.start();
        }
    }
    
    class MyThread extends Thread{
        private MyClass mc;
        public MyThread(MyClass mc){
            this.mc = mc;
        }
        public void run(){
            if(Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if(Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
            if(Thread.currentThread().getName().equals("t3")){
                mc.doss();
            }
            if(Thread.currentThread().getName().equals("t4")){
                mc.dosa();
            }
        }
    }
    
    
    class MyClass {
        //synchronized出现在静态方法上是找类锁,一个类只有一个类锁,同时只能执行一个既带有synchronized有又带有static关键字的方法
        public synchronized static void doSome(){
            System.out.println("doSome begin");
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
        }
            System.out.println("doSome over");
        }
        public synchronized static void doOther(){
            System.out.println("doOther begin");
            System.out.println("doOther over");
        }
        public synchronized void doss(){
            System.out.println("doooooooooooooooooooooooooooooooooo");
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("doooooooooooooooooooooooooooooooo");
        }
        public synchronized void dosa(){
            System.out.println("daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
            System.out.println("doaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        }
    }
    
  16. 死锁

    在这里插入图片描述

    死锁案例

    package deadlock;
    
    /*
        死锁代码要会写。
            一般面试官会要求你写,只有会写的,才会在以后的开发中注意这个事情。
            因为死锁很难调试
    
        以下程序:
            o2对象锁被t2线程占用,而t2线程继续执行需要o1的对象锁,o1正被t1使用
            o1对象锁被t1线程占用,而t1线程继续执行需要o2的对象锁,o2正被t2使用
            t1线程等待t2线程、t2线程等待t1线程,就发生了死锁。
    
        结论:
            synchronized在开发中最好不要嵌套使用,一不小心就可能发生死锁现象。
     */
    public class DeadLock {
        public static void main(String[] args) {
            Object o1 = new Object();
            Object o2 = new Object();
    
            //t1和t2这两个线程共享:o1、o2
            Thread t1 = new MyThread1(o1,o2);
            Thread t2 = new MyThread2(o1,o2);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            t2.start();
        }
    }
    
    class MyThread1 extends Thread{
        Object o1;
        Object o2;
    
        public MyThread1(Object o1, Object o2) {
            this.o1 = o1;
            this.o2 = o2;
        }
    
        @Override
        public void run() {
            synchronized (o1){
                System.out.println("1");
                //占有o1的对象锁之后睡一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //o2被t2线程占用,而t2线程继续执行需要o1的对象锁。
                synchronized (o2){
                    System.out.println("2");
    
                }
            }
        }
    }
    class MyThread2 extends Thread{
        Object o1;
        Object o2;
    
        public MyThread2(Object o1, Object o2) {
            this.o1 = o1;
            this.o2 = o2;
        }
    
        @Override
        public void run() {
            synchronized (o2){
                System.out.println("3");
                //占有o2的对象锁后,睡一秒。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    //o1对象锁被t1线程占用,而t1线程的继续执行需要o2的对象锁
                    System.out.println("4");
                }
            }
        }
    }
    
  17. 以后开发中应该怎么解决线程安全问题?

    • 是一上来就选择线程同步(synchronized)吗?

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

    • 第一种方案:

      尽量使用局部变量代替“实例变量“和”静态变量“,

    • 第二种方案:

      如果必须是实例变量,那么可以考虑拆功能键多个对象,这样实例变量的内存就不共享了。

      (一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)

    • 第三种方案:

      如果不能使用局部变量,对象也不能创建多个,这个使用就只能选择synchronized(线程同步机制)了

  18. 守护线程概述

    • java语言中线程分为两大类:

      • 用户线程

      • 守护线程(后台线程)

        其中具有代表性的就是:垃圾回收线程(守护线程)

    • 守护线程的特点:

      一般守护线程是一个死循环,所有的用户线程只要结束。守护线程自动结束

    • 主线程main方法是一个用户线程

    • 守护线程用在什么地方呢?比如:

      每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果都结束了,守护线程自动退出,没有必要进行数据备份了。

    package thread;
    
    /*
        守护线程
            启动线程之前,将线程设置为守护线程
            线程对象t.setDaemon(true);
     */
    public class ThreadText14 {
        public static void main(String[] args) {
            Thread t = new BakDataThread();
            t.setName("备份数据的线程");
    
            //启动线程之前,将线程设置为守护线程
            t.setDaemon(true);
    
            t.start();
    
            //主线程:主线程是用户线程
            for(int i = 0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+">>>>"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class BakDataThread extends Thread{
        @Override
        public void run() {
            int i = 0;
            //即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
            while(true){
                System.out.println(Thread.currentThread().getName()+"---->"+(++i));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  19. 定时器概述

    定时器的作用:

    ​ 间隔特定的时间,执行特定的程序

    ​ 如:每天要进行数据备份操作、每周要进行银行账户的总账操作。

    在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:

    • 可以使用sleep方法,睡眠,设置睡眠时间,每到一个固定的时间醒来,执行任务。这种方式是最原始的定时器(比较low)
    • 在Java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的
    • 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器任务。
    package thread;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    /*
        使用定时器指定定时任务
     */
    public class TimerText {
        public static void main(String[] args) throws ParseException {
            //创建计时器对象
            Timer timer = new Timer();
            //Timer timer = new Timer(true);    //守护线程方式
    
            //指定定时任务
            //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次)
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date firstTime = sdf.parse("2021-02-25 14:04:40");
            timer.schedule(new LogTimerTask() , firstTime , 1000*10);//每10秒执行一次
            /*
            2021-04-25 14:04:40成功完成了一次数据备份
            2021-04-25 14:04:50成功完成了一次数据备份
            2021-05-25 14:05:00成功完成了一次数据备份
            2021-05-25 14:05:10成功完成了一次数据备份
            2021-05-25 14:05:20成功完成了一次数据备份
             */
    
            //可以采用匿名内部类的方式
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    //代码
                }
            },firstTime,1000*10);
    
        }
    }
    
    //编写一个定时任务类,假设这是一个记录日志的定时任务
    class LogTimerTask extends TimerTask{
    
        @Override
        public void run() {
            //编写你需要执行的任务
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
            String strTime = sdf.format(new Date());
            System.out.println(strTime+"成功完成了一次数据备份");
        }
    }
    
  20. 实现线程的第三种方式:实现Callable接口。(JDK新特性)

    这种方式实现的线程可以获取线程的返回值。之前讲解的那种方式是无法获取线程返回值的,因为run方法返回void

    思考:

    ​ 系统委派一个线程去执行一个任务,该线程执行完成之后,可能会有一个执行结果,我们怎么才能拿到这个执行结果呢?

    ​ 使用第三种方式实现:实现Callable接口方式

    package thread;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask; //JUC包下的,属于java并发包,老JDK中没有这个包。新特性
    
    /*
        实现线程的第三种方式:
            实现Callable接口
            这种方式的优点:可以获取到线程的执行结果。
            这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程收到阻塞
     */
    public class ThreadText15 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //第一步:创建一个“未来任务类”对象
            //参数非常重要,需要给一个Callable接口实现类对象
            FutureTask task = new FutureTask(new Callable() {
                @Override
                public Object call() throws Exception {//call()方法就相当于run方法,只不过这个方法有返回值
                    //线程执行一个任务,执行之后可能会有一个执行结果。
                    //模拟执行
                    System.out.println("call method begin");
                    Thread.sleep(1000*10);
                    System.out.println("call method over");
                    int a = 100;
                    int b =200;
                    return a+b; //自动装箱:300结果变成Integer
                }
            });
    
            //创建线程对象
            Thread t =new Thread(task);
    
            //启动线程
            t.start();
    
            //这里是main方法,在主线程中。
            //在主线程中,怎么获取t线程的返回结果?
            //get()方法的执行会导致“当前线程阻塞”
            Object obj = task.get();
            System.out.println("线程执行结果:"+obj);
    
            //main方法这里的代码想要执行必须等待get()方法的结束。
            //而get方法可能需要很久,因为get()方法是为了拿另一个线程的返回值,需要等这个线程执行完。
            System.out.println("helloworld");
    
        }
    }
    
  21. 关于Object类中的wait方法和notify方法(生产者和消费者模式)

    • 第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的

    • 第二:wait()方法作用:

      Object o = new Object(); o.wait();

      表示: 让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。

      o.wait()方法的调用,会让”当前线程“(正在o对象上活动的线程)进入等待状态

    • 第三:notify()方法的作用:

      Object o = new Object(); o.notify();

      表示:唤醒正在o对象上等待的线程

      还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程

    在这里插入图片描述

  22. 生产者和消费者模式

    在这里插入图片描述

    package thread;
    
    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集合中假设只能存储一个元素,一个就表示仓库满了
            如果List集合元素个数是0,就表示仓库满了
            保证List集合中永远都是最多存储一个元素。
    
            必须做到:生产一个消费一个
     */
    public class ThreadText16 {
        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){
                synchronized (list){
                    if(list.size()>0){//大于0说明仓库中已经有一个元素了
                        //当前线程进入等待状态,并且释放Producer之前占用的list集合的对象锁
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            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) {
            this.list = list;
        }
    
        @Override
        public void run() {
            //一直消费
            while (true){
                synchronized (list){
                    if(list.size() == 0){//=0说明仓库中已经空了,消费者线程等待,释放list锁
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //程序能执行到这里,说明仓库有东西,进行消费
                    Object obj =list.remove(0);
                    System.out.println(Thread.currentThread().getName()+"---->"+obj);
                    //唤醒生产者生产
                    list.notify();
                }
            }
        }
    }
    
  23. 作业

    package thread;
    /*
    
    1、使用生产者和消费者模式实现,交替输出:
       假设只有两个线程,输出以下结果:
          t1-->1
          t2-->2
          t1-->3
          t2-->4
          t1-->5
          t2-->6
          ....
    
          要求:必须交替,并且t1线程负责输出奇数。t2线程负责输出偶数。
          两个线程共享一个数字,每个线程执行时都要对这个数字进行:++
    
          public class Num {
             int i;
          }
    
          synchronized(num){
             if(num是奇数){
                num.wait();
             }
             // 输出偶数
             // notifyAll()
          }
    
          synchronized(num){
             if(num是偶数){
                num.wait();
             }
             // 输出奇数
             // notifyAll();
          }
     */
    public class ThreadText17 {
        public static void main(String[] args) {
            //创建一个数
            Num num = new Num(1);
            //创建两个线程,公用num对象
            Thread ji = new Thread(new JiShu(num));
            Thread ou = new Thread(new OuShu(num));
            ji.setName("奇数");
            ou.setName("偶数");
            ji.start();
            ou.start();
    
        }
    }
    
    class Num{
        int i;
    
        public Num() {
        }
    
        public Num(int i) {
            this.i = i;
        }
    
        public int getI() {
            return i;
        }
    
        public void setI(int i) {
            this.i = i;
        }
    }
    //输出奇数的线程
    class JiShu implements Runnable{
        private Num num;
    
        public JiShu(Num num) {
            this.num = num;
        }
    
        @Override
        public void run() {
            while (true){
                synchronized (num){
                    //如果num是偶数,则等待
                    if(num.i % 2 == 0){
                        try {
                            num.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //执行到这里,说明是奇数,输出后自加1。
                    System.out.println(Thread.currentThread().getName()+"--->"+num.i++);
                    num.notify();
                }
            }
    
        }
    }
    
    //输出偶数的线程
    class OuShu implements Runnable{
        private Num num;
    
        public OuShu(Num num) {
            this.num = num;
        }
    
        @Override
        public void run() {
            while (true){
                synchronized (num){
                    //如果num是奇数,则等待
                    if(num.i % 2 != 0){
                        try {
                            num.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //执行到这里,说明是偶数
                    System.out.println(Thread.currentThread().getName()+"--->"+num.i++);
                    num.notify();
                }
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值