【尚硅谷_java基础】九、多线程

参考资料

  1. Java 多线程编程
  2. https://www.bilibili.com/video/BV1Kb411W75N?p=406

1. 基本概念:程序、进程、线程

  • 程序(program):为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

  • 进程(process):程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

    如:运行中的QQ,运行中的MP3播放器

    程序是静态的,进程是动态的;

    进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

  • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

    1.若一个进程同一时间并行执行多个线程,就是支持多线程的

    2.线程是调度和执行的单位,每个线程拥有独立运行栈和程序计数器(pc),线程切换的开销小

    3.一个进程中的多个线程共享相同的内存单元/内存地址空==》它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

在这里插入图片描述

1.1 进程与线程

在这里插入图片描述

  • 单核CPU和多核CPU的理解

    1.单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。

    2.如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)

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

  • 并行与并发

    1.并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

    2.并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

1.2 使用多线程的优点

  • 多线程程序的优点:

    1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

    2.提高计算机系统CPU的利用率

    3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和
    修改

1.3 何时需要多线程

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

2. 线程的创建和使用

2.1 线程的创建和启动

  • Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。

    注意:如下程序并不是多线程

    public class Sample {
    	public void method1(String str) {
    		System.out.println(str);
    	}
    	public void method2(String str) {
    		method1(str);
    	}
    	public static void main(String[] args) {
    		Sample s = new Sample();
    		s.method2("hello!");
    	}
    }
    

2.2 Thread类

  • Thread类的特性

    1.每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体

    2.通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

  • Thread 类的构造器

    Thread():创建新的Thread对象

    Thread(String threadname):创建线程并指定线程实例名

    Thread(Runnabletarget):指定创建线程的目标对象,它实现了Runnable接口中的run方法

    Thread(Runnable target, String name):创建新的Thread对象

  • Thread类的一些重要方法
    在这里插入图片描述
    在这里插入图片描述

2.3 API中创建线程的两种方式

**JDK1.5之前**创建新执行线程有两种方法:
   - 继承Thread类的方式
   - 实现Runnable接口的方式

2.3.1 方式一:继承Thread类的方式

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run方法。
  3. 创建Thread子类对象,即创建了线程对象
  4. 调用线程对象start方法:启动线程,调用run方法。
  • 示例

    package pers.chh3213.thread;
    
    class MyThread extends Thread{
        public MyThread(){
            super();
        }
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+" " +i);
            }
        }
    
    }
    public class TestThread{
        public static void main(String[] args) {
            //1.创建线程
            MyThread mt = new MyThread();
            //2.启动线程;并调用当前线程的run()方法
            mt.start();
    
            //如下操作仍在main线程中执行
            for (int i = 0; i <10 ; i++) {
                System.out.println("main method:"+i);
            }
    
        }
    }
    
    

    输出为:
    在这里插入图片描述

  • 启动过程
    在这里插入图片描述
    在这里插入图片描述

  • 注意点:

    1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
    2. run()方法由JVM调用,什么时候调用,执行的过程控制都由操作系统的CPU调度决定。
    3. 想要启动多线程必须调用start()方法。
    4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常IllegalThreadStateException
  • Thread类的常用方法

    • 1.start():启动当前线程,执行当前线程的run()
    • 2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    • 3.currentThread(): 静态方法,返回当前代码执行的线程
    • 4.getName():获取当前线程的名字
    • 5.setName():设置当前线程的名字
    • 6.yield():释放当前CPU的执行权
    • 7.join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
    • 8.stop():已过时。当执行此方法时,强制结束当前线程。
    • 9.sleep(long millitime):让当前线程“睡眠”指定时间的millitime毫秒)。在指定的millitime毫秒时间内,当前线程是阻塞状态的。
    • 10.isAlive():返回boolean,判断线程是否还活着
  • 示例

    class HelloThread extends Thread{
        @Override
        public void run() {
            for(int i = 0;i < 100; i++){
    
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
    //            if(i % 20 == 0){
    //                yield();
    //            }
            }
        }
    
        public HelloThread(String name){
            super(name);
        }
    }
    
    public class ThreadModeTest {
        public static void main(String[] args) {
            HelloThread h1 = new HelloThread("Thread : 1");
    
    //        h1.setName("线程一");
    
            h1.start();
    
            //给主线程命名
            Thread.currentThread().setName("主线程");
    
            for(int i = 0;i < 100; i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
    
                if(i == 20){
                    try {
                        h1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            System.out.println(h1.isAlive());
        }
    }
    
    

2.3.2 方式二:实现Runnable接口

  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread含参构造器创建线程对象。
  4. Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
  • 示例

    
    class RunnableDemo implements Runnable {
       private Thread t;
       private String threadName;
       
       RunnableDemo( String name) {
          threadName = name;
          System.out.println("Creating " +  threadName );
       }
       //子类中重写Runnable接口中的run方法
       public void run() {
          System.out.println("Running " +  threadName );
          try {
             for(int i = 4; i > 0; i--) {
                System.out.println("Thread: " + threadName + ", " + i);
                // 让线程睡眠一会
                Thread.sleep(50);
             }
          }catch (InterruptedException e) {
             System.out.println("Thread " +  threadName + " interrupted.");
          }
          System.out.println("Thread " +  threadName + " exiting.");
       }
       
       public void start () {
          System.out.println("Starting " +  threadName );
          if (t == null) {
          //通过Thread类含参构造器创建线程对象。
          //将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
             t = new Thread (this, threadName);
             t.start ();
          }
       }
    }
     
    public class TestThread {
     
       public static void main(String args[]) {
          RunnableDemo R1 = new RunnableDemo( "Thread-1");
          R1.start();
          
          RunnableDemo R2 = new RunnableDemo( "Thread-2");
          R2.start();
       }   
    }
    
    

    输出为:
    在这里插入图片描述

2.3.3 继承方式和实现方式的联系与区别

  • 区别

    继承Thread:线程代码存放在Thread子类run方法中。

    实现Runnable:线程代码存放在接口的子类的run方法。

  • 实现方式的好处

    避免了单继承的局限性

    多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

  • 练习

创建两个分线程,让其中一个线程输出1-100之间的偶数,另一个线程输出1-100之间的奇数。

  1. 继承方式

    package pers.chh3213.thread;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : pers.chh3213.thread
     * @FileName : ThreadExtend.java
     * @createTime : 2022/1/4 下午9:29
     * @Email :
     * @Description : 使用继承的方式
     */
    public class ThreadExtend {
        public static void main(String[] args) {
            thread01 t1 = new thread01();//也可以使用Thread类的匿名子类的方式
            t1.start();
            thread02 t2 = new thread02();
            t2.start();
        }
    }
    class thread01 extends Thread{
        public void run(){
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0)
                    System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }class thread02 extends Thread{
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 1)
                    System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
    
    
  2. Runnable方式

    package pers.chh3213.thread;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : pers.chh3213.thread
     * @FileName : ThreadExer.java
     * @createTime : 2022/1/4 下午9:21
     * @Email :
     * @Description :
     */
    public class ThreadExer {
        public static void main(String[] args) {
            Thread1 t1 = new Thread1();
            Thread tt1 = new Thread(t1);
            tt1.start();
            Thread2 t2 = new Thread2();
            Thread tt2 = new Thread(t2);
            tt2.start();
        }
    }
    
    class Thread1 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0)
                    System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }class Thread2 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 1)
                    System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
    
    
    
    

2.4 线程的优先级

  • 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

  • Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) ~ 10 (Thread.MAX_PRIORITY )

  • 默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)

  • 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台

  • 涉及的方法

    getPriority() :返回线程优先值

    setPriority(int newPriority):改变线程的优先级

  • 说明

    线程创建时继承父线程的优先级

    低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

2.5 线程的调度

  • 调度策略
    在这里插入图片描述

当一个线程被选择运行,它所运行的时间称之为“时间片”。

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

  • 优先级和时间片是线程的两个重要参数,分别描述了线程竞争处理器资源的能力和持有处理器时间长短的能力。

2.6 线程的分类

  • Java中的线程分为两类:一种是守护线程,一种是用户线程。

    它们几乎在每个方面都是相同的,唯一的区别是判断JVM何时离开。

    守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。

    Java垃圾回收就是一个典型的守护线程。

    JVM中都是守护线程,当前JVM将退出。

    形象理解:兔死狗烹,鸟尽弓藏

3. 线程的生命周期

  • 线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
    在这里插入图片描述

在这里插入图片描述

  • 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

    1.新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start()这个线程。

    2.就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

    3.运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。run()方法定义了线程的操作和功能。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

    4.阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    1.等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    
    2.同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    
    3.其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。
    当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
    

    5.死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

4. 线程的同步

4.1 问题的提出

多个线程执行的不确定性引起执行结果的不稳定

多个线程对账本的共享,会造成操作的不完整性会破坏数据

在这里插入图片描述

  • 示例

    模拟火车站售票程序,开启三个窗口售票。

    1.实现runnable接口方式

    package pers.chh3213.thread;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : pers.chh3213.thread
     * @FileName : TicketDemo.java
     * @createTime : 2022/1/4 下午10:27
     * @Email :
     * @Description :模拟火车站售票程序,开启三个窗口售票。
     */
    public class TicketDemo {
        public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
        }
    }
    
    class Ticket implements Runnable{
        private int tick=100;
        @Override
        public  void run(){
            while (true){
                if(tick>0){
                    System.out.println(Thread.currentThread().getName()+"售出车票,tick号为:"+tick--);
                }else break;
            }
        }
    }
    
    

    2.继承thread类方式

package pers.chh3213.thread;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread
 * @FileName : TicketDemo2.java
 * @createTime : 2022/1/4 下午10:27
 * @Email :
 * @Description :模拟火车站售票程序,开启三个窗口售票。使用继承threa类的方式
 */
public class TicketDemo2 {
    public static void main(String[] args) {
    Ticket2 t1 = new Ticket2();
    Ticket2 t2 = new Ticket2();
    Ticket2 t3 = new Ticket2();


    t1.setName("t1");
    t2.setName("t2");
    t3.setName("t3");
    t1.start();
    t2.start();
    t3.start();
    }
}

class Ticket2 extends Thread{
    private int tick=100;
    @Override
    public  void run(){
        while (true) {

                if (tick > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
                } else break;
        }
    }
}

在这里插入图片描述在这里插入图片描述

  • 多线程出现了安全问题

  • 问题的原因:
    当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
    执行完,另一个线程参与进来执行。导致共享数据的错误。

  • 解决办法
    对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
    参与执行。

4.2 Synchronized的使用方法–同步代码块和同步方法

  • Java对于多线程的安全问题提供了专业的解决方式:同步机制

方式一:同步代码块

synchronized(同步监视器){
      //需要被同步的代码
 }
  • 说明:
    1.操作共享数据的代码,即为需要被同步的代码 —>不能包含代码多了,也不能包含代码少了。
    2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
    3.同步监视器,俗称:锁。任何一个类的对象,都可以来充当锁。
  • 要求:多个线程必须要共用同一把锁。
  • 补充:在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器

方式二:同步方法

  • 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
public synchronized void show (String name){.
}
  • 好处:同步的方式,解决了线程的安全问题。
  • 缺点:操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。

4.2.1 同步代码块处理实现Runnable接口的线程安全问题

package pers.chh3213.thread;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread
 * @FileName : TicketDemo.java
 * @createTime : 2022/1/4 下午10:27
 * @Email :
 * @Description :模拟火车站售票程序,开启三个窗口售票。
 */
public class TicketDemo {
    public static void main(String[] args) {
    Ticket t = new Ticket();
    Thread t1 = new Thread(t);
    Thread t2 = new Thread(t);
    Thread t3 = new Thread(t);
    t1.setName("t1");
    t2.setName("t2");
    t3.setName("t3");
    t1.start();
    t2.start();
    t3.start();
    }
}

class Ticket implements Runnable{
    private int tick=100;
    @Override
    public  void run(){
        while (true) {
            synchronized (this) {//此时的this:唯一的Ticket 的对象
                if (tick > 0) {
//                    try {
//                        Thread.sleep(10);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }

                    System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
                } else break;
            }
        }
    }
}

4.2.2 同步代码块处理继承Thread类的线程安全问题

package pers.chh3213.thread;

import java.awt.*;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread
 * @FileName : TicketDemo2.java
 * @createTime : 2022/1/4 下午10:27
 * @Email :
 * @Description :模拟火车站售票程序,开启三个窗口售票。使用继承thread类的方式
 */
public class TicketDemo2 {
    public static void main(String[] args) {
    Ticket2 t1 = new Ticket2();
    Ticket2 t2 = new Ticket2();
    Ticket2 t3 = new Ticket2();


    t1.setName("t1");
    t2.setName("t2");
    t3.setName("t3");
    t1.start();
    t2.start();
    t3.start();
    }
}

class Ticket2 extends Thread{
    private int tick=100;
    private static Object obj = new Object();
    @Override
    public  void run(){
        while (true) {
//            synchronized (Ticket2.class) { //方式 1
            synchronized (obj) {//方式 2
//            synchronized (this) {//错误,因为此时this表示的是t1,t2,t3三个对象
                if (tick > 0) {
//                    try {
//                        Thread.sleep(10);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }

                    System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
                } else break;
            }
        }
    }
}

  • 分析同步原理
    在这里插入图片描述

4.2.3 同步方法处理实现Runnable的线程安全问题

package pers.chh3213.thread20220105;


/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread20220105
 * @ClassName : null.java.java
 * @createTime : 2022/1/5 9:35
 * @Email :
 * @Description :
 */
public class TicketSync {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Ticket implements Runnable {
    private int tick = 100;

    @Override
    public void run() {
        while (true) {
            if(tick<=0)break;
            buy();
        }
    }

    public synchronized void buy() { //同步监视器:this
//        synchronized (this){
        if (tick > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
    }
//}
}

4.2.4 同步方法处理继承Thread类的线程安全问题

package pers.chh3213.thread20220105;

import java.security.PublicKey;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread20220105
 * @ClassName : TicketSync2.java
 * @createTime : 2022/1/5 9:45
 * @Email :
 * @Description :同步方法处理继承Thread类的线程安全问题
 */
public class TicketSync2 {
    public static void main(String[] args) {
        Ticket2 t1 = new Ticket2();
        Ticket2 t2 = new Ticket2();
        Ticket2 t3 = new Ticket2();
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket2 extends Thread{
    private static int tick=100;
    private static Object obj = new Object();
    @Override
    public  void run(){
        while (true) {
            if(tick<=0)break;
            buy();
        }
    }
    public static synchronized void buy(){//synchronized 的锁为 Ticket2.class,即当前类本身
//    public  synchronized void buy(){//synchronized 的锁为 this,此种方式在 继承Thread 类的时候是错误的
            if (tick > 0) {
//                    try {
//                        Thread.sleep(10);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }

                System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
            }
    }
}

  • 关于同步方法的总结:
  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
  2. 非静态的同步方法,同步监视器是:this
    静态的同步方法,同步监视器是:当前类本身

4.3 线程安全的单例模式之懒汉式

package pers.chh3213.thread20220105;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread20220105
 * @ClassName : BankTest.java
 * @createTime : 2022/1/5 10:09
 * @Email :
 * @Description :使用同步机制将单例模式中的懒汉式改写为线程安全的
 */
public class BankTest {
    public static void main(String[] args) {
        Bank bank1 = Bank.getInstance();
        Bank bank2 = Bank.getInstance();
        System.out.println(bank1==bank2);

    }
}
class Bank{

    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance(){
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//                instance = new Bank();
//            }
//            return instance;
//        }

        //方式二:效率较高
        if(instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

4.4 同步机制中的锁

  • 同步锁机制:
    在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

  • synchronized的锁是什么?

    1.任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
    2.同步方法的锁:静态方法(类名.class)、非静态方法(this
    3.同步代码块:自己指定,很多时候也是指定为this类名.class

  • 注意
    1.必须确保使用同一个资源的多个线程共用一把锁,否则就无法保证共享资源的安全。
    2.一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块指定需谨慎

4.5 同步的范围

  • 如何找问题,即代码是否存在线程安全?(非常重要)
    (1)明确哪些代码是多线程运行的代码
    (2)明确多个线程是否有共享数据
    (3)明确多线程运行代码中是否有多条语句操作共享数据
  • 如何解决?(非常重要)
    对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
    即所有操作共享数据的这些语句都要放在同步范围中
  • 切记:
    范围太小:没锁住所有有安全问题的代码
    范围太大:没发挥多线程的功能。

4.6 释放vs.不会释放锁的操作

  • 释放锁的操作
    1.当前线程的同步方法、同步代码块执行结束。
    2.当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
    3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
    4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

  • 不会释放锁的操作
    1.线程执行同步代码块或同步方法时,程序调Thread.sleep()Thread.yield()方法暂停当前线程的执行
    2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
    应尽量避免使用suspend()resume()来控制线程

4.7 线程的死锁问题

  • 死锁
    1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
    2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

  • 解决方法
    1.专门的算法、原则
    2.尽量减少同步资源的定义
    3.尽量避免嵌套同步

  • 示例1

package pers.chh3213.thread20220105;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread20220105
 * @ClassName : ThreadTest.java
 * @createTime : 2022/1/5 11:00
 * @Email :
 * @Description : 演示线程的死锁
 */
public class ThreadTest {
    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(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(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);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

  • 示例2
package pers.chh3213.thread20220105;

class A {
    public synchronized void foo(B b) {
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 进入了A实例的foo方法"); // ①
        try {
            Thread.sleep(200);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 企图调用B实例的last方法"); // ③
        b.last();
    }

    public synchronized void last() {
        System.out.println("进入了A类的last方法内部");
    }
}

class B {
    public synchronized void bar(A a) {
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 进入了B实例的bar方法"); // ②
        try {
            Thread.sleep(200);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("当前线程名: " + Thread.currentThread().getName()
                + " 企图调用A实例的last方法"); // ④
        a.last();
    }

    public synchronized void last() {
        System.out.println("进入了B类的last方法内部");
    }
}

public class DeadLock implements Runnable {
    A a = new A();
    B b = new B();

    public void init() {
        Thread.currentThread().setName("主线程");
        // 调用a对象的foo方法
        a.foo(b);
        System.out.println("进入了主线程之后");
    }

    public void run() {
        Thread.currentThread().setName("副线程");
        // 调用b对象的bar方法
        b.bar(a);
        System.out.println("进入了副线程之后");
    }

    public static void main(String[] args) {
        DeadLock dl = new DeadLock();
        new Thread(dl).start();
        dl.init();
    }
}


4.8 Lock(锁)

  • JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock 类实现了Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
    在这里插入图片描述
  • 示例
package pers.chh3213.thread20220105;

/**
 * Created with IntelliJ IDEA.
 * 
 * @Description :解决线程安全问题的方式三:lock锁---》JDK5.0新增
 */
import java.util.concurrent.locks.ReentrantLock;


class Windows implements Runnable{

    private int tick = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();


    @Override
    public void run() {
        while(true){
            //调用锁定方法:lock()
            lock.lock();
            try{
                if(tick > 0){

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
                }else{
                    break;
                }
            }finally {
                //3.调用解锁方法:unlock()
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Windows w = new Windows();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

4.9 synchronized 与 Lock 的对比

1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)

4.10 练习

在这里插入图片描述

package pers.chh3213.thread20220105;

import java.util.concurrent.locks.ReentrantLock;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread20220105
 * @ClassName : BankAccountTest.java
 * @createTime : 2022/1/5 13:04
 * @Email :
 * @Description :使用lock的方式
 */
public class BankAccountTest {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(0);
        Customer customer = new Customer(bankAccount);
        Thread t1 = new Thread(customer);
        Thread t2 = new Thread(customer);
        t1.setName("账户1");
        t2.setName("账户2");
        t1.start();
        t2.start();
    }
}

/**
 * lock形式
 */
/*class BankAccount{
    private int money;
    private ReentrantLock lock = new ReentrantLock();
    BankAccount(int money){
        this.money= money;
    }
    public void deposit(int mon){
        lock.lock();
        try {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.money=this.money+mon;
            System.out.println(Thread.currentThread().getName()+"当前账户余额:" + money);
        } finally {
            lock.unlock();
        }
    }
}*/
/**
 * 同步代码块
 */
class BankAccount{
    private int money ;
    BankAccount(int money){
        this.money=money;
    }
    public void deposit(int mon){
        synchronized (this){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money+=mon;
            System.out.println(Thread.currentThread().getName()+"当前账户余额:" + money);
        }
    }
}


/**
 * 同步方法:
 */
/*class BankAccount{
    private int money ;
    BankAccount(int money){
        this.money=money;
    }
    public synchronized void deposit(int mon){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        money+=mon;
        System.out.println(Thread.currentThread().getName()+"当前账户余额:" + money);

    }
}*/
class Customer implements Runnable{
    private BankAccount bankAccount;
    Customer(BankAccount bankAccount){
        this.bankAccount = bankAccount;
    }

    @Override
    public void run() {
        int i=0;
        while (i<3) {
            bankAccount.deposit(1000);
            i++;
        }
    }

}


5. 线程的通信

5.1 问题提出

使用两个线程打印 1-100。线程1, 线程2 交替打印

package pers.chh3213.communication20220106;


/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.communication20220106
 * @FileName : CommunicationTest.java
 * @createTime : 2022/1/6 上午8:45
 * @Email :
 * @Description :
 */
public class CommunicationTest {
    public static void main(String[] args) {
    Communication communication = new Communication();
    Thread t1 = new Thread(communication);
    Thread t2 = new Thread(communication);
    t1.start();
    t2.start();
    }
}

class Communication implements Runnable{
    int i =1;
    @Override
    public void run(){
        while (true){
            synchronized(this){

                notify();
                if(i<=100){
                    System.out.println(Thread.currentThread().getName() +" : " +i++);
                }else {
                    break;
                }

                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

5.2 wait() 与 notify() 和 notifyAll()

1.wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

2.notify():唤醒正在排队等待同步资源的线程中优先级最高者,结束等待

3.notifyAll ():唤醒正在排队等待资源的所有线程,结束等待.

说明
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块同步方法中
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。 否则,会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

5.2.1 wait()方法

  • 在当前线程中调用方法:对象名.wait()
  • 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify(或notifyAll) 为止。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
  • 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
  • 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

5.2.2 notify()/notifyAll()

  • 在当前线程中调用方法: 对象名.notify()
  • 功能:唤醒等待该对象监控权的一个/所有线程
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

5.2.3 面试题:sleep() 和 wait()的异同?

  1. 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
  2. 不同点:
    1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
    2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
    3)关于是否释放同步监视器:如果两个方法都使用在同步代码块同步方法中,sleep()不会释放锁,wait()会释放锁。

5.3 经典例题:生产者/消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

这里可能出现两个问题:

生产者比消费者快时,消费者会漏掉一些数据没有取到。 消费者比生产者快时,消费者会取相同的数据。

  • 分析:
    1.是否是多线程的问题?是,生产者的线程,消费者的线程
    2.是否有共享数据的问题?是,店员、产品、产品数
    3.如何解决线程的安全问题?同步机制,有三种方法
    4.是否涉及线程的通信?是
package pers.chh3213.communication20220106;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.communication20220106
 * @FileName : ProductTest.java
 * @createTime : 2022/1/6 上午9:15
 * @Email :
 * @Description :经典例题:生产者/消费者问题
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk(0);
        Customer customer1 =new Customer(clerk);
        Customer customer2 =new Customer(clerk);
        Productor productor = new Productor(clerk);
        Thread thread1 = new Thread(customer1);
        Thread thread3 = new Thread(customer2);
        Thread thread2 = new Thread(productor);
        thread2.setName("生产者");
        thread1.setName("消费者1");
        thread3.setName("消费者2");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Clerk{
    private int product = 0;
    Clerk(int product){
        this.product=product;
    }
    public synchronized void toProduct(){
        if(product>=20){
            try{
                wait();
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        else{
            product++;
            System.out.println(Thread.currentThread().getName()+"生产第"+product+"个产品");
            notifyAll();
        }
    }
    public synchronized void getProduct(){
        if(product<=0){
            try {
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        else{
            product--;
            System.out.println(Thread.currentThread().getName()+"拿走了第"+product+"个产品");
            notifyAll();
        }
    }
}


class  Productor implements Runnable{
    private Clerk clerk;
    Productor(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"开始生产产品");
        while (true) {
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            clerk.toProduct();
        }

    }
}class  Customer implements Runnable{
    private Clerk clerk;
    Customer(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"开始取走产品");
        while (true) {
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            clerk.getProduct();
        }
    }

}

  • 练习

模拟银行取钱的问题

  1. 定义一个Account类
    1)该Account类封装了账户编号(String)和余额(double)两个属性
    2)设置相应属性的getter和setter方法
    3)提供无参和有两个参数的构造器
    4)系统根据账号判断与用户是否匹配,需提供hashCode()和equals()方法的重写
  2. 提供两个取钱的线程类:小明、小明’s wife
    1)提供了Account类的account属性和double类的取款额的属性
    2)提供带线程名的构造器
    3)run()方法中提供取钱的操作
  3. 在主类中创建线程进行测试。考虑线程安全问题。
package pers.chh3213.communication20220106;

/**
 * Created with IntelliJ IDEA.
 *
 * @author : chh3213
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.communication20220106
 * @FileName : AccountTest.java
 * @createTime : 2022/1/6 上午9:46
 * @Email :
 * @Description :模拟银行取钱的问题
 */
class Account {
    private String name;
    private double balance;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    Account(){

    }
    Account(String name,double balance){
        this.name = name;
        this.balance = balance;
    }
    public String toString(){
        return "Account[name="+name+",balance="+balance+"]";
    }
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 :
                name.hashCode());
        long temp;
        temp = Double.doubleToLongBits(balance);
        result = prime * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Account other = (Account) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (Double.doubleToLongBits(balance) !=
                Double.doubleToLongBits(other.balance))
            return false;
        return true;
    }
}


class XiaoMing implements Runnable{
    private Account account;
    private double withdraw;
    XiaoMing(Account account,double amt){
        this.account =account;
        this.withdraw = amt;
    }
    @Override
    public  void run(){
        synchronized (account) {//锁别加错
            if (account.getBalance() > withdraw) {
                System.out.println(Thread.currentThread().getName() + "取款成功,金额为:+" + withdraw);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance() - withdraw);
            } else {
                System.out.println("取款失败,当前余额为:" + account.getBalance());
            }
        }
    }
}
public  class AccountTest {
    public static void main(String[] args) {
        Account account = new Account("1111",10000);
        XiaoMing xiaoMing = new XiaoMing(account,8000);
        XiaoMing xiaoMingWife = new XiaoMing(account,8000);
        Thread t1 = new Thread(xiaoMing);
        Thread t2 = new Thread(xiaoMingWife);
        t1.setName("小明");
        t2.setName("小明's wife");
        t1.start();
        t2.start();
    }
}

注:因为需要提供带线程名的构造器,所以这里应该以继承Thread类的方式更合适

class WithDrawThread extends Thread {
    Account account;
    // 要取款的额度
    double withDraw;
    public WithDrawThread(String name, Account account, double amt) {
        super(name);
        this.account = account;
        this.withDraw = amt;
    }
    public void run() {
        synchronized (account) {
            if (account.getBalance() > withDraw) {
                System.out.println(Thread.currentThread().getName() + ":取款成功,取现的金额为:" + withDraw);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance() - withDraw);
            } else {
                System.out.println("取现额度超过账户余额,取款失败");
            }
            System.out.println("现在账户的余额为:" + account.getBalance());
        }
    }
}

6. JDK5.0新增线程创建方式

6.1 新增方式一:实现Callable接口

  • 与使用Runnable相比, Callable功能更强大些
     相比run()方法,可以有返回值
     方法可以抛出异常
     支持泛型的返回值
     需要借助FutureTask类,比如获取返回结果

  • 示例

package pers.chh3213.thread_new_add20220106;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


/**
 * Created with IntelliJ IDEA.
 * @version : 1.0
 * @Project : Multi_Thread
 * @Package : pers.chh3213.thread_new_add20220106
 * @FileName : ThreadNew.java
 * @createTime : 2022/1/6 上午10:24
 * @Email :
 * @Description :
 */

//1.创建一个实现Callable的实现类
class NumThread implements Callable {

    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();

        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);

        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}


  • Future接口
    1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
    2.FutrueTask是Futrue接口的唯一的实现类
    3.FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

6.2 新增方式二:使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:
    1.提高响应速度(减少了创建新线程的时间)
    2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3.便于线程管理:
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API

JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors

  1. ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
    void shutdown():关闭连接池

  2. Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

    Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    package pers.chh3213.thread_new_add20220106;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    /**
     * Created with IntelliJ IDEA.
     *
     * @Project : Multi_Thread
     * @Package : pers.chh3213.thread_new_add20220106
     * @FileName : ThreadPool.java
     * @createTime : 2022/1/6 上午10:58
     * @Email :
    */
    
    class NumberThread implements Runnable{
        @Override
        public void run() {
            for(int i = 0;i <= 100;i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }
    }
    
    class NumberThread1 implements Runnable{
        @Override
        public void run() {
            for(int i = 0;i <= 100;i++){
                if(i % 2 != 0){
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }
    }
    
    public class ThreadPool {
        public static void main(String[] args) {
    
            //1. 提供指定线程数量的线程池
            ExecutorService service = Executors.newFixedThreadPool(10);
            ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//向下转型
            //设置线程池的属性
    //        System.out.println(service.getClass());
    //        service1.setCorePoolSize(15);
    //        service1.setKeepAliveTime();
    
            //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
            service.execute(new NumberThread());  //适合适用于Runable
            service.execute(new NumberThread1());  //适合适用于Runable
    
    //        service.submit(Callable callable);   //适合适用于Callable
    
            //3.关闭连接池
            service.shutdown();
        }
    }
    
    
  • 面试题:创建多线程有几种方式?四种!
    实现runnable接口、继承Thread类、实现callable接口、使用线程池

7. 练习

  1. stop()和 suspend()方法为何不推荐使用?

    反对使用 stop(),是因为它不安全。它 会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。 suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来, 但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同 时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(), 而应在自己的 Thread 类中置入一个标志, 指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等 待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。

  2. sleep() 和 wait() 有什么区别?

    答:sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep不会释放对象锁。wait 是 Object 类的方法,对此对象调用 wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

  3. 同步和异步有何异同,在什么情况下分别使用他们?举例说明。

    答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据, 必须进行同步存取。
    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往 更有效率

  4. 启动一个线程是用 run()还是 start()?

    答:启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状 态,这意味着它可以由 JVM调度并执行。这并不意味着线程就会立即运行。 run() 方法就是正常的对象调用方法的执行,并不是使用分线程来执行的

  5. 当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其它方法?

    答:不能,一个对象的一个 synchronized 方法只能由一个线程访问。

  6. 简述synchronized和java.util.concurrent.locks.Lock 的异同 ?

    答:主要相同点:Lock 能完成 synchronized 所实现的所有功能
    主 要 不 同 点 : Lock 有 比 synchronized 更 精 确 的 线 程 语 义 和 更 好 的 性 能 。
    synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在finally 从句中释放。

  7. Java 为什么要引入线程机制,线程、程序、进程之间的关系是怎样的。

    答:线程可以彼此独立的执行,它是一种实现并发机制的有效手段,可以同时使用多个线程
    来完成不同的任务,并且一般用户在使用多线程时并不考虑底层处理的细节。
    程序是一段静态的代码,是软件执行的蓝本。进程是程序的一次动态执行过程,即是处于运 行过程中的程序。
    线程是比进程更小的程序执行单位,一个进程可以启动多个线程同时运行,不同线程之间可以共享相同的内存区域和数据。多线程程序是运行时间后可能出现在一个进程之内的、有 一个以上线程同时运行的情况的程序。

  8. Runnable 接口包括哪些抽象方法?Thread 类有哪些主要域和方法?

    答:Runnable 接口中仅有 run()抽象方法。 Thread类主要有:MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY。 主要方法start(),run(),sleep(),currentThread(),setPriority(),getPriority(),join()等。

  9. 编写一个继承 Thread 类的方式实现多线程的程序。该类 MyThread 有两个属性,一个字符
    串 WhoAmI 代表线程名,一个整数 delay 代表该线程随机要休眠的时间。构造有参的构造器,线程执行时,显示线程名和要休眠时间。另外,定义一个测试类 TestThread,创建三个线程对象以展示执行情况。

    package pers.chh3213.threadExecsise20220106;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : pers.chh3213.threadExecsise20220106
     * @FileName : TestThread.java
     * @createTime : 2022/1/6 下午5:15
     * @Email :
     * @Description :
     */
    public class TestThread {
        public static void main(String[] args) {
            MyThread myThread1 = new MyThread("t1",10);
            MyThread myThread2 = new MyThread("t2",10);
            MyThread myThread3 = new MyThread("t3",10);
            myThread1.start();
            myThread2.start();
            myThread3.start();
        }
    }
    
    class MyThread extends Thread{
        private String WhoAmI;
        private int delay;
        MyThread(String whoAmI, int delay){
            this.WhoAmI = whoAmI;
            this.delay = delay;
        }
        public void run(){
            synchronized (MyThread.class){
                try{
                    MyThread.sleep(delay);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(WhoAmI+" 休眠时间:" +delay);
            }
        }
    
    }
    
    
  10. 利用多线程设计一个程序,同时输出 50 以内的奇数和偶数,以及当前运行的线程名。

    package pers.chh3213.threadExecsise20220106;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : pers.chh3213.threadExecsise20220106
     * @FileName : ThreadPrint.java
     * @createTime : 2022/1/6 下午8:14
     * @Email :
     * @Description :
     */
    public class ThreadPrint extends Thread {
        int k = 1;
        public void run() {
            int i = k;
                while (i < 50) {
                    System.out.println(Thread.currentThread().getName() + "-----" + i);
                    i += 2;
                }
                System.out.println(Thread.currentThread().getName() + " end!");
        }
        public static void main(String[] args) {
            ThreadPrint t1 = new ThreadPrint();
            ThreadPrint t2 = new ThreadPrint();
            t1.k = 1;
            t2.k = 2;
            t1.start();
            t2.start();
        }
    }
    
    
  11. 实现一个由 A、B、C 三个窗口同时销售 100 张票的系统,要求打印出每个窗口打印的售票情况,并且每个窗口不得连续售票。

    package src.pers.chh3213.threadExecsise20220107;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : pers.chh3213.threadExecsise20220107
     * @FileName : TicketSellTest.java
     * @createTime : 2022/1/7 上午8:52
     * @Email :
     * @Description :实现一个由 A、B、C 三个窗口同时销售 100 张票的系统,
     * 要求打印出每个窗口打印的售票情况,并且每个窗口不得连
     * 续售票。
     */
    public class TicketSellTest {
        public static void main(String[] args) {
        	TicketSystem t = new TicketSystem();
        	Thread t1 = new Thread(t);
        	Thread t2 = new Thread(t);
        	Thread t3 = new Thread(t);
        	t1.setName("A");
        	t2.setName("B");
    		t3.setName("C");
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    
    class TicketSystem implements Runnable{
    	private int ticket = 100;
    	public void run(){
    			while(ticket>0){
    				synchronized(this){
    					notify();
    				ticket--;
    				System.out.println(Thread.currentThread().getName()+" 售卖第 " + ticket + " 张票");
    				try{
    					wait();
    				}catch(InterruptedException e){
    					System.out.println("出现异常");
    
    				}
    			}
    		}
    	}
    }
    
    
  12. 模拟 3 个人排队买票,每人买 1 张票。售货员只有 1 张五元
    的钱,电影票 5 元一张,王大拿拿一张二十元的人民币排在
    谢大脚前面买票,谢大脚拿 1 张十元的人民币排在在赵四的
    前面买票,赵四拿 1 张五元的人民币排在最后。即最终的卖
    票次序是:谢大脚、赵四、王大拿

    package src.pers.chh3213.threadExecsise20220107;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @Project : Multi_Thread
     * @Package : src.pers.chh3213.threadExecsise20220107
     * @FileName : TicketBuyTest.java
     * @createTime : 2022/1/7 上午9:24
     * @Email :
     * @Description :模拟 3 个人排队买票,每人买 1 张票。售货员只有 1 张五元的钱,
     * 电影票 5 元一张,王大拿拿一张二十元的人民币排在
     * 谢大脚前面买票,谢大脚拿 1 张十元的人民币排在在赵四的
     * 前面买票,赵四拿 1 张五元的人民币排在最后。即最终的卖
     * 票次序是:谢大脚、赵四、王大拿
     */
    public class TicketBuyTest {
        public static void main(String[] args) {
            Seller seller = new Seller();
            Man person1 = new Man("王大拿",20,seller);
            Man person2 = new Man("谢大脚",10,seller);
            Man person3 = new Man("赵四",5,seller);
            person1.start();
            person2.start();
            person3.start();
    
        }
    }
    
    class Man extends Thread{
        private String name;
        private int money;
        private static Seller seller;//用static,确保每个人的锁是一致的,且只有一把锁
        private boolean got_ticket=false;
        public Man(String name,int money, Seller seller){
            this.name = name;
            this.money = money;
            this.seller = seller;
        }
        @Override
        public void run(){
            while(!got_ticket){
                Thread.currentThread().setName(name);
                synchronized (seller){
                    if(seller.sellTicket(money)){
                        try {
                            wait();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"买到一张票");
                        got_ticket=true;
                    }
    
                }
            }
        }
    
    
    }
    class Seller{
        private int money = 5;
        private  final int TICKET_PRICE = 5;
        public boolean sellTicket(int customer_money){
            if(money>=(customer_money-TICKET_PRICE)){
                money += TICKET_PRICE;
                System.out.println("卖票!!");
                return true;
            }
            else{
                return false;
            }
        }
    }
    
  13. 编写生产者消费者多线程程序,设有一个最大库存量为 4 的电视机仓库,生产 10 台电视机,一边生产一边销售(消费)。

    1)启动两个线程对一个数字 i 操作(10 分)
    2)其中 1 个线程每次对 i 加 1(10 分)
    3)另 1 个线程每次对 i减一(10 分)
    4)各运行 20 次,结果 i 的值等于初始值。 (20 分)

    package src.pers.chh3213.threadExecsise20220107;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : src.pers.chh3213.threadExecsise20220107
     * @FileName : ProductConsumerTest.java
     * @createTime : 2022/1/7 上午11:17
     * @Email :
     * @Description : 编写生产者消费者多线程程序,设有一个最大库存量为 4 的电视机仓库,生产 10 台电视机,一边生产一边销售(消费)。
     */
    public class ProductConsumerTest {
        public static void main(String[] args) {
            Warehouse warehouse = new Warehouse(4);
            Product product = new Product(warehouse);
            Sell sell = new Sell(warehouse);
            product.start();
            sell.start();
        }
    }
    
    class Warehouse{
        private int  TV_number;
        public Warehouse(int TV_number){
            this.TV_number = TV_number;
        }
    
        public int getTV_number() {
            return TV_number;
        }
    
        public synchronized void toSell(){
            if(TV_number<=0){
                try {
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            notifyAll();
            TV_number--;
            System.out.println("开始销售,当前TV数量:"+TV_number);
        }
        public synchronized void toProduce(){
            if(TV_number>=4){
                try {
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            notifyAll();
            TV_number++;
            System.out.println("开始生产,当前TV数量:"+TV_number);
        }
    
    }
    class Product extends Thread{
        private  static Warehouse warehouse;
        public Product(Warehouse warehouse){
            this.warehouse = warehouse;
        }
        @Override
        public void run(){
            int i=1;
            while (i<=20){
                        warehouse.toProduce();
                        i++;
                        try {
                            Thread.sleep(10);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
    
            }
    }
    
    class Sell extends Thread{
        private static Warehouse warehouse;
        public Sell(Warehouse warehouse){
            this.warehouse = warehouse;
        }
        @Override
        public void run(){
            int i=1;
            while (i<=20){
    
                    warehouse.toSell();
                    i++;
                    try {
                        Thread.sleep(10);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
    
                }
            }
    }
    
  14. 模拟一个人生产 50 个玩具,每 200 毫秒生产一个,当生产到第 20 个时加入每秒吃 1 个馒头,共吃完 3 个后在接着生产的多线程。

    package src.pers.chh3213.threadExecsise20220107;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : src.pers.chh3213.threadExecsise20220107
     * @FileName : EatTest.java
     * @createTime : 2022/1/7 下午5:04
     * @Email :
     * @Description :
     */
    public class EatTest {
        public static void main(String[] args) {
            Somebody somebody = new Somebody(0,0);
            Eat eat = new Eat(somebody);
            MakeToy makeToy = new MakeToy(somebody);
    
            Thread t1 = new Thread(eat);
            Thread t2 = new Thread(makeToy);
            t2.start();
            t1.start();
    
        }
    }
    
    class Somebody {
        private int toy_number ;
        private int eat_mantou_number ;
        private boolean eat_flag = false;
        public Somebody(int toy_number,int eat_mantou_number ){
            this.toy_number = toy_number;
            this.eat_mantou_number = eat_mantou_number;
    
        }
        public synchronized void makeToy(){
            while (toy_number<50) {
    
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                toy_number++;
                System.out.println("当前共生产玩具:" + toy_number + "个");
    
                if(toy_number==20){
                    eat_flag = true;
                    try {
                        wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
    
                }
            }
        }
    
        public synchronized void eat(){
            if(eat_flag) {
                while (eat_mantou_number < 3) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    eat_mantou_number++;
                    System.out.println("吃第" + eat_mantou_number + "个馒头");
                }
                notify();
    //            eat_flag=false;
            }
    
        }
    
    }
    
    
    class Eat implements Runnable{
        private Somebody somebody;
        public Eat(Somebody somebody){
            this.somebody = somebody;
        }
        @Override
        public void run(){
            somebody.eat();
        }
    }class MakeToy implements Runnable{
        private Somebody somebody;
        public MakeToy(Somebody somebody){
            this.somebody = somebody;
        }
        @Override
        public void run(){
            somebody.makeToy();
        }
    }
    
    
    
    
  15. 编写龟兔赛跑多线程程序,设赛跑长度为 100 米,每跑完10 米输出一次结果。

    package src.pers.chh3213.threadExecsise20220108;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @author : chh3213
     * @version : 1.0
     * @Project : Multi_Thread
     * @Package : src.pers.chh3213.threadExecsise20220108
     * @FileName : TortoiseHareTest.java
     * @createTime : 2022/1/8 上午8:56
     * @Email :
     * @Description :编写龟兔赛跑多线程程序,设赛跑长度为 100 米,每跑完10 米输出一次结果。
     */
    public class TortoiseHareTest {
        public static void main(String[] args) {
            Animal tortoise = new Animal("tortoise",5);
            Animal hare = new Animal("hare",10);
            Thread t1 = new Thread(tortoise);
            Thread t2 = new Thread(hare);
            t1.start();
            t2.start();
        }
    }
    
    class Animal implements Runnable{
        private String name;
        private int speed;
        private int i=1;
        public Animal(String name,int speed ){
            this.name = name;
            this.speed=speed;
        }
        public synchronized void Begin(){
            Thread.currentThread().setName(name);
            while(i*speed<=100) {
                this.notify();
                try {
                    Thread.sleep((int) (1000 / speed));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if ((i * speed) % 10 == 0) {
                    System.out.println(Thread.currentThread().getName()+"已跑完:" + i * speed + "米");
                }
                i++;
    //            try {
    //                this.wait();
    //            }catch (InterruptedException e){
    //                e.printStackTrace();
    //            }
            }
    
        }
        public void run(){
            Begin();
        }
    
    }
    
  16. 改进上题的龟兔赛跑程序,通过改变优先级,并减掉休眠时间,使得乌龟以迅雷不及掩耳的速度跑完 100 米。

package src.pers.chh3213.threadExecsise20220108.newTortoiseHare;

	/**
	 * Created with IntelliJ IDEA.
	 *
	 * @author : chh3213
	 * @version : 1.0
	 * @Project : Multi_Thread
	 * @Package : src.pers.chh3213.threadExecsise20220108
	 * @FileName : TortoiseHareTest.java
	 * @createTime : 2022/1/8 上午8:56
	 * @Email :
	 * @Description :编写龟兔赛跑多线程程序,设赛跑长度为 100 米,每跑完10 米输出一次结果。
	 * 改进上题的龟兔赛跑程序,通过改变优先级,并减掉休眠时间,使得乌龟以迅雷不及掩耳的速度跑完 100 米。
	 *
	 */
	public class TortoiseHareTest {
	    public static void main(String[] args) {
	        Animal tortoise = new Animal("tortoise",5);
	        Animal hare = new Animal("hare",10);
	        Thread t1 = new Thread(tortoise);
	        Thread t2 = new Thread(hare);
	        t1.setPriority(1);
	        t2.setPriority(10);
	        t1.start();
	        t2.start();
	    }
	}
	
	class Animal implements Runnable{
	    private String name;
	    private int speed;
	    private int i=1;
	    public Animal(String name,int speed ){
	        this.name = name;
	        this.speed=speed;
	    }
	    public synchronized void Begin(){
	        Thread.currentThread().setName(name);
	        while(i*speed<=100) {
	//            this.notify();
	            if((i * speed) % 10 == 0) {
	                try {
	                    Thread.sleep((int) (10));
	                } catch (InterruptedException e) {
	                    e.printStackTrace();
	                }
	            }
	            if(Thread.currentThread().getName().equals("hare")&&(i * speed) % 10 == 0){
	                try {
	                    Thread.sleep((int) (1));
	                } catch (InterruptedException e) {
	                    e.printStackTrace();
	                }
	                System.out.println("=======================");
	                System.out.println("Hare have a rest");
	                System.out.println("------------------------");
	            }
	                System.out.println(Thread.currentThread().getName() + "已跑完:" + i * speed + "米");
	            i++;
	//            try {
	//                this.wait();
	//            }catch (InterruptedException e){
	//                e.printStackTrace();
	//            }
	        }
	
	    }
	    public void run(){
	        Begin();
	    }
	
	}
	```

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CHH3213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值