• Java(2)多线程


    @

    一、基本概念的理解

    1、程序、进程、线程

    1. 程序(program):一组指令的集合,一段静态的代码。
    2. 进程(progress):程序的一次执行过程,或正在运行的一个程序。
    3. 线程(thread):一个进程内部的一条执行路径。

    2、单核CPU与多个CPU

    1. 单核CPU:只有一个CPU芯片。一个极短的时间内只能执行一个线程任务。由于在线程之间切换的时间极短,给人多线程的假象。
    2. 多核CPU:多个CPU芯片。

    3、串行、并行、并发

    1. 串行:一个线程执行完再执行下一个线程。
    2. 并行:多个CPU同时执行多个任务。
    3. 并发:一个CPU同时执行多个任务,交替执行,抢占CPU时间。

    二、多线程

    1、java中的多线程举例

    • 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

    2、多线程优点

    • 提高应用程序的响应。命令行下,我们一般一个时间只做一件事,但是图形界面下则不是,因此有必要来使用多线程保证各个任务的响应速度。
    • 提高CPU利用率。
    • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。比如

    3、何时需要多线程

    1. 程序需要同时执行两个或多个任务。
    2. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
    3. 需要一些后台运行的程序时。

    三、多线程的创建和使用★★★★★

    1、方式一:继承Thread类(JDK1.5之前的两种之一)

    1. 代码实现
    package com.thread;
    
    public class ThreadTest1 {
        public static void main(String[] args) {
            new MyThread1().start();
        }
    }
    
    
    class MyThread1 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(MyThread1.currentThread().getName() + ":" + i);
            }
        }
    }
    
    //当某个线程只适用了一次的时候,也可以使用匿名类匿名对象的方式来创建多线程
    package com.thread;
    
    public class ThreadTest2 {
        public static void main(String[] args) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println("我是子线程");
                }
            }.start();
        }
    }
    
    1. 步骤总结
    1)  定义子类继承Thread类。 
    2)  子类中重写Thread类中的run方法。
    3)  创建Thread子类对象,即创建了线程对象。 
    4)  调用线程对象start方法:启动线程,调用run方法。
    
    1. 注意:
    • 启动多线程,必须调用start方法。如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式
    • 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。
      • 因此,如果我们想两个线程同时做一件事,要new两个对象
      • 因此,如果我们想两个线程同时做两件事,重写一个run()是不够的,这是我们就要创建2个类继承,然后再分别重写run()。
    1. Thread类中的方法(详细解释看API文档)

    2. 线程的调度:时间片策略、抢占式

      1. Java中,同优先级线程组成先进先出队列(先到先服务),使用时间片策略
      2. 对高优先级,使用优先调度的抢占式策略
    3. 线程的优先级

      1. 优先级等级
      • MAX_PRIORITY:10
      • MIN _PRIORITY:1
      • NORM_PRIORITY:5
      1. 方法
      • getPriority() :返回线程优先值
      • setPriority(int newPriority) :改变线程的优先级

    2、方式二:实现Runnable接口(JDK1.5之前的两种之一)

    1. 代码实现

      public class ThreadTest3 {
          public static void main(String[] args) {
              MyThread3 myThread3 = new MyThread3();
      
              Thread t1 = new Thread(myThread3);
              
              t1.start();
          }
      }
      
      class MyThread3 implements Runnable{
      
          @Override
          public void run() {
              System.out.println("我是子线程");
          }
      }
      
      //当某个线程只适用了一次的时候,也可以使用匿名类匿名对象的方式来创建多线程
      new Thread(new Runnable() {
                  @Override
                  public void run() {
                      
                  }
              }).start();
      
    2. 步骤总结

      1. 创建一个实现了Runnable接口的类
      2. 实现类去实现Runnable中的抽象方法:run()
      3. 创建实现类的对象
      4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
      5. 通过Thread类的对象调用start()
      
    3. 比较两种创建线程的方式

      1. 开发中:优先选择:实现Runnable接口的方式

        原因:

        1. 实现的方式没有类的单继承性的局限性
        2. 实现的方式更适合来处理多个线程有共享数据的情况。
      2. 联系:

        1. public class Thread implements Runnable
        2. 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

    3、方式三:实现Callable接口

    4、方式四:使用线程池(开发中主要是用这个)

    四、线程的生命周期

    1、线程的生命周期★★★★★

    • 说明
      • 一个线程的最终状态是死亡,不能停在阻塞。

    五、线程的同步

    1、线程安全问题举例和解决

    • 以卖票窗口为例,三个窗口本来就存在重票、错票(票号小于等于0),只是概率较小。可以通过sleep方法来使这种效果更加明显。这就是线程安全问题
    class Window implements Runnable{
    
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                if (ticket > 0){
                    //在这里让刚进入的线程阻塞100ms,错票的概率就会增加
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("售票,票号为: " + ticket);
                    //如果在这里让刚进入的线程阻塞100ms,重票的概率就会增加
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
    public class WindowTest {
        public static void main(String[] args) {
            Window w1 = new Window();
            Thread t1 = new Thread(w1);
            Thread t2 = new Thread(w1);
            Thread t3 = new Thread(w1);
    
            t1.setName("窗口1");
            t1.setName("窗口2");
            t1.setName("窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    • 解决方案:当一个线程a操作ticket的时候,其他线程不能进来,知道a完事了,及时是a阻塞了,也不能进来。(和上厕所把门锁住一个道理,逃~)在java中用同步机制来解决。
    • 对于继承和实现的方式,同步锁解决线程安全问题时,是重要牢记继承会创建多个对象,一般都要用static来修饰属性和方法的。

    2、同步代码块

    1. 语法

      synchronized(锁){
          需要被同步的代码
      }
      
    2. 说明

      1. 锁:即同步监视器,任何一个对象都可以充当锁。但是要求,多个线程必须共用一把锁(就是说,new对象这一步,必须是只发生一次)
      2. 被同步的代码:这一部分是操作共享数据的代码。但是不能包多--->如果我们把while也包起来,就成了一个窗口卖所有的票。
    3. 对window的改进

      1. 对继承的方式的改进
      /*
      说明:
      	1、继承的方式的特点就是我们需要new好几个对象,所以需要这些对象都共享票数和锁,声明为static即可解决这个问题。
      	2、还可以用Window1.class来充当锁。
      */
      
      class Window1 extends Thread{
      
          private static int ticket = 100;
      
          static Object obj = new  Object();
      
          @Override
          public void run() {
              while (true) {
                  synchronized (obj) {
                      if (ticket > 0) {
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println("ticket_number=" + ticket);
                          ticket--;
                      }else{
                          break;
                      }
                  }
              }
          }
      }
      
      public class Window_Thread_Security1 {
          public static void main(String[] args) {
      
              Window1 w1 = new Window1();
              Window1 w2 = new Window1();
              Window1 w3 = new Window1();
      
              w1.start();
              w2.start();
              w3.start();
          }
      }
      
      1. 对实现方式的改进
      /*
      说明:
      	1、实现的方式的特点是只new了一个类的对象,因此票数和锁自动就是共享的。
      	2、还可以用this来充当锁。
      */
      class Window2 implements Runnable{
      
          private int ticket = 100;
      
          @Override
          public void run() {
              while (true) {
                  synchronized (this) {
                      if (ticket > 0) {
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println("ticket_number=" + ticket);
                          ticket--;
                      }else{
                          break;
                      }
                  }
              }
          }
      }
      public class Window_Thread_Security2 {
          public static void main(String[] args) {
              Window2 window2 = new Window2();
      
              Thread t1 = new Thread(window2);
              Thread t2 = new Thread(window2);
              Thread t3 = new Thread(window2);
      
              t1.start();
              t2.start();
              t3.start();
          }
      }
      
    4. 优缺点

      1. 优:解决了线程安全问题
      2. 缺:操作同步代码时,只有一个线程参与,相当于是单线程,效率低。

    3、同步方法

    1. 语法:synchronized放在方法声明中,表示整个方法是同步方法

      public synchronized void show (String name){
      	//方法体
      }
      
    2. 对window问题的改进

      1. 对继承方式的改建
      class Window3 extends Thread{
      
          private static int ticket = 100;
      
          private static synchronized void show(){//因为我们这里的默认省略了锁,
              // 如果不加static,锁就是this,这显然多个对象对对应多个锁
              //加了static以后,锁就成了Window3.class,是惟一的锁
                  if (ticket > 0) {
      
                      try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      System.out.println("ticket_number=" + ticket);
                      ticket--;
                  }
          }
      
          @Override
          public void run() {
              while (true) {
                  show();
              }
          }
      }
      
      public class Window_Thread_Security3 {
          public static void main(String[] args) {
      
              Window1 w1 = new Window1();
              Window1 w2 = new Window1();
              Window1 w3 = new Window1();
      
              w1.start();
              w2.start();
              w3.start();
          }
      }
      
      1. 对实现方式的改进
      class Window4 implements Runnable{
      
          private int ticket = 100;
      
          private synchronized void show(){//这时,锁是this,只创建了一个Window4的对象,是唯一的.
              if (ticket > 0) {
      
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
      
                  System.out.println("ticket_number=" + ticket);
                  ticket--;
              }
          }
      
          @Override
          public void run() {
              while (true) {
                  show();
              }
          }
      }
      public class Window_Thread_Security4 {
          public static void main(String[] args) {
              com.thread.Window2 window2 = new com.thread.Window2();
      
              Thread t1 = new Thread(window2);
              Thread t2 = new Thread(window2);
              Thread t3 = new Thread(window2);
      
              t1.start();
              t2.start();
              t3.start();
          }
      }
      

    4、死锁

    1. 概念:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
    2. 尽量避免死锁
      1. 专门的算法、原则。
      2. 尽量减少同步资源的定义
      3. 尽量避免嵌套同步
    3. 死锁的演示
    package com.thread3;
    
    public class DeadLock {
        public static void main(String[] args) {
    
            StringBuffer s1 = new StringBuffer();
            StringBuffer s2 = new StringBuffer();
    
            new Thread(){
                @Override
                public void run() {
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
    
                        System.out.println("s1 = " + s1);
                        System.out.println("s2 = " + s2);
                    }
                }
            }.start();
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2) {
    
                        s1.append("c");
                        s2.append("3");
    
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                        synchronized (s1){
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println("s1 = " + s1);
                            System.out.println("s2 = " + s2);
                        }
    
    
                    }
    
            }).start();
        }
    }
    /*
    输出结果有哪些可能
        线程1先执行,线程2后执行:ab  12  adcd  1234
        线程2先执行,线程1后执行:cd  34  cdab  3412
        都阻塞:
     */
    
    

    5、lock

    1. 介绍

      1. 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。(Lock是一个接口)
      2. ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
    2. 语法

    class A{
        private final ReentrantLock lock = new ReentrantLock();
        
        public void method(){
            lock.lock();//请求锁
            try{
                代码逻辑
            }finnaly{
                lock.unlock();//释放锁
            }
        }
    }
    
    1. 用Lock解决window卖票的线程安全问题
    class Window5 extends Thread{
    
        private static int ticket = 100;
    
        private static final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
    			lock.lock();
                try {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("ticket_number=" + ticket);
                        ticket--;
                    }else{
                        break;
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    public class LockTest {
        public static void main(String[] args) {
    
            Window5 w1 = new Window5();
            Window5 w2 = new Window5();
            Window5 w3 = new Window5();
    
            w1.start();
            w2.start();
            w3.start();
        }
    }
    
    1. 面试题
      1. synchronized 与 Lock的异同?
        1. 同:都是用于解决线程安全问题
        2. 异:①、Lock是显示锁,需要我们自己手动开启和关闭;synchronized是隐式锁,出了作用域就会自动释放锁。②、Lock只有代码块锁,synchronized有代码块锁和方法锁。③、Lock锁性能好,拓展性好。
      2. 如何解决线程安全问题,有几种方式?
        1. 两大类。一类是Lock,一类是synchronized
    2. 开发中优先使用:Lock--->同步代码块--->同步方法

    六、线程的通信

    1. 线程通信的例子:使用两个线程打印 1-10。线程1, 线程2 交替打印
    //1、要先分析是不是多线程为题;
    //2、在分析是否有共享数据,有---同步机制来解决
    
    class MyPrinter implements Runnable {
    
        private int number = 1;
    
        @Override
        public synchronized void run() {
            while (true) {
                this.notify();
                if (number <= 10) {
    //                this.notify();//如果我把notify放在这里,程序就不会自动结束了?  因为,放在这里,
                    //最后一个循环的情况下,打印机二带着10进入,可以notify打印机一,再下一轮,打印机一拿着11
                    //,无法进入if,无法notify打印机二,打印机二就一直阻塞在那里
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
    
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
    
    public class ThreadCommunicationTest {
        public static void main(String[] args) {
            MyPrinter myPrinter = new MyPrinter();
            Thread t1 = new Thread(myPrinter);
            Thread t2 = new Thread(myPrinter);
    
            t1.setName("打印机一");
            t2.setName("打印机二");
    
            t1.start();
            t2.start();
        }
    }
    
    1. 涉及到的三个方法:
      1. wait():当前线程阻塞,并释放锁
      2. notify():当前线程唤醒被wait的其他一个线程,谁的优先级高就唤醒谁
      3. notifyAll():当前线程唤醒all被wait的线程
    2. 对这三个方法的说明
      1. 个方法必须使用在同步代码块或同步方法中。
      2. 三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常。
        1. 我这里用的是this;如果我自己new一个Object类,那么this就要改为Object类的对象。
      3. 三个方法是定义在java.lang.Object类中。
    3. 面试题:sleep() 和 wait()的异同?★★★★★
      1. 同:两者都可以使线程阻塞
      2. 异:
        1. 声明的位置不同:sleep()是声明在Thread类中;wait()是声明在Object类中
        2. 使用的位置不同:sleep()使用在任意位置;wait()使用在同步代码块或同步方法中
        3. 使用完了以后做的事不同:sleep()不会释放锁,wait()会释放锁

    七、生产者和消费者问题

    /**
     * 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
     *
     * 涉及到的三个方法:
     * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
     * notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
     * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
     *
     * 说明:
     * 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
     * 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
     *    否则,会出现IllegalMonitorStateException异常
     * 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
     *
     * 面试题:sleep() 和 wait()的异同?
     * 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
     * 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
     *          2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
     *          3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
     *
     * @author shkstart
     * @create 2019-02-15 下午 4:21
     */
    class Number implements Runnable{
        private int number = 1;
        private Object obj = new Object();
        @Override
        public void run() {
    
            while(true){
    
                synchronized (obj) {
    
                    obj.notify();
    
                    if(number <= 100){
    
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + ":" + number);
                        number++;
    
                        try {
                            //使得调用如下wait()方法的线程进入阻塞状态
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                    }else{
                        break;
                    }
                }
    
            }
    
        }
    }
    
    
    public class CommunicationTest {
        public static void main(String[] args) {
            Number number = new Number();
            Thread t1 = new Thread(number);
            Thread t2 = new Thread(number);
    
            t1.setName("线程1");
            t2.setName("线程2");
    
            t1.start();
            t2.start();
        }
    }
    
  • 相关阅读:
    Vue的基本使用
    django中的跨表查询梳理
    docker安装及配置
    大数据分析(一)探索性分析
    搜索引擎-一种提示词推荐算法
    shell变量详解
    机器学习之寻找KMeans的最优K
    大数据系统之监控系统(二)Flume的扩展
    大数据系统之监控系统(一)
    大数据系统之系统设计
  • 原文地址:https://www.cnblogs.com/liuzhixian666/p/13943259.html
Copyright © 2020-2023  润新知