精华内容
下载资源
问答
  • 万字图解Java多线程

    万次阅读 多人点赞 2020-09-06 14:45:07
    java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等,...

    前言

    授权Java面试者精选独家原创发布

    java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等,本篇文章将使用实例+图解+源码的方式来解析java多线程。

    文章篇幅较长,大家也可以有选择的看具体章节,建议多线程的代码全部手敲,永远不要相信你看到的结论,自己编码后运行出来的,才是自己的。

    什么是java多线程?

    进程与线程

    进程

    • 当一个程序被运行,就开启了一个进程, 比如启动了qq,word
    • 程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备

    线程

    • 一个进程内可分为多个线程
    • 一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令

    并行与并发

    并发:单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu

    并行:多核cpu运行 多线程时,真正的在同一时刻运行

    java提供了丰富的api来支持多线程。

    为什么用多线程?

    多线程能实现的都可以用单线程来完成,那单线程运行的好好的,为什么java要引入多线程的概念呢?

    多线程的好处:

    1. 程序运行的更快!快!快!

    2. 充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力

    多线程难在哪里?

    单线程只有一条执行线,过程容易理解,可以在大脑中清晰的勾勒出代码的执行流程

    多线程却是多条线,而且一般多条线之间有交互,多条线之间需要通信,一般难点有以下几点

    1. 多线程的执行结果不确定,受到cpu调度的影响
    2. 多线程的安全问题
    3. 线程资源宝贵,依赖线程池操作线程,线程池的参数设置问题
    4. 多线程执行是动态的,同时的,难以追踪过程
    5. 多线程的底层是操作系统层面的,源码难度大

    有时候希望自己变成一个字节穿梭于服务器中,搞清楚来龙去脉,就像无敌破坏王一样(没看过这部电影的可以看下,脑洞大开)。

    java多线程的基本使用

    定义任务、创建和运行线程

    任务: 线程的执行体。也就是我们的核心代码逻辑

    定义任务

    1. 继承Thread类 (可以说是 将任务和线程合并在一起)
    2. 实现Runnable接口 (可以说是 将任务和线程分开了)
    3. 实现Callable接口 (利用FutureTask执行任务)

    Thread实现任务的局限性

    1. 任务逻辑写在Thread类的run方法中,有单继承的局限性
    2. 创建多线程时,每个任务有成员变量时不共享,必须加static才能做到共享

    Runnable和Callable解决了Thread的局限性

    但是Runbale相比Callable有以下的局限性

    1. 任务没有返回值
    2. 任务无法抛异常给调用方

    如下代码 几种定义线程的方式

    @Slf4j
    class T extends Thread {
        @Override
        public void run() {
            log.info("我是继承Thread的任务");
        }
    }
    @Slf4j
    class R implements Runnable {
    
        @Override
        public void run() {
            log.info("我是实现Runnable的任务");
        }
    }
    @Slf4j
    class C implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            log.info("我是实现Callable的任务");
            return "success";
        }
    }
    

    创建线程的方式

    1. 通过Thread类直接创建线程
    2. 利用线程池内部创建线程

    启动线程的方式

    • 调用线程的start()方法
    // 启动继承Thread类的任务
    new T().start();
    
    // 启动继承Thread匿名内部类的任务 可用lambda优化
    Thread t = new Thread(){
      @Override
      public void run() {
        log.info("我是Thread匿名内部类的任务");
      }
    };
    
    //  启动实现Runnable接口的任务
    new Thread(new R()).start();
    
    //  启动实现Runnable匿名实现类的任务
    new Thread(new Runnable() {
        @Override
        public void run() {
            log.info("我是Runnable匿名内部类的任务");
        }
    }).start();
    
    //  启动实现Runnable的lambda简化后的任务
    new Thread(() -> log.info("我是Runnable的lambda简化后的任务")).start();
    
    // 启动实现了Callable接口的任务 结合FutureTask 可以获取线程执行的结果
    FutureTask<String> target = new FutureTask<>(new C());
    new Thread(target).start();
    log.info(target.get());
    
    

    以上各个线程相关的类的类图如下

    上下文切换

    多核cpu下,多线程是并行工作的,如果线程数多,单个核又会并发的调度线程,运行时会有上下文切换的概念

    cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。

    1. 线程的cpu时间片用完
    2. 垃圾回收
    3. 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

    当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。

    idea打断点的时候可以设置为Thread模式,idea的debug模式可以看出栈帧的变化

    线程的礼让-yield()&线程的优先级

    yield()方法会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配。

    代码如下

    // 方法的定义
    public static native void yield();
    
    Runnable r1 = () -> {
        int count = 0;
        for (;;){
           log.info("---- 1>" + count++);
        }
    };
    Runnable r2 = () -> {
        int count = 0;
        for (;;){
            Thread.yield();
            log.info("            ---- 2>" + count++);
        }
    };
    Thread t1 = new Thread(r1,"t1");
    Thread t2 = new Thread(r2,"t2");
    t1.start();
    t2.start();
    
    // 运行结果
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129504
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129505
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129506
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129507
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129508
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129509
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129510
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129511
    11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129512
    11:49:15.798 [t2] INFO thread.TestYield -             ---- 2>293
    11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129513
    11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129514
    11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129515
    11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129516
    11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129517
    11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129518
    

    如上述结果所示,t2线程每次执行时进行了yield(),线程1执行的机会明显比线程2要多。

    线程的优先级

    ​ 线程内部用1~10的数来调整线程的优先级,默认的线程优先级为NORM_PRIORITY:5

    ​ cpu比较忙时,优先级高的线程获取更多的时间片

    ​ cpu比较闲时,优先级设置基本没用

     public final static int MIN_PRIORITY = 1;
    
     public final static int NORM_PRIORITY = 5;
    
     public final static int MAX_PRIORITY = 10;
     
     // 方法的定义
     public final void setPriority(int newPriority) {
     }
    

    cpu比较忙时

    Runnable r1 = () -> {
        int count = 0;
        for (;;){
           log.info("---- 1>" + count++);
        }
    };
    Runnable r2 = () -> {
        int count = 0;
        for (;;){
            log.info("            ---- 2>" + count++);
        }
    };
    Thread t1 = new Thread(r1,"t1");
    Thread t2 = new Thread(r2,"t2");
    t1.setPriority(Thread.NORM_PRIORITY);
    t2.setPriority(Thread.MAX_PRIORITY);
    t1.start();
    t2.start();
    
    // 可能的运行结果
    11:59:00.696 [t1] INFO thread.TestYieldPriority - ---- 1>44102
    11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135903
    11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135904
    11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135905
    11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135906
    

    cpu比较闲时

    Runnable r1 = () -> {
        int count = 0;
        for (int i = 0; i < 10; i++) {
            log.info("---- 1>" + count++);
        }
    };
    Runnable r2 = () -> {
        int count = 0;
        for (int i = 0; i < 10; i++) {
            log.info("            ---- 2>" + count++);
    
        }
    };
    Thread t1 = new Thread(r1,"t1");
    Thread t2 = new Thread(r2,"t2");
    t1.setPriority(Thread.MIN_PRIORITY);
    t2.setPriority(Thread.MAX_PRIORITY);
    t1.start();
    t2.start();
    
    // 可能的运行结果 线程1优先级低 却先运行完
    12:01:09.916 [t1] INFO thread.TestYieldPriority - ---- 1>7
    12:01:09.916 [t1] INFO thread.TestYieldPriority - ---- 1>8
    12:01:09.916 [t1] INFO thread.TestYieldPriority - ---- 1>9
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>2
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>3
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>4
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>5
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>6
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>7
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>8
    12:01:09.916 [t2] INFO thread.TestYieldPriority -             ---- 2>9
    
    

    守护线程

    默认情况下,java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。

    默认的线程都是非守护线程。

    垃圾回收线程就是典型的守护线程

    // 方法的定义
    public final void setDaemon(boolean on) {
    }
    
    Thread thread = new Thread(() -> {
        while (true) {
        }
    });
    // 具体的api。设为true表示未守护线程,当主线程结束后,守护线程也结束。
    // 默认是false,当主线程结束后,thread继续运行,程序不停止
    thread.setDaemon(true);
    thread.start();
    log.info("结束");
    

    线程的阻塞

    线程的阻塞可以分为好多种,从操作系统层面和java层面阻塞的定义可能不同,但是广义上使得线程阻塞的方式有下面几种

    1. BIO阻塞,即使用了阻塞式的io流
    2. sleep(long time) 让线程休眠进入阻塞状态
    3. a.join() 调用该方法的线程进入阻塞,等待a线程执行完恢复运行
    4. sychronized或ReentrantLock 造成线程未获得锁进入阻塞状态 (同步锁章节细说)
    5. 获得锁之后调用wait()方法 也会让线程进入阻塞状态 (同步锁章节细说)
    6. LockSupport.park() 让线程进入阻塞状态 (同步锁章节细说)

    sleep()

    ​ 使线程休眠,会将运行中的线程进入阻塞状态。当休眠时间结束后,重新争抢cpu的时间片继续运行

    // 方法的定义 native方法
    public static native void sleep(long millis) throws InterruptedException; 
    
    try {
       // 休眠2秒
       // 该方法会抛出 InterruptedException异常 即休眠过程中可被中断,被中断后抛出异常
       Thread.sleep(2000);
     } catch (InterruptedException异常 e) {
     }
     try {
       // 使用TimeUnit的api可替代 Thread.sleep 
       TimeUnit.SECONDS.sleep(1);
     } catch (InterruptedException e) {
     }
    

    join()

    ​ join是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行

    // 方法的定义 有重载
    // 等待线程执行完才恢复运行
    public final void join() throws InterruptedException {
    }
    // 指定join的时间。指定时间内 线程还未执行完 调用方线程不继续等待就恢复运行
    public final synchronized void join(long millis)
        throws InterruptedException{}
    
    
    Thread t = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        r = 10;
    });
    
    t.start();
    // 让主线程阻塞 等待t线程执行完才继续执行 
    // 去除该行,执行结果为0,加上该行 执行结果为10
    t.join();
    log.info("r:{}", r);
    
    // 运行结果
    13:09:13.892 [main] INFO thread.TestJoin - r:10
    

    线程的打断-interrupt()

    // 相关方法的定义
    public void interrupt() {
    }
    public boolean isInterrupted() {
    }
    public static boolean interrupted() {
    }
    

    打断标记:线程是否被打断,true表示被打断了,false表示没有

    isInterrupted() 获取线程的打断标记 ,调用后不会修改线程的打断标记

    interrupt()方法用于中断线程

    1. 可以打断sleep,wait,join等显式的抛出InterruptedException方法的线程,但是打断后,线程的打断标记还是false
    2. 打断正常线程 ,线程不会真正被中断,但是线程的打断标记为true

    interrupted() 获取线程的打断标记,调用后清空打断标记 即如果获取为true 调用后打断标记为false (不常用)

    interrupt实例: 有个后台监控线程不停的监控,当外界打断它时,就结束运行。代码如下

    @Slf4j
    class TwoPhaseTerminal{
        // 监控线程
        private Thread monitor;
    
        public void start(){
            monitor = new Thread(() ->{
               // 不停的监控
                while (true){
                    Thread thread = Thread.currentThread();
                 	// 判断当前线程是否被打断
                    if (thread.isInterrupted()){
                        log.info("当前线程被打断,结束运行");
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                    	// 监控逻辑中被打断后,打断标记为true
                        log.info("监控");
                    } catch (InterruptedException e) {
                        // 睡眠时被打断时抛出异常 在该处捕获到 此时打断标记还是false
                        // 在调用一次中断 使得中断标记为true
                        thread.interrupt();
                    }
                }
            });
            monitor.start();
        }
    
        public void stop(){
            monitor.interrupt();
        }
    }
    

    线程的状态

    上面说了一些基本的api的使用,调用上面的方法后都会使得线程有对应的状态。

    线程的状态可从 操作系统层面分为五种状态 从java api层面分为六种状态。

    五种状态

    1. 初始状态:创建线程对象时的状态
    2. 可运行状态(就绪状态):调用start()方法后进入就绪状态,也就是准备好被cpu调度执行
    3. 运行状态:线程获取到cpu的时间片,执行run()方法的逻辑
    4. 阻塞状态: 线程被阻塞,放弃cpu的时间片,等待解除阻塞重新回到就绪状态争抢时间片
    5. 终止状态: 线程执行完成或抛出异常后的状态

    六种状态

    Thread类中的内部枚举State

    public enum State {
    	NEW,
    	RUNNABLE,
    	BLOCKED,
    	WAITING,
    	TIMED_WAITING,
    	TERMINATED;
    }
    
    1. NEW 线程对象被创建
    2. Runnable 线程调用了start()方法后进入该状态,该状态包含了三种情况
      1. 就绪状态 :等待cpu分配时间片
      2. 运行状态:进入Runnable方法执行任务
      3. 阻塞状态:BIO 执行阻塞式io流时的状态
    3. Blocked 没获取到锁时的阻塞状态(同步锁章节会细说)
    4. WAITING 调用wait()、join()等方法后的状态
    5. TIMED_WAITING 调用 sleep(time)、wait(time)、join(time)等方法后的状态
    6. TERMINATED 线程执行完成或抛出异常后的状态

    六种线程状态和方法的对应关系

    线程的相关方法总结

    主要总结Thread类中的核心方法

    方法名称是否static方法说明
    start()让线程启动,进入就绪状态,等待cpu分配时间片
    run()重写Runnable接口的方法,线程获取到cpu时间片时执行的具体逻辑
    yield()线程的礼让,使得获取到cpu时间片的线程进入就绪状态,重新争抢时间片
    sleep(time)线程休眠固定时间,进入阻塞状态,休眠时间完成后重新争抢时间片,休眠可被打断
    join()/join(time)调用线程对象的join方法,调用者线程进入阻塞,等待线程对象执行完或者到达指定时间才恢复,重新争抢时间片
    isInterrupted()获取线程的打断标记,true:被打断,false:没有被打断。调用后不会修改打断标记
    interrupt()打断线程,抛出InterruptedException异常的方法均可被打断,但是打断后不会修改打断标记,正常执行的线程被打断后会修改打断标记
    interrupted()获取线程的打断标记。调用后会清空打断标记
    stop()停止线程运行 不推荐
    suspend()挂起线程 不推荐
    resume()恢复线程运行 不推荐
    currentThread()获取当前线程

    Object中与线程相关方法

    方法名称方法说明
    wait()/wait(long timeout)获取到锁的线程进入阻塞状态
    notify()随机唤醒被wait()的一个线程
    notifyAll();唤醒被wait()的所有线程,重新争抢时间片

    同步锁

    线程安全

    • 一个程序运行多个线程本身是没有问题的
    • 问题有可能出现在多个线程访问共享资源
      • 多个线程都是读共享资源也是没有问题的
      • 当多个线程读写共享资源时,如果发生指令交错,就会出现问题

    临界区: 一段代码如果对共享资源的多线程读写操作,这段代码就被称为临界区。

    注意的是 指令交错指的是 java代码在解析成字节码文件时,java代码的一行代码在字节码中可能有多行,在线程上下文切换时就有可能交错。

    线程安全指的是多线程调用同一个对象的临界区的方法时,对象的属性值一定不会发生错误,这就是保证了线程安全。

    如下面不安全的代码

    // 对象的成员变量
    private static int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
      // t1线程对变量+5000次
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });
      // t2线程对变量-5000次
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count--;
            }
        });
    
        t1.start();
        t2.start();
    
        // 让t1 t2都执行完
        t1.join();
        t2.join();
        System.out.println(count);
    }
    
    // 运行结果 
    -1399
    

    上面的代码 两个线程,一个+5000次,一个-5000次,如果线程安全,count的值应该还是0。

    但是运行很多次,每次的结果不同,且都不是0,所以是线程不安全的。

    线程安全的类一定所有的操作都线程安全吗?

    开发中经常会说到一些线程安全的类,如ConcurrentHashMap,线程安全指的是类里每一个独立的方法是线程安全的,但是方法的组合就不一定是线程安全的

    成员变量和静态变量是否线程安全?

    • 如果没有多线程共享,则线程安全
    • 如果存在多线程共享
      • 多线程只有读操作,则线程安全
      • 多线程存在写操作,写操作的代码又是临界区,则线程不安全

    局部变量是否线程安全?

    • 局部变量是线程安全的
    • 局部变量引用的对象未必是线程安全的
      • 如果该对象没有逃离该方法的作用范围,则线程安全
      • 如果该对象逃离了该方法的作用范围,比如:方法的返回值,需要考虑线程安全

    synchronized

    同步锁也叫对象锁,是锁在对象上的,不同的对象就是不同的锁。

    该关键字是用于保证线程安全的,是阻塞式的解决方案。

    让同一个时刻最多只有一个线程能持有对象锁,其他线程在想获取这个对象锁就会被阻塞,不用担心上下文切换的问题。

    注意: 不要理解为一个线程加了锁 ,进入 synchronized代码块中就会一直执行下去。如果时间片切换了,也会执行其他线程,再切换回来会紧接着执行,只是不会执行到有竞争锁的资源,因为当前线程还未释放锁。

    当一个线程执行完synchronized的代码块后 会唤醒正在等待的线程

    synchronized实际上使用对象锁保证临界区的原子性 临界区的代码是不可分割的 不会因为线程切换所打断

    基本使用

    // 加在方法上 实际是对this对象加锁
    private synchronized void a() {
    }
    
    // 同步代码块,锁对象可以是任意的,加在this上 和a()方法作用相同
    private void b(){
        synchronized (this){
    
        }
    }
    
    // 加在静态方法上 实际是对类对象加锁
    private synchronized static void c() {
    
    }
    
    // 同步代码块 实际是对类对象加锁 和c()方法作用相同
    private void d(){
        synchronized (TestSynchronized.class){
            
        }
    }
    
    // 上述b方法对应的字节码源码 其中monitorenter就是加锁的地方
     0 aload_0
     1 dup
     2 astore_1
     3 monitorenter
     4 aload_1
     5 monitorexit
     6 goto 14 (+8)
     9 astore_2
    10 aload_1
    11 monitorexit
    12 aload_2
    13 athrow
    14 return
    

    线程安全的代码

    private static int count = 0;
    
    private static Object lock = new Object();
    
    private static Object lock2 = new Object();
    
     // t1线程和t2对象都是对同一对象加锁。保证了线程安全。此段代码无论执行多少次,结果都是0
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    count--;
                }
            }
        });
     
        t1.start();
        t2.start();
    
        // 让t1 t2都执行完
        t1.join();
        t2.join();
        System.out.println(count);
    }
    

    重点:加锁是加在对象上,一定要保证是同一对象,加锁才能生效

    线程通信

    wait+notify

    线程间通信可以通过共享变量+wait()&notify()来实现

    wait()将线程进入阻塞状态,notify()将线程唤醒

    当多线程竞争访问对象的同步方法时,锁对象会关联一个底层的Monitor对象(重量级锁的实现)

    如下图所示 Thread0,1先竞争到锁执行了代码后,2,3,4,5线程同时来执行临界区的代码,开始竞争锁

    1. Thread-0先获取到对象的锁,关联到monitor的owner,同步代码块内调用了锁对象的wait()方法,调用后会进入waitSet等待,Thread-1同样如此,此时Thread-0的状态为Waitting
    2. Thread2、3、4、5同时竞争,2获取到锁后,关联了monitor的owner,3、4、5只能进入EntryList中等待,此时2线程状态为 Runnable,3、4、5状态为Blocked
    3. 2执行后,唤醒entryList中的线程,3、4、5进行竞争锁,获取到的线程即会关联monitor的owner
    4. 3、4、5线程在执行过程中,调用了锁对象的notify()或notifyAll()时,会唤醒waitSet的线程,唤醒的线程进入entryList等待重新竞争锁

    注意:

    1. Blocked状态和Waitting状态都是阻塞状态

    2. Blocked线程会在owner线程释放锁时唤醒

    3. wait和notify使用场景是必须要有同步,且必须获得对象的锁才能调用,使用锁对象去调用,否则会抛异常

    • wait() 释放锁 进入 waitSet 可传入时间,如果指定时间内未被唤醒 则自动唤醒
    • notify()随机唤醒一个waitSet里的线程
    • notifyAll()唤醒waitSet中所有的线程
    static final Object lock = new Object();
    new Thread(() -> {
        synchronized (lock) {
            log.info("开始执行");
            try {
              	// 同步代码内部才能调用
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("继续执行核心逻辑");
        }
    }, "t1").start();
    
    new Thread(() -> {
        synchronized (lock) {
            log.info("开始执行");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("继续执行核心逻辑");
        }
    }, "t2").start();
    
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("开始唤醒");
    
    synchronized (lock) {
      // 同步代码内部才能调用
        lock.notifyAll();
    }
    // 执行结果
    14:29:47.138 [t1] INFO TestWaitNotify - 开始执行
    14:29:47.141 [t2] INFO TestWaitNotify - 开始执行
    14:29:49.136 [main] INFO TestWaitNotify - 开始唤醒
    14:29:49.136 [t2] INFO TestWaitNotify - 继续执行核心逻辑
    14:29:49.136 [t1] INFO TestWaitNotify - 继续执行核心逻辑
    

    wait 和 sleep的区别?

    二者都会让线程进入阻塞状态,有以下区别

    1. wait是Object的方法 sleep是Thread的方法
    2. wait会立即释放锁 sleep不会释放锁
    3. wait后线程的状态是Watting sleep后线程的状态为 Time_Waiting

    park&unpark

    LockSupport是juc下的工具类,提供了park和unpark方法,可以实现线程通信

    与wait和notity相比的不同点

    1. wait 和notify需要获取对象锁 park unpark不要
    2. unpark 可以指定唤醒线程 notify随机唤醒
    3. park和unpark的顺序可以先unpark wait和notify的顺序不能颠倒

    生产者消费者模型

    指的是有生产者来生产数据,消费者来消费数据,生产者生产满了就不生产了,通知消费者取,等消费了再进行生产。
    

    消费者消费不到了就不消费了,通知生产者生产,生产到了再继续消费。

      public static void main(String[] args) throws InterruptedException {
            MessageQueue queue = new MessageQueue(2);
    		
    		// 三个生产者向队列里存值
            for (int i = 0; i < 3; i++) {
                int id = i;
                new Thread(() -> {
                    queue.put(new Message(id, "值" + id));
                }, "生产者" + i).start();
            }
    
            Thread.sleep(1000);
    
    		// 一个消费者不停的从队列里取值
            new Thread(() -> {
                while (true) {
                    queue.take();
                }
            }, "消费者").start();
    
        }
    }
    
    
    // 消息队列被生产者和消费者持有
    class MessageQueue {
        private LinkedList<Message> list = new LinkedList<>();
    
        // 容量
        private int capacity;
    
        public MessageQueue(int capacity) {
            this.capacity = capacity;
        }
    
        /**
         * 生产
         */
        public void put(Message message) {
            synchronized (list) {
                while (list.size() == capacity) {
                    log.info("队列已满,生产者等待");
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.addLast(message);
                log.info("生产消息:{}", message);
                // 生产后通知消费者
                list.notifyAll();
            }
        }
    
        public Message take() {
            synchronized (list) {
                while (list.isEmpty()) {
                    log.info("队列已空,消费者等待");
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Message message = list.removeFirst();
                log.info("消费消息:{}", message);
                // 消费后通知生产者
                list.notifyAll();
                return message;
            }
        }
    
    
    }
     // 消息
    class Message {
    
        private int id;
    
        private Object value;
    }
    

    同步锁案例

    为了更形象的表达加同步锁的概念,这里举一个生活中的例子,尽量把以上的概念具体化出来。

    这里举一个每个人非常感兴趣的一件东西。 钱!!!(马老师除外)。

    现实中,我们去银行门口的自动取款机取钱,取款机的钱就是共享变量,为了保障安全,不可能两个陌生人同时进入同一个取款机内取钱,所以只能一个人进入取钱,然后锁上取款机的门,其他人只能在取款机门口等待。

    取款机有多个,里面的钱互不影响,锁也有多个(多个对象锁),取钱人在多个取款机里同时取钱也没有安全问题。

    假如每个取钱的陌生人都是线程,当取钱人进入取款机锁了门后(线程获得锁),取到钱后出门(线程释放锁),下一个人竞争到锁来取钱。

    假设工作人员也是一个线程,如果取钱人进入后发现取款机钱不足了,这时通知工作人员来向取款机里加钱(调用notifyAll方法),取钱人暂停取钱,进入银行大堂阻塞等待(调用wait方法)。

    银行大堂里的工作人员和取钱人都被唤醒,重新竞争锁,进入后如果是取钱人,由于取款机没钱,还得进入银行大堂等待。

    当工作人员获得取款机的锁进入后,加了钱后会通知大厅里的人来取钱(调用notifyAll方法)。自己暂停加钱,进入银行大堂等待唤醒加钱(调用wait方法)。

    这时大堂里等待的人都来竞争锁,谁获取到谁进入继续取钱。

    和现实中不同的就是这里没有排队的概念,谁抢到锁谁进去取。

    ReentrantLock

    可重入锁 : 一个线程获取到对象的锁后,执行方法内部在需要获取锁的时候是可以获取到的。如以下代码

    private static final ReentrantLock LOCK = new ReentrantLock();
    
    private static void m() {
        LOCK.lock();
        try {
            log.info("begin");
          	// 调用m1()
            m1();
        } finally {
            // 注意锁的释放
            LOCK.unlock();
        }
    }
    public static void m1() {
        LOCK.lock();
        try {
            log.info("m1");
            m2();
        } finally {
            // 注意锁的释放
            LOCK.unlock();
        }
    }
    

    synchronized 也是可重入锁,ReentrantLock有以下优点

    1. 支持获取锁的超时时间
    2. 获取锁时可被打断
    3. 可设为公平锁
    4. 可以有不同的条件变量,即有多个waitSet,可以指定唤醒

    api

    // 默认非公平锁,参数传true 表示未公平锁
    ReentrantLock lock = new ReentrantLock(false);
    // 尝试获取锁
    lock()
    // 释放锁 应放在finally块中 必须执行到
    unlock()
    try {
        // 获取锁时可被打断,阻塞中的线程可被打断
        LOCK.lockInterruptibly();
    } catch (InterruptedException e) {
        return;
    }
    // 尝试获取锁 获取不到就返回false
    LOCK.tryLock()
    // 支持超时时间 一段时间没获取到就返回false
    tryLock(long timeout, TimeUnit unit)
    // 指定条件变量 休息室 一个锁可以创建多个休息室
    Condition waitSet = ROOM.newCondition();
    // 释放锁  进入waitSet等待 释放后其他线程可以抢锁
    yanWaitSet.await()
    // 唤醒具体休息室的线程 唤醒后 重写竞争锁
    yanWaitSet.signal()
    
    

    实例:一个线程输出a,一个线程输出b,一个线程输出c,abc按照顺序输出,连续输出5次

    这个考的就是线程的通信,利用 wait()/notify()和控制变量可以实现,此处使用ReentrantLock即可实现该功能。

      public static void main(String[] args) {
            AwaitSignal awaitSignal = new AwaitSignal(5);
            // 构建三个条件变量
            Condition a = awaitSignal.newCondition();
            Condition b = awaitSignal.newCondition();
            Condition c = awaitSignal.newCondition();
            // 开启三个线程
            new Thread(() -> {
                awaitSignal.print("a", a, b);
            }).start();
    
            new Thread(() -> {
                awaitSignal.print("b", b, c);
            }).start();
    
            new Thread(() -> {
                awaitSignal.print("c", c, a);
            }).start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            awaitSignal.lock();
            try {
                // 先唤醒a
                a.signal();
            } finally {
                awaitSignal.unlock();
            }
        }
    
    
    }
    
    class AwaitSignal extends ReentrantLock {
    
        // 循环次数
        private int loopNumber;
    
        public AwaitSignal(int loopNumber) {
            this.loopNumber = loopNumber;
        }
    
        /**
         * @param print   输出的字符
         * @param current 当前条件变量
         * @param next    下一个条件变量
         */
        public void print(String print, Condition current, Condition next) {
    
            for (int i = 0; i < loopNumber; i++) {
                lock();
                try {
                    try {
                        // 获取锁之后等待
                        current.await();
                        System.out.print(print);
                    } catch (InterruptedException e) {
                    }
                    next.signal();
                } finally {
                    unlock();
                }
            }
        }
    

    死锁

    说到死锁,先举个例子,

    下面是代码实现

    static Beer beer = new Beer();
    static Story story = new Story();
    
    public static void main(String[] args) {
        new Thread(() ->{
            synchronized (beer){
                log.info("我有酒,给我故事");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (story){
                    log.info("小王开始喝酒讲故事");
                }
            }
        },"小王").start();
    
        new Thread(() ->{
            synchronized (story){
                log.info("我有故事,给我酒");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (beer){
                    log.info("老王开始喝酒讲故事");
                }
            }
        },"老王").start();
    }
    class Beer {
    }
    
    class Story{
    }
    

    死锁导致程序无法正常运行下去

    检测工具可以检查到死锁信息

    java内存模型(JMM)

    jmm 体现在以下三个方面

    1. 原子性 保证指令不会受到上下文切换的影响
    2. 可见性 保证指令不会受到cpu缓存的影响
    3. 有序性 保证指令不会受并行优化的影响

    可见性

    停不下来的程序

    static boolean run = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (run) {
                // ....
            }
        });
        t.start();
        Thread.sleep(1000);
       // 线程t不会如预想的停下来
        run = false; 
    }
    

    如上图所示,线程有自己的工作缓存,当主线程修改了变量并同步到主内存时,t线程没有读取到,所以程序停不下来

    有序性

    JVM在不影响程序正确性的情况下可能会调整语句的执行顺序,该情况也称为 指令重排序

      static int i;
      static int j;
    // 在某个线程内执行如下赋值操作
            i = ...;
            j = ...;
      有可能将j先赋值
    

    原子性

    原子性大家应该比较熟悉,上述同步锁的synchronized代码块就是保证了原子性,就是一段代码是一个整体,原子性保证了线程安全,不会受到上下文切换的影响。

    volatile

    该关键字解决了可见性和有序性,volatile通过内存屏障来实现的

    • 写屏障

    会在对象写操作之后加写屏障,会对写屏障的之前的数据都同步到主存,并且保证写屏障的执行顺序在写屏障之前

    • 读屏障

    会在对象读操作之前加读屏障,会在读屏障之后的语句都从主存读,并保证读屏障之后的代码执行在读屏障之后

    注意: volatile不能解决原子性,即不能通过该关键字实现线程安全。

    volatile应用场景:一个线程读取变量,另外的线程操作变量,加了该关键字后保证写变量后,读变量的线程可以及时感知。

    无锁-cas

    cas (compare and swap) 比较并交换

    为变量赋值时,从内存中读取到的值v,获取到要交换的新值n,执行 compareAndSwap()方法时,比较v和当前内存中的值是否一致,如果一致则将n和v交换,如果不一致,则自旋重试。

    cas底层是cpu层面的,即不使用同步锁也可以保证操作的原子性。

    private AtomicInteger balance;
    
    // 模拟cas的具体操作
    @Override
    public void withdraw(Integer amount) {
        while (true) {
            // 获取当前值
            int pre = balance.get();
            // 进行操作后得到新值
            int next = pre - amount;
            // 比较并设置成功 则中断 否则自旋重试
            if (balance.compareAndSet(pre, next)) {
                break;
            }
        }
    }
    

    无锁的效率是要高于之前的锁的,由于无锁不会涉及线程的上下文切换

    cas是乐观锁的思想,sychronized是悲观锁的思想

    cas适合很少有线程竞争的场景,如果竞争很强,重试经常发生,反而降低效率

    juc并发包下包含了实现了cas的原子类

    1. AtomicInteger/AtomicBoolean/AtomicLong
    2. AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray
    3. AtomicReference/AtomicStampedReference/AtomicMarkableReference

    AtomicInteger

    常用api

    new AtomicInteger(balance)
    get()
    compareAndSet(pre, next)
    //        i.incrementAndGet() ++i
    //        i.decrementAndGet() --i
    //        i.getAndIncrement() i++
    //        i.getAndDecrement() ++i
     i.addAndGet()
      // 传入函数式接口 修改i
      int getAndUpdate(IntUnaryOperator updateFunction)
      // cas 的核心方法
      compareAndSet(int expect, int update)
    

    ABA问题

    cas存在ABA问题,即比较并交换时,如果原值为A,有其他线程将其修改为B,在有其他线程将其修改为A。

    此时实际发生过交换,但是比较和交换由于值没改变可以交换成功

    解决方式

    AtomicStampedReference/AtomicMarkableReference

    上面两个类解决ABA问题,原理就是为对象增加版本号,每次修改时增加版本号,就可以避免ABA问题

    或者增加个布尔变量标识,修改后调整布尔变量值,也可以避免ABA问题

    线程池

    线程池的介绍

    线程池是java并发最重要的一个知识点,也是难点,是实际应用最广泛的。

    线程的资源很宝贵,不可能无限的创建,必须要有管理线程的工具,线程池就是一种管理线程的工具,java开发中经常有池化的思想,如 数据库连接池、Redis连接池等。

    预先创建好一些线程,任务提交时直接执行,既可以节约创建线程的时间,又可以控制线程的数量。

    线程池的好处

    1. 降低资源消耗,通过池化思想,减少创建线程和销毁线程的消耗,控制资源
    2. 提高响应速度,任务到达时,无需创建线程即可运行
    3. 提供更多更强大的功能,可扩展性高

    线程池的构造方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
     
    }
    

    构造器参数的意义

    参数名参数意义
    corePoolSize核心线程数
    maximumPoolSize最大线程数
    keepAliveTime救急线程的空闲时间
    unit救急线程的空闲时间单位
    workQueue阻塞队列
    threadFactory创建线程的工厂,主要定义线程名
    handler拒绝策略

    线程池案例

    下面 我们通过一个实例来理解线程池的参数以及线程池的接收任务的过程

    如上图 银行办理业务。

    1. 客户到银行时,开启柜台进行办理,柜台相当于线程,客户相当于任务,有两个是常开的柜台,三个是临时柜台。2就是核心线程数,5是最大线程数。即有两个核心线程
    2. 当柜台开到第二个后,都还在处理业务。客户再来就到排队大厅排队。排队大厅只有三个座位。
    3. 排队大厅坐满时,再来客户就继续开柜台处理,目前最大有三个临时柜台,也就是三个救急线程
    4. 此时再来客户,就无法正常为其 提供业务,采用拒绝策略来处理它们
    5. 当柜台处理完业务,就会从排队大厅取任务,当柜台隔一段空闲时间都取不到任务时,如果当前线程数大于核心线程数时,就会回收线程。即撤销该柜台。

    线程池的状态

    线程池通过一个int变量的高3位来表示线程池的状态,低29位来存储线程池的数量

    状态名称高三位接收新任务处理阻塞队列任务说明
    Running111YY正常接收任务,正常处理任务
    Shutdown000NY不会接收任务,会执行完正在执行的任务,也会处理阻塞队列里的任务
    stop001NN不会接收任务,会中断正在执行的任务,会放弃处理阻塞队列里的任务
    Tidying010NN任务全部执行完毕,当前活动线程是0,即将进入终结
    Termitted011NN终结状态
    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
    

    线程池的主要流程

    线程池创建、接收任务、执行任务、回收线程的步骤

    1. 创建线程池后,线程池的状态是Running,该状态下才能有下面的步骤
    2. 提交任务时,线程池会创建线程去处理任务
    3. 当线程池的工作线程数达到corePoolSize时,继续提交任务会进入阻塞队列
    4. 当阻塞队列装满时,继续提交任务,会创建救急线程来处理
    5. 当线程池中的工作线程数达到maximumPoolSize时,会执行拒绝策略
    6. 当线程取任务的时间达到keepAliveTime还没有取到任务,工作线程数大于corePoolSize时,会回收该线程

    注意: 不是刚创建的线程是核心线程,后面创建的线程是非核心线程,线程是没有核心非核心的概念的,这是我长期以来的误解。

    拒绝策略

    1. 调用者抛出RejectedExecutionException (默认策略)
    2. 让调用者运行任务
    3. 丢弃此次任务
    4. 丢弃阻塞队列中最早的任务,加入该任务

    提交任务的方法

    // 执行Runnable
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
    // 提交Callable
    public <T> Future<T> submit(Callable<T> task) {
      if (task == null) throw new NullPointerException();
       // 内部构建FutureTask
      RunnableFuture<T> ftask = newTaskFor(task);
      execute(ftask);
      return ftask;
    }
    // 提交Runnable,指定返回值
    public Future<?> submit(Runnable task) {
      if (task == null) throw new NullPointerException();
      // 内部构建FutureTask
      RunnableFuture<Void> ftask = newTaskFor(task, null);
      execute(ftask);
      return ftask;
    } 
    //  提交Runnable,指定返回值
    public <T> Future<T> submit(Runnable task, T result) {
      if (task == null) throw new NullPointerException();
       // 内部构建FutureTask
      RunnableFuture<T> ftask = newTaskFor(task, result);
      execute(ftask);
      return ftask;
    }
    
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
            return new FutureTask<T>(runnable, value);
    }
    

    Execetors创建线程池

    注意: 下面几种方式都不推荐使用

    1.newFixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    • 核心线程数 = 最大线程数 没有救急线程
    • 阻塞队列无界 可能导致oom

    2.newCachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    • 核心线程数是0,最大线程数无限制 ,救急线程60秒回收
    • 队列采用 SynchronousQueue 实现 没有容量,即放入队列后没有线程来取就放不进去
    • 可能导致线程数过多,cpu负担太大

    3.newSingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    • 核心线程数和最大线程数都是1,没有救急线程,无界队列 可以不停的接收任务
    • 将任务串行化 一个个执行, 使用包装类是为了屏蔽修改线程池的一些参数 比如 corePoolSize
    • 如果某线程抛出异常了,会重新创建一个线程继续执行
    • 可能造成oom

    4.newScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    • 任务调度的线程池 可以指定延迟时间调用,可以指定隔一段时间调用

    线程池的关闭

    shutdown()

    会让线程池状态为shutdown,不能接收任务,但是会将工作线程和阻塞队列里的任务执行完 相当于优雅关闭

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    

    shutdownNow()

    会让线程池状态为stop, 不能接收任务,会立即中断执行中的工作线程,并且不会执行阻塞队列里的任务, 会返回阻塞队列的任务列表

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
    

    线程池的正确使用姿势

    线程池难就难在参数的配置,有一套理论配置参数

    cpu密集型 : 指的是程序主要发生cpu的运算

    ​ 核心线程数: CPU核心数+1

    IO密集型: 远程调用RPC,操作数据库等,不需要使用cpu进行大量的运算。 大多数应用的场景

    ​ 核心线程数=核数*cpu期望利用率 *总时间/cpu运算时间

    但是基于以上理论还是很难去配置,因为cpu运算时间不好估算

    实际配置大小可参考下表

    cpu密集型io密集型
    线程数数量核数<=x<=核数*2核心数*50<=x<=核心数 *100
    队列长度y>=1001<=y<=10

    1.线程池参数通过分布式配置,修改配置无需重启应用

    线程池参数是根据线上的请求数变化而变化的,最好的方式是 核心线程数、最大线程数 队列大小都是可配置的

    主要配置 corePoolSize maxPoolSize queueSize

    java提供了可方法覆盖参数,线程池内部会处理好参数 进行平滑的修改

    public void setCorePoolSize(int corePoolSize) {
    }
    

    2.增加线程池的监控

    3.io密集型可调整为先新增任务到最大线程数后再将任务放到阻塞队列

    代码 主要可重写阻塞队列 加入任务的方法

    public boolean offer(Runnable runnable) {
        if (executor == null) {
            throw new RejectedExecutionException("The task queue does not have executor!");
        }
    
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int currentPoolThreadSize = executor.getPoolSize();
           
            // 如果提交任务数小于当前创建的线程数, 说明还有空闲线程,
            if (executor.getTaskCount() < currentPoolThreadSize) {
                // 将任务放入队列中,让线程去处理任务
                return super.offer(runnable);
            }
    		// 核心改动
            // 如果当前线程数小于最大线程数,则返回 false ,让线程池去创建新的线程
            if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
                return false;
            }
    
            // 否则,就将任务放入队列中
            return super.offer(runnable);
        } finally {
            lock.unlock();
        }
    }
    

    3.拒绝策略 建议使用tomcat的拒绝策略(给一次机会)

    // tomcat的源码
    @Override
    public void execute(Runnable command) {
        if ( executor != null ) {
            try {
                executor.execute(command);
            } catch (RejectedExecutionException rx) {
                // 捕获到异常后 在从队列获取,相当于重试1取不到任务 在执行拒绝任务
                if ( !( (TaskQueue) executor.getQueue()).force(command) ) throw new RejectedExecutionException("Work queue full.");
            }
        } else throw new IllegalStateException("StandardThreadPool not started.");
    }
    

    建议修改从队列取任务的方式: 增加超时时间,超时1分钟取不到在进行返回

    public boolean offer(E e, long timeout, TimeUnit unit){}
    

    结语

    工作三四年了,还没有正式的写过博客,自学一直都是通过笔记的方式积累,最近重新学了一下java多线程,想着周末把这部分内容认真的写篇博客分享出去。

    文章篇幅较长,给看到这里的小伙伴点个大大的赞!由于作者水平有限,加之第一次写博客,文章中难免会有错误之处,欢迎小伙伴们反馈指正。

    如果觉得文章对你有帮助,麻烦 点赞、评论、转发、在看 走起

    你的支持是我最大的动力!!!

    展开全文
  • Java 多线程:彻底搞懂线程池

    万次阅读 多人点赞 2019-07-09 19:27:00
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...

    熟悉 Java 多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。

    目录

    1 线程池的优势

    2 线程池的使用

    3 线程池的工作原理

    4 线程池的参数

    4.1 任务队列(workQueue)

    4.2 线程工厂(threadFactory)

    4.3 拒绝策略(handler)

    5 功能线程池

    5.1 定长线程池(FixedThreadPool)

    5.2 定时线程池(ScheduledThreadPool )

    5.3 可缓存线程池(CachedThreadPool)

    5.4 单线程化线程池(SingleThreadExecutor)

    5.5 对比

    6 总结

    参考


    1 线程池的优势

    总体来说,线程池有如下的优势:

    (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

    (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    2 线程池的使用

    线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

    可以看到,其需要如下几个参数:

    • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
    • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
    • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
    • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
    • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

    线程池的使用流程如下:

    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                                 MAXIMUM_POOL_SIZE,
                                                 KEEP_ALIVE,
                                                 TimeUnit.SECONDS,
                                                 sPoolWorkQueue,
                                                 sThreadFactory);
    // 向线程池提交任务
    threadPool.execute(new Runnable() {
        @Override
        public void run() {
            ... // 线程执行的任务
        }
    });
    // 关闭线程池
    threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
    threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

    3 线程池的工作原理

    下面来描述一下线程池工作的原理,同时对上面的参数有一个更深的了解。其工作原理流程图如下:

    通过上图,相信大家已经对所有参数有个了解了。下面再对任务队列、线程工厂和拒绝策略做更多的说明。

    4 线程池的参数

    4.1 任务队列(workQueue)

    任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现:

    1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
    2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE
    3. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
    4. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
    5. SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
    6. LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
    7. LinkedTransferQueue: 它是ConcurrentLinkedQueueLinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

    注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

    4.2 线程工厂(threadFactory)

    线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:

    /**
     * The default thread factory.
     */
    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;
    
        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }
    
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    4.3 拒绝策略(handler)

    当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:

    1. AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
    2. CallerRunsPolicy:由调用线程处理该任务。
    3. DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
    4. DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

    5 功能线程池

    嫌上面使用线程池的方法太麻烦?其实Executors已经为我们封装好了 4 种常见的功能线程池,如下:

    • 定长线程池(FixedThreadPool)
    • 定时线程池(ScheduledThreadPool )
    • 可缓存线程池(CachedThreadPool)
    • 单线程化线程池(SingleThreadExecutor)

    5.1 定长线程池(FixedThreadPool)

    创建方法的源码:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    • 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:控制线程最大并发数。

    使用示例:

    // 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    fixedThreadPool.execute(task);

    5.2 定时线程池(ScheduledThreadPool )

    创建方法的源码:

    private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
    
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }
    • 特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
    • 应用场景:执行定时或周期性的任务。

    使用示例:

    // 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务

    5.3 可缓存线程池(CachedThreadPool)

    创建方法的源码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
    • 特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
    • 应用场景:执行大量、耗时少的任务。

    使用示例:

    // 1. 创建可缓存线程池对象
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    cachedThreadPool.execute(task);

    5.4 单线程化线程池(SingleThreadExecutor)

    创建方法的源码:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
    • 特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

    使用示例:

    // 1. 创建单线程化线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    singleThreadExecutor.execute(task);

    5.5 对比

    6 总结

    Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

    其实 Executors 的 4 个功能线程有如下弊端:

    • FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM。
    • CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

     

    参考

     

    展开全文
  • Java多线程

    万次阅读 多人点赞 2021-06-11 16:28:49
    Java多线程Java多线程线程的创建线程常见方法线程的状态线程的优先级守护线程线程组Java线程池线程池的创建线程池的参数线程池的使用线程不安全问题Java中的锁synchronized同步方法synchronized同步语句块...

    Java多线程

    线程的创建

    1.继承Thread
    2.实现Runnable
    3.实现Callable
    使用继承Thread类来开发多线程的应用程序在设计上是有局限性的,因为Java是单继承。
    继承Thread类

    public class ThreadDemo1 {
        // 继承Thread类 写法1
        static class MyThread extends Thread{
            @Override
            public void run() {
                //要实现的业务代码
            }
        }
    
        // 写法2
        Thread thread = new Thread(){
            @Override
            public void run() {
                //要实现的业务代码
            }
        };
    
    
    }
    

    实现Runnable接口

    //实现Runnable接口 写法1
    
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            //要实现的业务代码
        }
    }
    
    //实现Runnable接口 写法2 匿名内部类
    class MyRunnable2 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //要实现的业务代码
                }
            });
        }
    }
    

    实现Callable接口(Callable + FutureTask 创建带有返回值的线程)

    package ThreadDeom;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 17:34;
     */
    //创建有返回值的线程 Callable + Future
    public class ThreadDemo2 {
        static class MyCallable implements Callable<Integer>{
            @Override
            public Integer call() throws Exception {
                return 0;
            }
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //创建Callable子对象
            MyCallable myCallable = new MyCallable();
            //使用FutureTask 接受 Callable
            FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
            //创建线程并设置任务
            Thread thread = new Thread(futureTask);
            //启动线程
            thread.start();
            //得到线程的执行结果
            int num = futureTask.get();
        }
    }
    
    

    也可以使用lambda表达式

    class ThreadDemo21{
        //lambda表达式
        Thread thread = new Thread(()-> {
            //要实现的业务代码
        });
    }
    
    

    Thread的构造方法
    在这里插入图片描述

    线程常用方法

    获取当前线程的引用、线程的休眠

    class Main{
        public static void main(String[] args) throws InterruptedException {
            Thread.sleep(1000);
            //休眠1000毫秒之后打印
            System.out.println(Thread.currentThread());
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    在这里插入图片描述

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 18:38;
     */
    public class ThreadDemo6 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
    
                    System.out.println("线程的ID:" + Thread.currentThread().getId());
                    System.out.println("线程的名称:" + Thread.currentThread().getName());
                    System.out.println("线程的状态:" + Thread.currentThread().getState());
    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"线程一");
    
            thread.start();
            Thread.sleep(100);
            //打印线程的状态
            System.out.println("线程的状态:"+thread.getState());
            System.out.println("线程的优先级:"+thread.getPriority());
            System.out.println("线程是否存活:"+thread.isAlive());
            System.out.println("线程是否是守护线程:"+thread.isDaemon());
            System.out.println("线程是否被打断:"+thread.isInterrupted());
        }
    }
    
    

    在这里插入图片描述

    线程的等待
    假设有一个坑位,thread1 和 thread2 都要上厕所。一次只能一个人上,thread2只能等待thread1使用完才能使用厕所。就可以使用join()方法,等待线程1执行完,thread2在去执行。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:48;
     */
    public class ThreadDemo13 {
        public static void main(String[] args) throws InterruptedException {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"🚾");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName()+"出来了");
                }
            };
    
            Thread t1 = new Thread(runnable,"thread1");
            t1.start();
    
            //t1.join();
            Thread t2 = new Thread(runnable,"thread2");
            t2.start();
        }
    }
    
    

    在这里插入图片描述

    没有join()显然是不行的。加上join()之后:
    在这里插入图片描述

    线程的终止

    1.自定义实现线程的终止

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:59;
     */
    public class ThreadDemo11 {
        private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!flag){
                        System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
                }
    
            },"thread");
            thread.start();
    
            Thread.sleep(300);
            flag = true;
    
        }
    }
    
    

    在这里插入图片描述

    2.使用Thread的interrupted来中断

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:59;
     */
    public class ThreadDemo11 {
    //    private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!Thread.interrupted()){
                        System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
    //                        e.printStackTrace();
                            break;
                        }
                    }
                    System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
                }
    
            },"thread");
            thread.start();
    
            Thread.sleep(300);
            thread.interrupt();
    //        flag = true;
    
        }
    }
    
    

    在这里插入图片描述
    3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别
    Thread.interrupted()方法第一次接收到终止的状态后,之后会将状态复位,Thread.interrupted()是静态的,是全局的。Threaed.currentThread().interrupt()只是普通的方法。
    Thraed.interrupted()方法

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:32;
     */
    public class ThreadDemo12 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() ->{
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.interrupted());
                }
            });
    
            thread.start();
            thread.interrupt();
    
        }
    }
    
    

    在这里插入图片描述

    Threaed.currentThread().interrupt()

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:32;
     */
    public class ThreadDemo12 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() ->{
                for (int i = 0; i < 10; i++) {
    //                System.out.println(Thread.interrupted());
                    System.out.println(Thread.currentThread().isInterrupted());
                }
    
            });
    
            thread.start();
            thread.interrupt();
    
        }
    }
    
    

    在这里插入图片描述

    yield()方法
    让出CPU的执行权

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:47;
     */
    public class ThreadDemo15 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    Thread.yield();
                    System.out.println("thread1");
                }
            });
    
            thread1.start();
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    System.out.println("thread2");
                }
            });
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述

    线程的状态

    在这里插入图片描述

    打印出线程的所有的状态,所有的线程的状态都在枚举中。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:06;
     */
    public class ThreadDemo14 {
        public static void main(String[] args) {
            for (Thread.State state: Thread.State.values()) {
                System.out.println(state);
            }
        }
    }
    
    

    在这里插入图片描述

    NEW 创建了线程但是还没有开始工作
    RUNNABLE 正在Java虚拟机中执行的线程
    BLOCKED 受到阻塞并且正在等待某个监视器的锁的时候所处的状态
    WAITTING 无限期的等待另一个线程执行某个特定操作的线程处于这个状态
    TIME_WAITTING 有具体等待时间的等待
    TERMINATED 已经退出的线程处于这种状态

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:06;
     */
    
    class TestThreadDemo{
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            System.out.println(thread.getState());
            thread.start();
            System.out.println(thread.getState());
    
            Thread.sleep(100);
    
            System.out.println(thread.getState());
    
            thread.join();
    
            System.out.println(thread.getState());
        }
    }
    

    在这里插入图片描述

    线程的优先级

    在Java中线程 的优先级分为1 ~ 10 一共十个等级

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 21:22;
     */
    public class ThreadDemo9 {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                Thread t1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t1");
                    }
                });
                //最大优先级
                t1.setPriority(10);
                t1.start();
                Thread t2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t2");
                    }
                });
                //最小优先级
                t2.setPriority(1);
                t2.start();
                Thread t3 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t3");
                    }
                });
                t3.setPriority(1);
                t3.start();
            }
        }
    }
    
    

    在这里插入图片描述
    线程的优先级不是绝对的,只是给程序的建议。
    线程之间的优先级具有继承的特性,如果A线程启动了B线程,那么B的线程的优先级与A是一样的。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 20:46;
     */
    class ThreadA extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadA优先级是:" + this.getPriority());
            ThreadB threadB = new ThreadB();
            threadB.start();
        }
    }
    
    class ThreadB extends ThreadA{
        @Override
        public void run() {
            System.out.println("ThreadB的优先级是:" + this.getPriority());
        }
    }
    public class ThreadDemo7 {
        public static void main(String[] args) {
    
            System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
            
            System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
    
            ThreadA threadA = new ThreadA();
            threadA.start();
        }
    }
    
    

    在这里插入图片描述

    再看👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 20:46;
     */
    class ThreadA extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadA优先级是:" + this.getPriority());
            ThreadB threadB = new ThreadB();
            threadB.start();
        }
    }
    
    class ThreadB extends ThreadA{
        @Override
        public void run() {
            System.out.println("ThreadB的优先级是:" + this.getPriority());
        }
    }
    public class ThreadDemo7 {
        public static void main(String[] args) {
    
            System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
            Thread.currentThread().setPriority(9);
            System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
    
            ThreadA threadA = new ThreadA();
            threadA.start();
        }
    }
    
    

    结果为👇
    在这里插入图片描述

    守护线程

    Java中有两种线程:一种是用户线程,一种就是守护线程。
    什么是守护线程?守护线程是一种特殊的线程,当进程中不存在用户线程的时候,守护线程就会自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有了非守护线程,则垃圾回收线程也就没有存在的必要了。
    Daemon线程的作用就是为其他线程的运行提供便利的。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 21:06;
     */
    
    public class ThreadDemo8 {
        static private int i = 0;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        i++;
                        System.out.println(i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            //设置守护线程
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("我是守护线程thread 当用户线程执行完成后 我也就销毁了😭哭了");
        }
    }
    
    

    在这里插入图片描述
    注意:守护线程的设置必须放在start()之前,否则就会报错。
    在这里插入图片描述
    在守护线程中创建的线程默认也是守护线程。

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:35;
     */
    public class ThreadDemo10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                Thread thread2 = new Thread(() -> {
                },"thread2");
                System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
    
            },"thread1");
    
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
            //thread1.setDaemon(true);
            thread1.start();
           // System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
        }
    }
    
    

    在这里插入图片描述
    再看👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:35;
     */
    public class ThreadDemo10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                Thread thread2 = new Thread(() -> {
                },"thread2");
                System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
    
            },"thread1");
    
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
            thread1.setDaemon(true);
            thread1.start();
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
        }
    }
    

    在这里插入图片描述

    线程组

    为了便于对某些具有相同功能的线程进行管理,可以把这些线程归属到同一个线程组中,线程组中既可以有线程对象,也可以有线程组,组中也可以有线程。
    使用线程模拟赛跑

    public class ThreadDemo5 {
        //线程模拟赛跑(未使用线程分组)
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
                }
            }, "选手一");
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
                }
            }, "选手二");
    
            t1.start();
            t2.start();
    
    
            System.out.println("所有选手到达了终点");
        }
    }
    
    

    运行结果:
    在这里插入图片描述
    不符合预期效果,就可以使用线程组来实现

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 18:24;
     */
    class ThreadGroup1 {
        //线程分组模拟赛跑
        public static void main(String[] args) {
            ThreadGroup threadGroup = new ThreadGroup("Group");
            Thread t1 = new Thread(threadGroup, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("选手一到达了终点");
                }
            });
    
            Thread t2 = new Thread(threadGroup, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("选手二到达了终点");
                }
            });
            t2.start();
            t1.start();
    
            while (threadGroup.activeCount() != 0) {
    
            }
            System.out.println("所有选手到达了终点");
        }
    }
    
    

    在这里插入图片描述
    线程组常用的方法
    在这里插入图片描述

    线程安全问题

    来看单线程情况下让count分别自增和自减10000次

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 12:03;
     */
    class Counter {
        private static int count = 0;
        public void increase(){
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease(){
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount(){
            return count;
        }
    }
    public class ThreadDemo16 {
        public static void main(String[] args) {
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
        }
    }
    
    

    结果符合预期
    在这里插入图片描述

    如果想使程序的执行速度快,就可以使用多线程的方式来执行。在来看多线程情况下的问题

    public class ThreadDemo16 {
        public static void main(String[] args) throws InterruptedException {
    
            //多线程情况下
            Counter counter = new Counter();
            Thread thread1 = new Thread(()->{
                counter.decrease();
            });
    
            Thread thread2 = new Thread(()->{
                counter.increase();
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.getCount());
    
                    /*
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
             */
        }
    }
    
    

    执行结果:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    每次的执行结果是不一样的。这就是多线程的不安全问题
    在这里插入图片描述
    预期的结果是0,但结果却不是。
    线程不安全问题的原因:
    1.CPU的抢占式执行
    2.多个线程共同操作一个变量
    3.内存可见性
    4.原子性问题
    5.编译器优化(指令重排)

    多个线程操作同一个变量
    如果多个线程操作的不是一个变量,就不会发生线程的不安全问题,可以将上面的代码修改如下:👇

    public class ThreadDemo16 {
        static int res1 = 0;
        static int res2 = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            Counter counter = new Counter();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    res1 = counter.getCount();
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    res2 = counter.getCount();
                }
            });
            System.out.println(res1 + res2);
    /*
            //多线程情况下
            Counter counter = new Counter();
            Thread thread1 = new Thread(()->{
                counter.decrease();
            });
    
            Thread thread2 = new Thread(()->{
                counter.increase();
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.getCount());
            */
    
    
                    /*
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
             */
        }
    }
    
    

    这样就可以了:
    在这里插入图片描述

    内存不可见问题:看下面的代码,是不是到thread2执行的时候,就会改变num的值,从而终止了thread1呢?

    package ThreadDeom;
    
    import java.util.Scanner;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 13:03;
     */
    public class ThreadDemo17 {
    
        private static int num = 0;
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (num == 0){}
                }
            });
    
            thread1.start();
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Scanner scanner = new Scanner(System.in);
                    System.out.println("输入一个数字来终止线程thread1");
                    num = scanner.nextInt();
                }
            });
    
            thread2.start();
        }
    
    }
    
    

    结果是不能的:
    在这里插入图片描述
    输入一个数字后回车,并没有让thread1的循环结束。这就是内存不可见的问题。
    原子性的问题
    上面的++和–操作其实是分三步来执行的
    在这里插入图片描述
    假设在第二部的时候,有另外一个线程也来修改值,那么就会出现脏数据的问题了。
    所以就会发生线程的不安全问题

    编译器优化
    编译器的优化会打乱原本程序的执行顺序,就有可能导致线程的不安全问题发生。
    在单线程不会发生线程的不安全问题,在多线程就可能会不安全。

    volatile关键字

    可以使用volatile关键字,这个关键字可以解决指令重排和内存不可见的问题。
    在这里插入图片描述
    加上volatile关键字之后的运行结果
    在这里插入图片描述
    但是volatile关键字不能解决原子性的问题👇:

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:02;
     */
    
    class Counter1 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo18 {
        public static void main(String[] args) throws InterruptedException {
            Counter1 counter1 = new Counter1();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter1.decrease();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                counter1.increase();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter1.getCount());
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    那么Java中如何解决原子性的问题呢👇

    Java中的锁

    Java中的加锁操作有两种:
    1.synchronized锁(jvm层的解决方案,也叫监视器锁)
    在操作系统的层面使用的是互斥锁(mutex lock)
    在Java中放在了对象头中。
    2.手动锁Lock
    操作锁的流程
    1.尝试获取锁
    2.使用锁
    3.释放锁

    synchronized锁

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:12;
     */
    class Counter2 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo19 {
        public static void main(String[] args) throws InterruptedException {
            //声明锁对象,任何的对象都可以作为锁
            Object lock = new Object();
            Counter2 counter2 = new Counter2();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //使用锁
                    synchronized (lock) {
                        counter2.decrease();
    
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    counter2.increase();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter2.getCount());
        }
    }
    
    

    结果是:
    在这里插入图片描述

    synchronized使用场景

    1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作)

    public class ThreadDemo19 {
        public static void main(String[] args) throws InterruptedException {
            //声明锁对象,任何的对象都可以作为锁
            Object lock = new Object();
            Counter2 counter2 = new Counter2();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //使用锁
                    synchronized (lock) {
                        counter2.decrease();
    
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    counter2.increase();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter2.getCount());
        }
    }
    
    

    在这里插入图片描述

    2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作)

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:02;
     */
    
    class Counter1 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo18 {
        public static void main(String[] args) throws InterruptedException {
            Counter1 counter1 = new Counter1();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter1.decrease();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                counter1.increase();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter1.getCount());
        }
    }
    
    

    在这里插入图片描述

    3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁)

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:12;
     */
    public class ThreadDemo20 {
        private static int num = 0;
        private static final int maxSize = 100000;
    
        public static void main(String[] args) throws InterruptedException {
    
            ThreadDemo20 threadDemo20 = new ThreadDemo20();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    threadDemo20.increase();
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                   threadDemo20. decrease();
                }
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(num);
        }
    
        //给静态的方法进行加锁,被加的锁是当前的对象。
    //    public synchronized static void increase(){
    
        //给普通的方法进行加锁的操作
        public synchronized void increase() {
    
            for (int i = 0; i < maxSize; i++) {
                num++;
            }
        }
    
        //    public synchronized static void decrease(){
        public synchronized void decrease() {
    
            for (int i = 0; i < maxSize; i++) {
                num--;
            }
        }
    }
    

    在这里插入图片描述

    synchronized注意事项

    1.加锁的时候一定要使用同一把锁对象

    Lock类的使用

    也叫手动锁

    package ThreadDeom;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 18:32;
     */
    public class ThreadDemo22 {
        private static int number = 0;
        private static final int maxSize = 100000;
    
        public static void main(String[] args) {
            //创建lock锁对象,lock是接口,不能实列化
            Lock lock = new ReentrantLock();
    
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number++;
    
                    } finally {
                        lock.unlock();
                    }
                }
            });
    
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number--;
    
                    } finally {
                        lock.unlock();
                    }
    
                }
            });
    
            System.out.println(number);
        }
    }
    
    

    在这里插入图片描述

    Lock锁使用的注意事项

    1.lock()操作一定要放在try外面
    如果放在try的里面:
    1.try中抛出了异常,还没有加锁就释放了finally中的锁的操作了
    2.如果放在了try,没加锁就释放了锁,就会抛出异常,就会将业务代码中的异常吞噬掉👇
    如果一定要放的话,将lock()放在try的第一行。

    package ThreadDeom;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 18:49;
     */
    public class ThreadDemo23 {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
    
            try{
    
                System.out.println(1/0);
                lock.lock();
    
            } finally {
                lock.unlock();
            }
        }
    }
    
    

    在这里插入图片描述

    公平锁、非公平锁

    公平锁的调度:
    一个线程释放锁。
    主动唤醒“需要得到锁”的队列来得到锁。
    非公平锁
    当一个线程释放锁之后,另一个线程刚好执行到获取锁的代码就可以直接获取锁。
    Java中的所有锁默认都是非公平锁。
    非公平锁的性能更高。
    ReentrantLock可以设置非公平锁。
    公平锁

    package ThreadDeom;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 19:22;
     */
    public class ThreadDemo24 {
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock reentrantLock = new ReentrantLock();
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    reentrantLock.lock();
                    try {
                        System.out.println("thread1");
    
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    reentrantLock.lock();
                    try {
                        System.out.println("thread2");
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            });
    
            Thread.sleep(100);
            thread1.start();
            thread2.start();
        }
    }
    
    

    打印的结果是无序的
    在这里插入图片描述
    如果设置为公平锁:👇
    在这里插入图片描述

    在这里插入图片描述
    thread1和thread2 交替输出

    synchronzied 和 Lock 的区别

    1.synchronzied可以自动的进行加锁和释放锁,而Lock需要手动的加锁、释放锁。
    2.Lock是Java层面的锁实现,而synchronzied 是JVM层面锁的实现
    3.synchronzed 即可以修饰代码块,又可以修饰普通方法和静态的方法,而Lock 只能修饰代码块
    4. synchronized 实现的是 非公平的锁,而Lock 可以实现公平锁。

    5.lock的灵活性更高

    死锁

    在两个或两个以上的线程运行中,因为资源的抢占而造成线程一直等待的问题。
    看👇:

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 19:48;
     */
    public class ThreadDemo25 {
        public static void main(String[] args) throws InterruptedException {
            Object lockA = new Object();
            Object lockB = new Object();
    
    
            Thread thread1 = new Thread(() -> {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    //让线程2获取lockB
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (lockB) {
    
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
    
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //线程2获取资源B
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                        //让线程1先获取到锁lockA
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lockA) {
                            System.out.println(Thread.currentThread().getName() + "获取到lockA");
    
                        }
    
                    }
    
                }
            });
            
            thread1.start();
    
            thread2.start();
        }
    }
    
    

    这就造成了死锁
    在这里插入图片描述

    造成死锁的四个条件

    1.互斥条件:
    当资源被一个线程拥有之后,就不能被其它的线程拥有了
    2.拥有请求条件:
    当一个线程拥有了一个资源之后,又试图请求另一个资源。
    3.不可剥夺条件:
    当一个线程拥有了一个资源之后,如果不是这个线程主动的释放资源,其他线程就不能拥有这个线程。
    4.环路等待条件:
    两个或两个以上的线程拥有了资源之后,试图获取对方的资源的时候形成了一个环路。

    死锁的解决方案

    解决请求拥有和环路等待。
    最有效的解决方案就是控制加锁的顺序。

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 20:25;
     */
    public class ThreadDemo26 {
        public static void main(String[] args) throws InterruptedException {
            Object lockA = new Object();
            Object lockB = new Object();
    
    
            Thread thread1 = new Thread(() -> {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    //让线程2获取lockB
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (lockB) {
    
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
    
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lockA) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockA");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lockB) {
                            System.out.println(Thread.currentThread().getName() + "获取到lockB");
    
                        }
    
                    }
    
                }
            });
    
            thread1.start();
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述

    线程间通信

    线程之间的通讯是指在一个线程中的操作可以影响另一个线程。

    wait/notify机制的原理

    拥有相同锁的线程之间才能使用wait/notify机制。
    wait()是Object()的方法,它的作用是是当前执行wait()方法的线程等待,在wati()所在的代码出停止执行,并释放锁,直到接到通知或者被中断为止。即在调用wait()的方法之前,线程必需先获取到对象级别的锁,也就是只能在同步方法或者同步块中使用wait()方法。
    如果在使用wait()方法之前线程没有获得相应的锁,那么程序在执行时就会抛出异常。
    notify()方法要在同步方法或者同步块中执行,即在调用notify()方法之前,线程必需要先获取到锁对象。如果线程没有持有锁对象的话,那么也会抛出异常。该方法用来通知可能在等待该锁的其它线程,如果有多个线程,那么则按照执行wait()方法的顺序来对处于wait()方法的线程发出通知,并使该线程重新获取锁。执行notify()方法之后,当前线程不会马上释放锁,处于wait()状态的线程也不会立马得到这个对象锁。而是要等notify的synchronized同步区域执行完成之后才会释放锁,处于wait()状态的线程才会得到锁对象。

    总结:wait()方法用于让线程停止运行,而notify()方法用于通知暂停的线程继续运行。
    在使用wait()或者notify()方法之前没有对象锁,就会报异常👇:

            lock.notify();
    
    

    在这里插入图片描述
    正确的使用之后

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 21:11;
     */
    public class ThreadDemo27 {
        //设置锁对象
    
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
    
                @Override
    
                public void run() {
                    synchronized (lock) {
                        System.out.println("在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("被notify()唤醒之后");
                    }
                }
            });
            thread.start();
    
            Thread.sleep(1000);
    
            synchronized (lock) {
                lock.notify();
    
            }
    
        }
    }
    
    

    在这里插入图片描述

    注意:使用wait()方法的时候一定要和线程的锁对象是一个锁。

    notifyAll

    在多线程的情况下使用notify()方法只可以唤醒一个线程👇
    在这里插入图片描述

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 8:06;
     */
    public class ThreadDemo28 {
        //设置锁对象
    
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Runnable() {
    
                @Override
    
                public void run() {
                    synchronized (lock) {
                        System.out.println("thread1在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("thread1被notify()唤醒之后");
                    }
                }
            });
    
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("thread2在wait()");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread2被notify()唤醒之后");
                }
            });
    
            Thread thread3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        System.out.println("thread3在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("thread3被notify()唤醒之后");
                    }
    
                }
            });
    
    
            thread1.start();
            thread2.start();
            thread3.start();
            Thread.sleep(1000);
    
            synchronized (lock) {
    
                System.out.println("主线程调用notify()之后");
    
                lock.notify();
    
            }
    
        }
    }
    
    

    那么如果使用notifyAll()方法呢?
    在这里插入图片描述
    可以看到所有的线程都被唤醒了
    在这里插入图片描述

    那么使用notify()唤醒的线程有没有什么顺序呢?
    使用notify()唤醒线程的顺序是正序、倒序、还是随机的,这取决与JVM的具体实现,并不是所有的JVM在执行notify()时都是按照wait()的执行顺序进行唤醒的,也不是所有的notidyAll()都是按照wait()方法的倒序进行唤醒的,这取决于JVM的具体实现。
    wait()和notify()不能唤醒指定的线程。

    wait()和sleep()的区别

    也可以让wait()等待指定的时间,如果超过给定的时间,wait()不会无限期的等待下去.
    在这里插入图片描述
    没有被notify()唤醒,过了1000毫秒之后会自动停止。
    在这里插入图片描述

    wait()在不传入任何参数的时候,线程会进入waiting 的状态,而在wait()中加入一个大于0的参数的时候,线程会进入time_wating的状态。

    sleep()和wait()的区别 : 线程在sleep()的时候是不会释放锁的,而执行wait()的时候它就会释放锁。👇:

    package ThreadDeom;
    
    import jdk.nashorn.internal.ir.Block;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 8:45;
     */
    public class ThreadDemo29 {
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        try {
                            System.out.println("thread获取到了锁");
                            //如果sleep释放锁的话,会在thread获取到了锁和thread释放了锁之间打印
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
    
                    }
                    System.out.println("thread释放了锁");
    
                }
            });
            thread.start();
    
            //让thread 先获取到锁
            Thread.sleep(1000);
            synchronized (lock) {
                System.out.println("主线程获取到了锁");
            }
        }
    }
    
    

    在这里插入图片描述

    可以看到线程在sleep()的时候,线程是不会释放锁的。再来看看wait()方法👇:
    在这里插入图片描述

    在这里插入图片描述
    线程使用wait()的时候它就会释放掉锁。

    1.wait()和sleep()都是让线程进行休眠的
    2.wait()和sleep()方法都有可能在执行的过程接收到线程终止的通知
    3.wait()必须和synchronzied一起使用,而sleep()不用。
    4.wait()会释放锁,而sleep()不会释放锁。
    5.wait()时Object的方法,而sleep()时Thread的方法。
    6.默认情况下,wait()不传任何的参数的情况下,wait()会进入waiting的状态,如果传递了参数,wait()会进入time_waiting的状态。而sleep()进入的是time_waiting的状态。
    sleep(0) 和wait(0)的区别:
    1.sleep(0)表示0毫秒之后继续执行,而wait(0)表示线程会一直休眠下去wait(0)和wait()是一样的,wait()的源码就是调用了wait(0)方法。
    2.sleep(0)表示重新出发一次CPU的竞争。
    为什么wait()会释放锁,而sleep()不会释放锁?
    sleep()需要传递一个最大的等待时间,也就是说sleep()是可控的,而wait()是不可以传递参数的,从设计的层面来说,如果让wait()一直持有所得话,那么线程就可能一直阻塞。
    为什么wait()是Object的方法,而sleep()是线程的方法?
    wait()需要操作锁,而锁是属于对象级别的,所有的锁都是放在对象头中的,它不是线程级别的,一个线程可以有多把的锁,为了灵活,就将wait()放在Object中了。

    LockSupport park()/unpark()

    使用LockSupport可以解决wait()/notify()随机唤醒的问题。

    package ThreadDeom;
    
    import java.util.concurrent.locks.LockSupport;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 9:36;
     */
    public class ThreadDemo30 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //让线程休眠
                    LockSupport.park();
                    System.out.println("unPark()了thread1");
                }
            });
    
            Thread thread2 = new Thread(() -> {
                LockSupport.park();
                System.out.println("unPark()了thread2");
    
            });
    
    
            Thread thread3 = new Thread() {
                @Override
                public void run() {
                    LockSupport.park();
                    System.out.println("unPark()了thread3");
    
                }
            };
    
    
            thread1.start();
            thread2.start();
            thread3.start();
    
    
            LockSupport.unpark(thread1);
            LockSupport.unpark(thread2);
    
    
        }
    }
    
    

    在这里插入图片描述

    Java线程池

    线程的缺点:
    1.线程的创建它会开辟本地方法栈、JVM栈、程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源。
    2.在任务量远远大于线程可以处理的任务量的时候,不能很好的拒绝任务。
    所以就有了线程池:
    使用池化的而技术来管理和使用线程。

    线程池的优点

    1.可以避免频繁的创建和销毁线程
    2.可以更好的管理线程的个数和资源的个数。
    3.线程池拥有更多的功能,比如线程池可以进行定时任务的执行。
    4.线程池可以更友好的拒绝不能处理的任务。

    线程池的6种创建方式

    一共有7种创建方式
    创建方式一:
    创建固定个数的线程池:

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 10:24;
     */
    public class ThreadPoolDemo1 {
        public static void main(String[] args) {
            //创建一个固定个数的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            //执行任务
            for (int i = 0; i < 10; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名" + Thread.currentThread().getName());
                    }
                });
            }
    
        }
    }
    
    

    在这里插入图片描述
    那么如果执行次数大于10次呢?
    线程池不会创建新的线程,它会复用之前的线程。
    在这里插入图片描述

    在这里插入图片描述
    那么如果只执行两个任务呢?它创建了是10个线程还是两个线程呢?
    我们可以使用Jconsole来看一看:
    在这里插入图片描述

    在这里插入图片描述

    结果是只有2个线程被创建。

    创建方式二:
    创建带有缓存的线程池:
    适用于短期有大量的任务的时候使用

    public class ThreadPoolDemo2 {
        public static void main(String[] args) {
            //创建带缓存的线程池
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 100; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述

    方式三:
    创建执行定时任务的线程池

    package ThreadPoolDemo;
    
    import java.util.Date;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 11:32;
     */
    public class ThreadPoolDemo3 {
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    
            System.out.println("执行定时任务前的时间:" + new Date());
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务的时间:" + new Date());
                }
            },1,2, TimeUnit.SECONDS);
        }
    }
    
    

    在这里插入图片描述
    执行任务的四个参数的意义:
    参数1:延迟执行的任务
    参数2:延迟一段时间后执行
    参数3:定时任务执行的频率
    参数4:配合前两个参数使用,是2、3参数的时间单位

    还有两种执行的方法:
    只会执行一次的方法:
    在这里插入图片描述

    在这里插入图片描述
    第三种的执行方式:
    在这里插入图片描述

    在这里插入图片描述
    那么这种的执行方式和第一种的执行方式有什么区别呢?
    当在两种执行的方式中分别加上sleep()之后:
    在这里插入图片描述

    方式一:
    在这里插入图片描述

    方式三:
    在这里插入图片描述
    结论很明显了:
    第一种方式是以上一个任务的开始时间+定时的时间作为当前任务的开始时间

    第三种方式是以上一个任务的结束时间来作为当前任务的开始时间。

    创建方式四:

    package ThreadPoolDemo;
    
    import java.util.Date;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 12:38;
     */
    public class ThreadPoolDemo4 {
        public static void main(String[] args) {
            //创建单个执行任务的线程池
            ScheduledExecutorService scheduledExecutorService
                    = Executors.newSingleThreadScheduledExecutor();
            System.out.println("执行任务之前" + new Date());
            scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    System.out.println("我是SingleThreadSchedule"+ new Date());
                }
            },3,1, TimeUnit.SECONDS);
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    创建方式五:
    创建单个线程的线程池

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 12:55;
     */
    public class ThreadPoolDemo5 {
        public static void main(String[] args) {
            //创建单个线程的线程池
    
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 20; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名 " +  Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述
    创建单个线程池的作用是什么?
    1.可以避免频繁创建和销毁线程所带来的性能的开销
    2.它有任务队列,可以存储多余的任务
    3.可以更好的管理任务
    4.当有大量的任务不能处理的时候,可以友好的执行拒绝策略
    创建方式六:
    创建异步线程池根据当前CPU来创建对应个数的线程池

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 13:12;
     */
    public class ThreadPoolDemo6 {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newWorkStealingPool();
    
            for (int i = 0; i < 10; i++) { 
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名" + Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述
    运行结果为什么什么都没有呢?
    看下面的异步与同步的区别就知道了。
    加上这个
    在这里插入图片描述

    就可以输出结果了
    在这里插入图片描述

    线程池的第七种创建方式

    前六种的创建方式有什么问题呢?
    1.线程的数量不可控(比如带缓存的线程池)
    2.工作任务量不可控(默认的任务队列的大小时Integer.MAX_VALUE),任务比较大肯会导致内存的溢出。
    所以就可以使用下面的创建线程池的方式了:

    package ThreadPoolDemo;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 15:05;
     */
    public class ThreadPoolDemo7 {
        private static int threadId = 0;
    
        public static void main(String[] args) {
            ThreadFactory threadFactory = new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("我是threadPool-" + ++threadId);
                    return thread;
                }
            };
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
                    threadFactory, new ThreadPoolExecutor.AbortPolicy());
    
            for (int i = 0; i < 15; i++) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    
    

    在这里插入图片描述

    参数说明:
    在这里插入图片描述

    参数一:核心线程数|线程池正常情况下的线程 数量
    参数二:最大线程数|当有大量的任务的时候可以创建的最多的线程数
    参数三:最大线程的存活时间
    参数四:配合参数三一起使用的表示参数三的时间单位
    参数五:任务队列
    参数六:线程工厂
    参数七:决绝策略

    注意事项:最大的线程数要大于等于核心的线程数
    在这里插入图片描述

    在这里插入图片描述

    五种拒绝策略

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

    为什么拒绝策略可以舍弃最新的任务或者最旧的任务呢?
    因为LinkedBlockingDeque时FIFO的。
    第五种:自定义的拒绝策略
    在这里插入图片描述

    在这里插入图片描述

    ThreadPoolExecutor的执行方式

    在这里插入图片描述

    package ThreadPoolDemo;
    
    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 16:58;
     */
    public class ThreadPoolDemo9 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
    
    
            //线程池的执行方式一
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("使用了execute()执行了线程池");
                }
            });
    
            //线程池的执行方式二
    
            Future<String> futureTask =
                    threadPoolExecutor.submit(new Callable<String>() {
                        @Override
                        public String call() throws Exception {
                            return "使用submit(new Callable<>())执行了线程池";
                        }
                    });
    
            System.out.println(futureTask.get());
            
            
    
        }
    }
    
    

    无返回值的执行方式
    在这里插入图片描述

    有返回值的执行方式
    在这里插入图片描述

    ThreadPoolExecutor的执行流程

    当任务量小于核心线程数的时候,ThreadPoolExecutor会创建线程来执行任务
    当任务量大于核心的线程数的时候,并且没有空闲的线程时候,且当线程池的线程数小于最大线程数的时候,此时会将任务存放到任务队列中
    如果任务队列也被存满了,且最大线程数大于线程池的线程数的时候,会创建新的线程来执行任务。
    如果线程池的线程数等于最大的线程数,并且任务队列也已经满了,就会执行拒绝策略。👇
    在这里插入图片描述

    线程池的终止

    shutdown()
    线程池的任务会执行完
    shutdownNow()
    立即终止线程池,线程池的任务不会执行完

    线程池的状态

    在这里插入图片描述

    异步、同步

    1. Java 线程 同步与异步
      多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制

    同步
    A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去

    异步
    A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待
    同步的方式:
    1.发送请求
    2.等待执行完成
    3.有结果的返回

    异步的方式
    1.发请求
    2.执行完成
    3.另一个线程异步处理
    4.处理完成之后返回回调结果

    显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个进程崩溃,使用异步的机制,性能会有所提升

    线程工厂

    设想这样一种场景,我们需要一个线程池,并且对于线程池中的线程对象,赋予统一的线程优先级、统一的名称、甚至进行统一的业务处理或和业务方面的初始化工作,这时工厂方法就是最好用的方法了

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 11:12;
     */
    public class ThreadFactoryDemo {
        public static void main(String[] args) {
            MyThreadFactory myThreadFactory = new MyThreadFactory();
            ExecutorService executorService =  Executors.newFixedThreadPool(10,myThreadFactory);
    
            for (int i = 0; i < 10; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("使用线程工厂设置的线程名:"+ Thread.currentThread().getName() +
                                " 使用线程工厂设置的线程的优先级" + Thread.currentThread().getPriority());
                    }
                });
            }
    
    
    
        }
    
        private static int count = 0;
         static class MyThreadFactory implements ThreadFactory{
             @Override
             public Thread newThread(Runnable r) {
                 Thread thread = new Thread(r);
                 thread.setPriority(8);
                 thread.setName("thread--" + count++);
                 return thread;
             }
         }
    
    }
    
    
    
    

    在这里插入图片描述

    SimpleDateFormat非线程安全问题

    实现1000个线程的时间格式化

    package SimpleDateFormat;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 17:30;
     */
    public class SimpleDateFormat1 {
        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,100,
                    TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),new ThreadPoolExecutor.DiscardPolicy());
    
    
            for (int i = 0; i < 1001; i++) {
                int finalI = i;
                threadPoolExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        Date date = new Date(finalI * 1000);
                        myFormatTime(date);
                    }
                });
            }
    
            threadPoolExecutor.shutdown();
        }
    
        private static void myFormatTime(Date date){
            System.out.println(simpleDateFormat.format(date));
        }
    
    }
    
    

    产生了线程不安全的问题👇:

    在这里插入图片描述
    这是因为:
    在这里插入图片描述
    多线程的情况下:
    在这里插入图片描述
    线程1在时间片用完之后,线程2来setTime()那么线程1的得到了线程2的时间。

    所以可以使用加锁的操作:

    在这里插入图片描述

    就不会有重复的时间了
    在这里插入图片描述
    但是虽然可以解决线程不安全的问题,但是排队等待锁,性能就会变得低

    所以可以使用局部变量:
    在这里插入图片描述
    也解决了线程不安全的问题:
    在这里插入图片描述

    但是每次也都会创建新的私有变量
    那么有没有一种方案既可以避免加锁排队执行,又不会每次创建任务的时候不会创建私有的变量呢?
    那就是ThreadLocal👇:

    ThreadLocal

    ThreadLocal的作用就是让每一个线程都拥有自己的变量。
    那么选择锁还是ThreadLocal?
    看创建实列对象的复用率,如果复用率比较高的话,就使用ThreadLocal。

    ThreadLocal的原理

    类ThreadLocal的主要作用就是将数据放到当前对象的Map中,这个Map时thread类的实列变量。类ThreadLocal自己不管理、不存储任何的数据,它只是数据和Map之间的桥梁。
    执行的流程:数据—>ThreadLocal—>currentThread()—>Map。
    执行后每个Map存有自己的数据,Map中的key中存储的就是ThreadLocal对象,value就是存储的值。每个Thread的Map值只对当前的线程可见,其它的线程不可以访问当前线程对象中Map的值。当前的线程被销毁,Map也随之被销毁,Map中的数据如果没有被引用、没有被使用,则随时GC回收。

    ThreadLocal常用方法

    在这里插入图片描述

    set(T):将内容存储到ThreadLocal
    get():从线程去私有的变量
    remove():从线程中移除私有变量

    package ThreadLocalDemo;
    
    import java.text.SimpleDateFormat;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 18:37;
     */
    public class ThreadLocalDemo1 {
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
            //设置私有变量
            threadLocal.set(new SimpleDateFormat("mm:ss"));
    
            //得到ThreadLocal
            SimpleDateFormat simpleDateFormat = threadLocal.get();
            
            //移除
            threadLocal.remove();
        }
    }
    
    

    ThreadLocal的初始化

    ThreadLocal提供了两种初始化的方法
    initialValue()和
    initialValue()初始化:

    package ThreadLocalDemo;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 19:07;
     */
    public class ThreadLocalDemo2 {
        //创建并初始化ThreadLocal
    
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal() {
            @Override
            protected SimpleDateFormat initialValue() {
                System.out.println(Thread.currentThread().getName() + "执行了自己的threadLocal中的初始化方法initialValue()");
                return new SimpleDateFormat("mm:ss");
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                Date date = new Date(5000);
                System.out.println("thread0格式化时间之后得结果时:" + threadLocal.get().format(date));
            });
            thread1.setName("thread0");
            thread1.start();
    
    
            Thread thread2 = new Thread(() -> {
                Date date = new Date(6000);
                System.out.println("thread1格式化时间之后得结果时:" + threadLocal.get().format(date));
            });
            thread2.setName("thread1");
    
            thread2.start();
    
        }
    }
    
    

    在这里插入图片描述
    withInitial方法初始化:

    package ThreadLocalDemo;
    
    import java.util.function.Supplier;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 17:23;
     */
    public class ThreadLocalDemo3 {
        private static ThreadLocal<String> stringThreadLocal =
                ThreadLocal.withInitial(new Supplier<String>() {
                    @Override
                    public String get() {
                        System.out.println("执行了withInitial()方法");
                        return "我是" + Thread.currentThread().getName() + "的ThreadLocal";
                    }
                });
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                System.out.println(stringThreadLocal.get());
            });
    
            thread1.start();
    
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(stringThreadLocal.get());
                }
            });
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述
    注意:
    ThreadLocal如果使用了set()方法的话,那么它的初始化方法就不会起作用了。
    来看:👇

    package ThreadLocalDemo;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 18:43;
     */
    
    class Tools {
        public static ThreadLocal t1 = new ThreadLocal();
    }
    
    class ThreadA extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA中取值:" + Tools.t1.get());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class ThreadLocalDemo4 {
        public static void main(String[] args) throws InterruptedException {
            //main是ThreadA 的 父线程 让main线程set,ThreadA,是get不到的
    
            if (Tools.t1.get() == null) {
                Tools.t1.set("main父线程的set");
            }
    
            System.out.println("main get 到了: " + Tools.t1.get());
    
    
            Thread.sleep(1000);
            ThreadA a = new ThreadA();
            a.start();
    
        }
    }
    
    

    在这里插入图片描述
    类ThreadLocal不能实现值的继承,那么就可以使用InheritableThreadLocal了👇

    InheritableThreadLocal的使用

    使用InheritableThreadLocal可以使子线程继承父线程的值

    在这里插入图片描述
    在来看运行的结果:
    在这里插入图片描述

    子线程有最新的值,父线程依旧是旧的值

    package ThreadLocalDemo;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 19:07;
     */
    class ThreadB extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadB中取值:" + Tools.t1.get());
                if (i == 5){
                    Tools.t1.set("我是ThreadB中新set()");
                }
                try {
                    Thread.sleep(100);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class ThreadLocalDemo5 {
        public static void main(String[] args) throws InterruptedException {
            if (Tools.t1.get() == null) {
                Tools.t1.set("main父线程的set");
            }
    
            System.out.println("main get 到了: " + Tools.t1.get());
    
    
            Thread.sleep(1000);
            ThreadA a = new ThreadA();
            a.start();
            Thread.sleep(5000);
    
            for (int i = 0; i < 10; i++) {
                System.out.println("main的get是:" + Tools.t1.get());
                Thread.sleep(100);
            }
    
        }
    }
    
    

    在这里插入图片描述
    ThreadLocal的脏读问题
    来看👇

    package ThreadLocalDemo;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 19:49;
     */
    
    public class ThreadLocalDemo6 {
        private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        private static class MyThread extends Thread {
            private static boolean flag = false;
    
            @Override
            public void run() {
                String name = this.getName();
    
                if (!flag) {
                    threadLocal.set(name);
                    System.out.println(name + "设置了" + name);
                    flag = true;
                }
    
                System.out.println(name + "得到了" + threadLocal.get());
            }
        }
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
    
    
            for (int i = 0; i < 2; i++) {
                threadPoolExecutor.execute(new MyThread());
            }
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述

    发生了脏读:
    线程池复用了线程,也复用了这个线程相关的静态属性,就导致了脏读
    那么如何避免脏读呢?
    去掉static 之后:
    在这里插入图片描述
    在这里插入图片描述

    单例模式与多线程

    单例模式就是全局唯一但是所有程序都可以使用的对象
    写单例模式步骤:
    1.将构造函数设置为私有的
    2.创建一个静态的类变量
    3.提供获取单例的方法

    立即加载/饿汉模式

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:02;
     */
    //饿汉方式实现单例模式
    public class Singleton {
        //1.将构造函数设置为私有的,不然外部可以创建
        private Singleton(){
        }
        
        //2.创建静态的类变量(让第三步的方法进行返回)
        private static Singleton singleton = new Singleton();
        
        //给外部接口提供的获取单例的方法
        public static Singleton getInstance(){
            return singleton;
        }
        
    }
    
    

    测试饿汉的单例模式

        //测试饿汉方式实现的单例模式,创建两个线程,看是不是得到了一个实列对象,如果为true就说明饿汉的单例模式没有问题
    
        static Singleton singleton1 = null;
        static Singleton singleton2 = null;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                singleton1 = Singleton.getInstance();
            });
            Thread thread2 = new Thread(() -> {
                singleton2 = Singleton.getInstance();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(singleton1 == singleton2);
        }
    
    

    在这里插入图片描述

    延时加载/懒汉模式

    不会随着程序的启动而启动,而是等到有人调用它的时候,它才会初始化

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    singleton = new Singleton();
                }
    
                return singleton;
            }
        }
    }
    
    

    那么这样写有什么问题呢?
    我们来看看多线程情况下的懒汉方式实现单例模式:

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() throws InterruptedException {
                if (singleton == null) {
                    Thread.sleep(100);
                    singleton = new Singleton();
                }
    
                return singleton;
            }
        }
    
        static Singleton singleton1 = null;
        static Singleton singleton2 = null;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                try {
                    singleton1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread thread2 = new Thread(() -> {
                try {
                    singleton2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(singleton1 == singleton2);
        }
    
    }
    
    

    结果:
    在这里插入图片描述

    所以发生了线程不安全的问题
    那么要如何更改呢?
    加锁:👇
    在这里插入图片描述
    结果就是true了:
    在这里插入图片描述
    给方法加锁可以实现线程安全,但是所锁的粒度太大。
    使用双重校验锁优化后:

        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    synchronized (Singleton.class) {
                        if (singleton == null) {
                            singleton = new Singleton();
                        }
                    }
                }
    
                return singleton;
            }
        }
    
    

    在这里插入图片描述
    那么这样写就没有问题了吗?

    不是的:有可能还会发生指令重排的问题
    当有线程在进行第一次初始化的时候,就有可能发生问题👇
    先来看初始化的过程
    1。先分配内存空间
    2.初始化
    3.将singleton指向内存

    有可能指令重排序之后:
    线程1执行的顺序变成了 1 --> 3 --> 2
    在线程1执行完1、3之后时间片使用完了
    线程2再来执行,线程2得到了未初始化的singleton,也就是的到了一个空的对象
    也就发生了线程不安全的问题

    那么要如何解决指令重排序的问题呢?
    那就是使用volatile关键字👇:

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static volatile Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    synchronized (Singleton.class) {
                        if (singleton == null) {
                            singleton = new Singleton();
                        }
                    }
                }
    
                return singleton;
            }
        }
    
    
    

    这样就没有问题了

    饿汉/懒汉对比

    饿汉方式:
    优点:实现简单,不存在线程安全的问题,因为饿汉的方式是随着程序的启动而初始化的,因为类加载是线程安全的,所以它是线程安全的。
    缺点:随着程序的启动而启动,有可能在整个程序的运行周期都没有用到,这样就带来了不必要的开销。

    阻塞队列的实现

    import java.util.Random;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 8:57;
     */
    public class MyBlockingQueue {
        private int[] values;
        private int first;
        private int last;
        private int size;
    
        MyBlockingQueue(int maxSize) {
            this.values = new int[maxSize];
            this.first = 0;
            this.last = 0;
            this.size = 0;
        }
    
        public void offer(int val) throws InterruptedException {
    
            synchronized (this) {
                if (this.size == values.length) {
                    this.wait();
                }
                this.values[last++] = val;
                size++;
    
                //变为循环队列
                if (this.last == values.length) {
                    this.last = 0;
                }
    
                //唤醒消费者
                this.notify();
            }
    
        }
    
        public int poll() throws InterruptedException {
            int result = 0;
            synchronized (this) {
                if (size == 0) {
                    this.wait();
                }
                result = this.values[first++];
                this.size--;
                if (first == this.values.length) {
                    this.first = 0;
                }
                //唤醒生产者开生产数据
                this.notify();
    
            }
            return result;
        }
    
        public static void main(String[] args) {
            MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
            //生产者
            Thread thread1 = new Thread(() -> {
    
                while (true) {
                    try {
                        int num = new Random().nextInt(100);
                        myBlockingQueue.offer(num);
                        System.out.println("生产者生产数据:" + num);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                }
            });
    
    
            //消费者
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            int res = myBlockingQueue.poll();
    
                            System.out.println("消费者消费数据:" + res);
                        }
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }
    
    

    可以看到生产者每生产一个数据都会被取走:
    在这里插入图片描述

    常见的锁策略

    乐观锁

    它认为程序在一般的情况下不会发生问题,所以他在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有就会返回失败信息给用户自行处理。

    CAS

    乐观锁的经典实现
    Compare and Swap
    CAS 实现的三个重要的属性:
    (V,A,B)

    V:内存中的值
    A:预期的旧值
    B:新值
    V == A? V -> B : 修改失败

    修改失之后:
    自旋对比和替换
    CAS 的底层实现:
    CAS在Java中是通过unsafe来实现的,unsafe时本地类和本地方法,它是c/c++实现的原生方法,通过调用操作系统Atomic:: cmpxchg原子指令来实现的

    CAS在java中的应用

    i++、i–问题
    可以使用加锁、ThreadLocal 解决问题
    也可以使用atomic.AtomicInteger来解决问题,底层也使用了乐观锁。

    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 10:12;
     */
    public class ThreadDemo1 {
        private static AtomicInteger count  = new AtomicInteger(0);
        private static final int MaxSize = 100000;
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < MaxSize; i++) {
                        count.getAndIncrement();//i++
                    }
                }
            });
    
            thread1.start();
    
            Thread thread2 = new Thread(()->{
                for (int i = 0; i < MaxSize; i++) {
                 count.getAndDecrement();//i--
                }
            });
    
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(count);
        }
    }
    
    

    在这里插入图片描述

    CAS 的ABA问题

    当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。
    来看:

    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 10:43;
     */
    public class ThreadDemo2 {
        //线程操作资源,原子类ai的初始值为4
        static AtomicInteger ai = new AtomicInteger(4);
        public static void main(String[] args) {
            new Thread(() -> {
                //利用CAS将ai的值改成5
                boolean b = ai.compareAndSet(4, 5);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);
                //休眠一秒
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                //利用CAS将ai的值改回4
                b = ai.compareAndSet(5,4);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);
            },"A").start();
            new Thread(() -> {
                //模拟此线程执行较慢的情况
                try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
                //利用CAS将ai的值从4改为10
                boolean b = ai.compareAndSet(4, 10);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);
            },"B").start();
    
            //等待其他线程完成,为什么是2,因为一个是main线程,一个是后台的GC线程
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
    
            System.out.println("ai最终的值为:"+ai.get());
        }
    }
    
    

    上面例子模拟的是A、B两个线程操作一个资源ai,A的执行速度比B的快,在B执行前,A就已经将ai的值改为5之后马上又把ai的值改回为4,但是B不感知,所以最后B就修改成功了。

    那么会造成会有什么问题呢?
    假设A现在有100元,要给B转账100元,点击了两次转账按钮,第一次B只会得到100元,A现在剩余0元。第二次A是0元,预期的旧值是100,不相等,就不会执行转账操作。
    如果点击第二次按钮之前,A又得到了100元,B不能感知的到,此时A得到了转账100元,预期的旧值就是100,又会转给B100元。

    那么如何解决这个问题呢?👇

    ABA 问题的解决

    我们可以给操作加上版本号,每次修改的时候判断版本号和预期的旧值,如果不一样就不会执行操作了。
    即是预期的旧值和V值相等,但是版本号不一样,也不会执行操作。
    在Java中的实现:

    
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 11:05;
     */
    public class ThreadDemo3 {
        static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);
        public static void main(String[] args) {
            new Thread(() -> {
                //四个参数分别是预估内存值,更新值,预估版本号,初始版本号
                //只有当预估内存值==实际内存值相等并且预估版本号==实际版本号,才会进行修改
                boolean b = ai.compareAndSet(4, 5,0,1);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                b = ai.compareAndSet(5,4,1,2);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);
            },"A").start();
            new Thread(() -> {
                try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
                boolean b = ai.compareAndSet(4, 10,0,1);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);
            },"B").start();
    
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
    
            System.out.println("ai最终的值为:"+ai.getReference());
        }
    }
    
    

    在这里插入图片描述

    注意:里面的旧值对比的是引用。
    如果范围在-128 - 127 里,会使用缓存的值,如果超过了这个范围,就会重新来new对象
    可以将Integer 的高速缓存的值的边界调整

    悲观锁

    悲观锁认为只要执行多线程的任务,就会发生线程不安全的问题,所以正在进入方法之后会直接加锁。
    直接使用synchronzied关键字给方法加锁就可以了

    独占锁、共享锁、自旋锁、可重入锁

    独占锁:指的是这一把锁只能被一个线程所拥有
    比如:synchronzied、Lock
    共享锁: 指的是一把锁可以被多个线程同时拥有
    ReadWriterLock读写锁就是共享锁
    读锁就是共享的,将锁的粒度更加的细化

    import java.util.Date;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 11:42;
     */
    public class ThreadDemo4 {
        //创建读写锁
        public static void main(String[] args) {
            ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
            //读锁
    
            ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
            //写锁
            ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
    
    
            //任务一:读锁演示
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了读锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            });
    
            //任务二:读锁演示
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了读锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            });
    
            //任务三:写锁
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了写锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                }
            });
            //任务四:写锁
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了写锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                }
            });
    
    
        }
    }
    
    

    在这里插入图片描述

    可重入锁:
    当一个线程拥有了锁之后,可以重复的进入,就叫可重入锁。
    synchronzied就是典型的可重入锁的代表
    读锁的时间在一秒内,所以两个线程读到的锁是一把锁,即读锁是共享锁
    而写锁的时间刚好是一秒,所以写锁是独占锁。

    在这里插入图片描述

    在这里插入图片描述

    自旋锁:相当于死循环,一直尝试获取锁

    详解synchronized锁的优化问题

    synchroized加锁的整个过程,都是依赖于Monitor(监视器锁)实现的,监视器锁在虚拟机中又是根据操作系统的Metux Lock(互斥量)来实现的,这就导致在加锁的过程中需要频繁的在操作系统的内核态和和JVM级别的用户态进行切换,并且涉及到线程上下文的切换,是比较消耗性能的。所以后来有一位大佬Doug Lea基于java实现了一个AQS的框架,提供了Lock锁,性能远远高于synchroized。这就导致Oracle公司很没有面子,因此他们在JDK1.6对synchroized做了优化,引入了偏向锁和轻量级锁。存在一个从无锁-》偏向锁–》轻量级锁–》重量级锁的升级过程,优化后性能就可以和Lock锁的方式持平了。
    对象头
    HotSpot虚拟机中,对象在内存中分为三块区域:对象头、实例数据和对齐填充。
    在这里插入图片描述

    对象头包括两部分:Mark Word 和 类型指针。类型指针是指向该对象所属类对象的指针,我们不关注。mark word用于存储对象的HashCode、GC分代年龄、锁状态等信息。在32位系统上mark word长度为32bit,64位系统上长度为64bit。他不是一个固定的数据结构,是和对象的状态紧密相关,有一个对应关系的,具体如下表所示:

    在这里插入图片描述

    当某一线程第一次获得锁的时候,虚拟机会把对象头中的锁标志位设置为“01”,把偏向模式设置为“1”,表示进入偏向锁模式。同时使用CAS操作将获取到这个锁的线程的ID记录在对象的Mark Word中。如果CAS操作成功,持有偏向锁的线程每次进入这个锁的相关的同步块的时候。虚拟机都可以不在进行任何的同步操作。

    当其他线程进入同步块时,发现已经有偏向的线程了,偏向模式马上结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向,也就是将偏向模式设置为“0”,撤销后标志位恢复到“01”,也就是未锁定的状态或者轻量级锁定,标志位为“00”的状态,后续的同步操作就按照下面的轻量级锁那样去执行
    1、在线程进入同步块的时候,如果同步对象状态为无锁状态(锁标志为 01),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用来存储锁对象目前的 Mark Word 的拷贝。拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record 里的 owner 指针指向锁对象的 Mark Word。如果更新成功,则执行 2,否则执行 3。

    在这里插入图片描述
    2、如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且锁对象的 Mark Word 中的锁标志位设置为 “00”,即表示此对象处于轻量级锁定状态,这时候虚拟机线程栈与堆中锁对象的对象头的状态如图所示。
    在这里插入图片描述
    3、如果这个更新操作失败了,虚拟机首先会检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重要量级锁,锁标志的状态值变为 “10”,Mark Word 中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁。自旋失败后膨胀为重量级锁,被阻塞。

    Semaphore

    Semaphore的作用:

    在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。也就是做限流的作用

    Semaphore实现原理:

    Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

    如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

    就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

    Semaphore的使用:

    Semaphore使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。

    使用Semaphore 来模拟有四辆车同时到达了停车场的门口,但是停车位只有两个,也就是只能停两辆车,这就可以使用信号量来实现。👇:

    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 14:00;
     */
    public class ThreadDemo6 {
        public static void main(String[] args) {
            Semaphore semaphore = new Semaphore(2);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(1000);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(2000);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(500);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(1500);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述

    CountDownLatch\CyclicBarrier

    CountDownLatch
    一个可以用来协调多个线程之间的同步,或者说起到线程之间的通信作用的工具类。

    它能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。

    CountDownLatch的用法

    某一线程在开始运行前等待n个线程执行完毕。
    将CountDownLatch的计数器初始化为n:new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1, countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

    实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

    CountDownLatch的不足
    CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。

    在这里插入图片描述
    模拟赛跑:当三个运动员都到达终点的时候宣布比赛结束

    import java.util.Random;
    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 14:27;
     */
    public class ThreadDemo7 {
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(3);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
            countDownLatch.await();
            System.out.println("所有的选手都到达了终点");
            threadPoolExecutor.shutdown();
        }
    }
    

    在这里插入图片描述
    CyclicBarrier

    CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

    CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 15:03;
     */
    public class ThreadDemo8 {
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
                @Override
                public void run() {
                    System.out.println("到达了循环屏障");
                }
            });
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    
            for (int i = 0; i < 10; i++) {
    
                int finalI = i;
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
    
                        try {
                            Thread.sleep(finalI * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "进入了任务");
    
                        try {
    
                            cyclicBarrier.await();
    
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "退出了任务");
    
                    }
                });
            }
    
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述
    CyclicBarrier原理
    每当线程执行await,内部变量count减1,如果count!= 0,说明有线程还未到屏障处,则在锁条件变量trip上等待。
    当count == 0时,说明所有线程都已经到屏障处,执行条件变量的signalAll方法唤醒等待的线程。
    其中 nextGeneration方法可以实现屏障的循环使用:
    重新生成Generation对象
    恢复count值
    CyclicBarrier可以循环的使用。

    hashmap/ConcurrentHashMap

    hashmap在JDK1.7中头插死循环问题

    来看👇JDK1.7 hashMap transfer的源码

    void transfer(Entry[] newTable, boolean rehash) {
            int newCapacity = newTable.length;
            for (Entry<K,V> e : table) {
                while(null != e) {
                    Entry<K,V> next = e.next;
                    if (rehash) {
                        e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
    

    来看多线程情况下的问题:
    在这里插入图片描述
    这样就会造成死循环。

    hashmap在JDK1.8中值覆盖问题

    在JDK1.8的时候使用的是尾插法
    来看👇:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    
    
    

    在多线程的情况下:
    在这里插入图片描述
    其中第六行代码是判断是否出现hash碰撞,假设两个线程1、2都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程1执行完第六行代码后由于时间片耗尽导致被挂起,而线程2得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程2插入的数据被线程1覆盖了,从而线程不安全。

    除此之前,还有就是代码的第38行处有个++size,我们这样想,还是线程1、2,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程1执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程2快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程1再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程1、2都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。

    ConcurrentHashMap & HashTable

    来看这个🤣

    展开全文
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...

    引言

    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。
    那么话不多说,今天本帅将记录自己线程的学习。

    程序,进程,线程的基本概念+并行与并发:

    程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
    进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
    线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

    即:线程《线程(一个程序可以有多个线程)
    程序:静态的代码 进程:动态执行的程序
    线程:进程中要同时干几件事时,每一件事的执行路径成为线程。

    并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
    并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

    线程的相关API

    //获取当前线程的名字
    Thread.currentThread().getName()

    1.start():1.启动当前线程2.调用线程中的run方法
    2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3.currentThread():静态方法,返回执行当前代码的线程
    4.getName():获取当前线程的名字
    5.setName():设置当前线程的名字
    6.yield():主动释放当前线程的执行权
    7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
    8.stop():过时方法。当执行此方法时,强制结束当前线程。
    9.sleep(long millitime):线程休眠一段时间
    10.isAlive():判断当前线程是否存活

    判断是否是多线程

    一条线程即为一条执行路径,即当能用一条路径画出来时即为一个线程
    例:如下看似既执行了方法一,又执行了方法2,但是其实质就是主线程在执行方法2和方法1这一条路径,所以就是一个线程

    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");
    	}
    }
    

    在这里插入图片描述

    线程的调度

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

    线程的优先级

    等级:
    MAX_PRIORITY:10
    MIN_PRIORITY:1
    NORM_PRIORITY:5

    方法:
    getPriority():返回线程优先级
    setPriority(int newPriority):改变线程的优先级

    注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

    多线程的创建方式

    1. 方式1:继承于Thread类

    1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
    2.重写Thread类的run()方法
    3.创建Thread子类的对象
    4.通过此对象调用start()方法

    start与run方法的区别:

    start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
    调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
    run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

    总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
    在这里插入图片描述

    多线程例子(火车站多窗口卖票问题)

    	package com.example.paoduantui.Thread;
    	
    	import android.view.Window;
    	
    	/**
    	 *
    	 * 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
    	 * 用静态变量保证三个线程的数据独一份
    	 * 
    	 * 存在线程的安全问题,有待解决
    	 *
    	 * */
    	
    	public class ThreadDemo extends Thread{
    	
    	    public static void main(String[] args){
    	        window t1 = new window();
    	        window t2 = new window();
    	        window t3 = new window();
    	
    	        t1.setName("售票口1");
    	        t2.setName("售票口2");
    	        t3.setName("售票口3");
    	
    	        t1.start();
    	        t2.start();
    	        t3.start();
    	    }
    	
    	}
    	
    	class window extends Thread{
    	    private static int ticket = 100; //将其加载在类的静态区,所有线程共享该静态变量
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    2. 方式2:实现Runable接口方式

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

    具体操作,将一个类实现Runable接口,(插上接口一端)。
    另外一端,通过实现类的对象与线程对象通过此Runable接口插上接口实现

    	package com.example.paoduantui.Thread;
    	
    	public class ThreadDemo01 {
    	    
    	    public static  void main(String[] args){
    	        window1 w = new window1();
    	        
    	        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
    	        
    	        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();
    	    }
    	}
    	
    	class window1 implements Runnable{
    	    
    	    private int ticket = 100;
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    比较创建线程的两种方式:
    开发中,优先选择实现Runable接口的方式
    原因1:实现的方式没有类的单继承性的局限性
    2:实现的方式更适合用来处理多个线程有共享数据的情况
    联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

    3.新增的两种创建多线程方式

    1.实现callable接口方式:

    与使用runnable方式相比,callable功能更强大些:
    runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
    方法可以抛出异常
    支持泛型的返回值
    需要借助FutureTask类,比如获取返回结果

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * 创建线程的方式三:实现callable接口。---JDK 5.0新增
     *是否多线程?否,就一个线程
     *
     * 比runable多一个FutureTask类,用来接收call方法的返回值。
     * 适用于需要从线程中接收返回值的形式
     * 
     * //callable实现新建线程的步骤:
     * 1.创建一个实现callable的实现类
     * 2.实现call方法,将此线程需要执行的操作声明在call()中
     * 3.创建callable实现类的对象
     * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
     * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
     * 
     * */
    
    
    //实现callable接口的call方法
    class NumThread implements Callable{
    
        private int sum=0;//
    
        //可以抛出异常
        @Override
        public Object call() throws Exception {
            for(int i = 0;i<=100;i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    
    public class ThreadNew {
    
        public static void main(String[] args){
            //new一个实现callable接口的对象
            NumThread numThread = new NumThread();
    
            //通过futureTask对象的get方法来接收futureTask的值
            FutureTask futureTask = new FutureTask(numThread);
    
            Thread t1 = new Thread(futureTask);
            t1.setName("线程1");
            t1.start();
    
            try {
                //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
               Object sum = futureTask.get();
               System.out.println(Thread.currentThread().getName()+":"+sum);
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    使用线程池的方式:

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

    JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
    void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
    Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池。

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

    线程池构造批量线程代码如下:

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 创建线程的方式四:使用线程池(批量使用线程)
     *1.需要创建实现runnable或者callable接口方式的对象
     * 2.创建executorservice线程池
     * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
     * 4.关闭线程池
     *
     * */
    
    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==1){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
    }
    
    public class ThreadPool {
    
        public static void main(String[] args){
    
            //创建固定线程个数为十个的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            //new一个Runnable接口的对象
            NumberThread number = new NumberThread();
            NumberThread1 number1 = new NumberThread1();
    
            //执行线程,最多十个
            executorService.execute(number1);
            executorService.execute(number);//适合适用于Runnable
    
            //executorService.submit();//适合使用于Callable
            //关闭线程池
            executorService.shutdown();
        }
    
    }
    

    目前两种方式要想调用新线程,都需要用到Thread中的start方法。

    java virtual machine(JVM):java虚拟机内存结构

    程序(一段静态的代码)——————》加载到内存中——————》进程(加载到内存中的代码,动态的程序)
    进程可细分为多个线程,一个线程代表一个程序内部的一条执行路径
    每个线程有其独立的程序计数器(PC,指导着程序向下执行)与运行栈(本地变量等,本地方法等)
    在这里插入图片描述

    大佬传送门:https://blog.csdn.net/bluetjs/article/details/52874852

    线程通信方法:

    wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

    由于wait,notify,以及notifyAll都涉及到与锁相关的操作
    wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
    notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

    有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

    所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

    线程的分类:

    java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

    若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

    线程的生命周期:

    JDK中用Thread.State类定义了线程的几种状态,如下:

    线程生命周期的阶段描述
    新建当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    就绪处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
    运行当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
    阻塞在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
    死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    在这里插入图片描述

    线程的同步:在同步代码块中,只能存在一个线程。

    线程的安全问题:

    什么是线程安全问题呢?
    线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

    上述例子中:创建三个窗口卖票,总票数为100张票
    1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
    2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
    生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
    3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
    4.在java中,我们通过同步机制,来解决线程的安全问题。

    方式一:同步代码块
    使用同步监视器(锁)
    Synchronized(同步监视器){
    //需要被同步的代码
    }
    说明:

    1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
    2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
    3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

    Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

    方式二:同步方法
    使用同步方法,对方法进行synchronized关键字修饰
    将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
    对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
    而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

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

    方式三:JDK5.0新增的lock锁方法

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.locks.ReentrantLock;
    
    class Window implements Runnable{
        private int ticket = 100;//定义一百张票
        //1.实例化锁
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            
                while (true) {
    
                    //2.调用锁定方法lock
                    lock.lock();
    
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票");
                        ticket--;
                    } else {
                        break;
                    }
                }
    
    
            }
    }
    
    public class LockTest {
    
        public static void main(String[] args){
           Window w= new Window();
    
           Thread t1 = new Thread(w);
           Thread t2 = new Thread(w);
           Thread t3 = new Thread(w);
    
           t1.setName("窗口1");
           t2.setName("窗口1");
           t3.setName("窗口1");
    
           t1.start();
           t2.start();
           t3.start();
        }
    
    }
    

    总结:Synchronized与lock的异同?

    相同:二者都可以解决线程安全问题
    不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
    lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

    优先使用顺序:
    LOCK-》同步代码块-》同步方法

    判断线程是否有安全问题,以及如何解决:

    1.先判断是否多线程
    2.再判断是否有共享数据
    3.是否并发的对共享数据进行操作
    4.选择上述三种方法解决线程安全问题

    例题:

    	package com.example.paoduantui.Thread;
    	
    	/***
    	 * 描述:甲乙同时往银行存钱,存够3000
    	 *
    	 *
    	 * */
    	
    	//账户
    	class Account{
    	    private double balance;//余额
    	    //构造器
    	    public Account(double balance) {
    	        this.balance = balance;
    	    }
    	    //存钱方法
    	    public synchronized void deposit(double amt){
    	        if(amt>0){
    	            balance +=amt;
    	            try {
    	                Thread.sleep(1000);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	            System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
    	        }
    	    }
    	}
    	
    	//两个顾客线程
    	class Customer extends Thread{
    	     private Account acct;
    	
    	     public Customer(Account acct){
    	         this.acct = acct;
    	     }
    	
    	
    	
    	    @Override
    	    public void run() {
    	        for (int i = 0;i<3;i++){
    	            acct.deposit(1000);
    	        }
    	    }
    	}
    	
    	//主方法,之中new同一个账户,甲乙两个存钱线程。
    	public class AccountTest {
    	
    	    public static void main(String[] args){
    	        Account acct = new Account(0);
    	        Customer c1 = new Customer(acct);
    	        Customer c2 = new Customer(acct);
    	
    	        c1.setName("甲");
    	        c2.setName("乙");
    	
    	        c1.start();
    	        c2.start();
    	    }
    	
    	}
    

    解决单例模式的懒汉式的线程安全问题:

    单例:只能通过静态方法获取一个实例,不能通过构造器来构造实例
    1.构造器的私有化:
    private Bank(){}//可以在构造器中初始化东西
    private static Bank instance = null;//初始化静态实例

    public static Bank getInstance(){
    if(instance!=null){
    instance = new Bank();
    }
    return instance;
    }

    假设有多个线程调用此单例,而调用的获取单例的函数作为操作共享单例的代码块并没有解决线程的安全问题,会导致多个线程都判断实例是否为空,此时就会导致多个实例的产生,也就是单例模式的线程安全问题。

    解决线程安全问题的思路:

    1. 将获取单例的方法改写成同部方法,即加上synchronized关键字,此时同步监视器为当前类本身。(当有多个线程并发的获取实例时,同时只能有一个线程获取实例),解决了单例模式的线程安全问题。
    2. 用同步监视器包裹住同步代码块的方式。

    懒汉式单例模式的模型,例如:生活中的限量版的抢购:
    当一群人并发的抢一个限量版的东西的时候,可能同时抢到了几个人,他们同时进入了房间(同步代码块内)
    但是只有第一个拿到限量版东西的人才能到手,其余人都不能拿到,所以效率稍高的做法是,当东西被拿走时,我们在门外立一块牌子,售罄。
    这样就减少了线程的等待。即下面效率稍高的懒汉式写法:

    package com.example.paoduantui.Thread;
    
    public class Bank {
        //私有化构造器
        private Bank(){}
        //初始化静态实例化对象
        private static  Bank instance = null;
    
        //获取单例实例,此种懒汉式单例模式存在线程不安全问题(从并发考虑)
    
        public static  Bank getInstance(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
    
        //同步方法模式的线程安全
        public static synchronized Bank getInstance1(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
        //同步代码块模式的线程安全(上锁)
        public  static Bank getInstance2(){
            synchronized (Bank.class){
                if(instance==null){
                    instance = new Bank();
                }
                return  instance;
            }
        }
        
        //效率更高的线程安全的懒汉式单例模式
        /**
         * 由于当高并发调用单例模式的时候,类似于万人夺宝,只有第一个进入房间的人才能拿到宝物,
         * 当多个人进入这个房间时,第一个人拿走了宝物,也就另外几个人需要在同步代码块外等候,
         * 剩下的人只需要看到门口售罄的牌子即已知宝物已经被夺,可以不用进入同步代码块内,提高了效率。
         * 
         * 
         * */
        public static Bank getInstance3(){
            if (instance==null){
                synchronized (Bank.class){
                    if(instance==null){
                        instance = new Bank();
                    }
                }
            }
            return  instance;
        }
    }
    

    线程的死锁问题:

    线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
    出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续

    package com.example.paoduantui.Thread;
    
    
    /**
     * 演示线程的死锁问题
     *
     * */
    public class Demo {
    
        public static void main(String[] args){
    
            final StringBuffer s1 = new StringBuffer();
            final StringBuffer s2 = new StringBuffer();
    
    
            new Thread(){
                @Override
                public void run() {
                    //先拿锁一,再拿锁二
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        synchronized (s2) {
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            //使用匿名内部类实现runnable接口的方式实现线程的创建
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
    
                        synchronized (s1) {
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    
    }
    

    运行结果:
    1.先调用上面的线程,再调用下面的线程:
    在这里插入图片描述
    2.出现死锁:
    在这里插入图片描述
    3.先调用下面的线程,再调用上面的线程。
    在这里插入图片描述

    死锁的解决办法:

    1.减少同步共享变量
    2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
    3.减少锁的嵌套。

    线程的通信

    通信常用方法:

    通信方法描述
    wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    notify一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
    notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

    使用前提:这三个方法均只能使用在同步代码块或者同步方法中。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的例子:使用两个线程打印1—100,线程1,线程2交替打印
     *
     * 当我们不采取线程之间的通信时,无法达到线程1,2交替打印(cpu的控制权,是自动分配的)
     * 若想达到线程1,2交替打印,需要:
     * 1.当线程1获取锁以后,进入代码块里将number++(数字打印并增加)操作完以后,为了保证下个锁为线程2所有,需要将线程1阻塞(线程1你等等wait())。(输出1,number为2)
     * 2.当线程2获取锁以后,此时线程1已经不能进入同步代码块中了,所以,为了让线程1继续抢占下一把锁,需要让线程1的阻塞状态取消(通知线程1不用等了notify()及notifyAll()),即应该在进入同步代码块时取消线程1的阻塞。
     *
     * */
    
    class Number implements Runnable{
    
        private int number = 1;//设置共享数据(线程之间对于共享数据的共享即为通信)
    
    
        //对共享数据进行操作的代码块,需要线程安全
        @Override
        public synchronized void run() {
    
            while(true){
                //使得线程交替等待以及通知交替解等待
                notify();//省略了this.notify()关键字
                if(number<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
    
    public class CommunicationTest {
    
        public static void main(String[] args){
            //创建runnable对象
            Number number = new Number();
    
            //创建线程,并实现runnable接口
            Thread t1 = new Thread(number);
            Thread t2 = new Thread(number);
    
            //给线程设置名字
            t1.setName("线程1");
            t2.setName("线程2");
    
            //开启线程
            t1.start();
            t2.start();
    
        }
    
    }
    

    sleep和wait的异同:

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

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

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

    这里可能出现两个问题:
    生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
    消费者比生产者快时,消费者会去相同的数据。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的应用:生产者/消费者问题
     *
     * 1.是否是多线程问题?是的,有生产者线程和消费者线程(多线程的创建,四种方式)
     * 2.多线程问题是否存在共享数据? 存在共享数据----产品(同步方法,同步代码块,lock锁)
     * 3.多线程是否存在线程安全问题? 存在----都对共享数据产品进行了操作。(三种方法)
     * 4.是否存在线程间的通信,是,如果生产多了到20时,需要通知停止生产(wait)。(线程之间的通信问题,需要wait,notify等)
     *
     * */
    
    
    	class Clerk{
    	
    	    private int productCount = 0;
    	
    	
    	    //生产产品
    	    public synchronized void produceProduct() {
    	
    	        if(productCount<20) {
    	            productCount++;
    	
    	            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
    	            notify();
    	        }else{
    	            //当有20个时,等待wait
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	
    	    //消费产品
    	    public synchronized void consumeProduct() {
    	        if (productCount>0){
    	            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
    	            productCount--;
    	            notify();
    	        }else{
    	            //当0个时等待
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	}
    	
    	class Producer extends Thread{//生产者线程
    	
    	    private Clerk clerk;
    	
    	    public Producer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        try {
    	            sleep(10);
    	        } catch (InterruptedException e) {
    	            e.printStackTrace();
    	        }
    	        System.out.println(Thread.currentThread().getName()+";开始生产产品......");
    	
    	        while(true){
    	            clerk.produceProduct();
    	        }
    	    }
    	}
    	
    	class Consumer implements Runnable{//消费者线程
    	
    	    private Clerk clerk;
    	
    	    public Consumer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        System.out.println(Thread.currentThread().getName()+":开始消费产品");
    	
    	        while(true){
    	            try {
    	                Thread.sleep(1);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	
    	            clerk.consumeProduct();
    	        }
    	
    	    }
    	}
    	
    	public class ProductTest {
    	
    	    public static void main(String[] args){
    	        Clerk clerk = new Clerk();
    	
    	        Producer p1 = new Producer(clerk);
    	        p1.setName("生产者1");
    	
    	        Consumer c1 = new Consumer(clerk);
    	        Thread t1 = new Thread(c1);
    	        t1.setName("消费者1");
    	
    	        p1.start();
    	        t1.start();
    	
    	    }
    	
    	}
    
    展开全文
  • Java多线程详解(一)Java多线程入门

    千次阅读 多人点赞 2019-11-27 20:18:20
    最近听很多面试的小伙伴说,网上往往是一篇一篇的Java多线程的文章,除了书籍没有什么学习多线程的一系列文章。但是仅仅凭借一两篇文章很难对多线程有系统的学习,而且面试的时候多线程这方面的知识往往也是考察的...
  • java多线程编程实例

    万次阅读 多人点赞 2018-05-25 10:01:22
    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下。1.相关知识:Java多线程程序设计到的知识:(1)对同一个数量...
  • Java多线程面试题

    万次阅读 2020-10-25 15:56:40
    sleep 方法: 是 Thread 类的静态方法,当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行可运行状态,等待 CPU 的到来。睡眠不释放锁(如果有的话); wait 方法: 是 Object 的方法...
  • java多线程存钱,钱例子

    千次阅读 2015-12-09 23:59:30
    java多线程存钱,钱例子
  • JAVA多线程并发

    千次阅读 多人点赞 2019-09-18 12:14:29
    JAVA多线程并发1 JAVA并发知识库2 JAVA 线程实现/创建方式2.1 继承 Thread 类2.2 实现 Runnable 接口2.3 Callable 、Future 、ExecutorService 有返回值线程2.4 基于线程池的方式2.4.1 4种线程池2.4.1.1 ...
  • 主要介绍了以银行钱为例模拟Java多线程同步问题完整代码,具有一定借鉴价值,需要的朋友可以参考下。
  • Java 多线程面试题

    千次阅读 2019-03-27 20:06:51
    在看完《Java多线程编程核心技术》与《Java并发编程的艺术》之后,对于多线程的理解到了新的境界. 先拿如下的题目试试手把. 投行面试 Q1: 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 ...
  • 现在我要用java多线程实现类似军训报的功能, 即开启两个线程,让它们轮流数数,从1到10,如: 线程A:1 线程B:2 线程A:3 线程B:4 线程A:5 线程B:6 ...... 如何实现该功能呢? ---------------...
  • Java多线程模型

    千次阅读 2019-03-25 14:54:48
    Java多线程模型 生命周期 Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡 5 个状态。一个 Java 线程总是处于这 5 个生命周期状态之一,并在一定条件下可以在不同状态之间进行转换 。 线程的实现 实现线程主要...
  • java多线程模拟队列实现排队叫号,多线程模拟排队叫号
  • JAVA多线程常见的十大问题

    万次阅读 2020-05-06 20:55:40
    JAVA多线程常见的十大问题讲解
  • Java 多线程 系列文章目录: Java 多线程(一)线程间的互斥和同步通信 Java 多线程(二)同步线程分组问题 Java 多线程(三)线程池入门 Callable 和 Future Java 多线程(四)ThreadPoolExecutor 线程池各参数...
  • Java多线程机制

    千次阅读 2012-10-13 12:01:10
    Java多线程机制   【程序、进程和线程】: 程序是一段静态的代码,它是应用程序执行的蓝本。进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、...
  • Java 多线程 -- 从入门到精通

    千次阅读 多人点赞 2020-11-04 20:48:26
    Java 多线程 -- 从入门到精通Java线程与线程的区别多线程的实现方法Thread中start和run方法的区别Thread和Runnable的关系使用Callable和Future创建线程线程返回值的处理方法线程的六个状态线程不安全解决线程不安全...
  • java多线程的常见例子

    万次阅读 多人点赞 2016-09-13 11:43:44
    本文有比较常见的java多线程问题的解决方法。对部分知识也有总结。
  • Java 多线程 系列文章目录: Java 多线程(一)线程间的互斥和同步通信 Java 多线程(二)同步线程分组问题 Java 多线程(三)线程池入门 Callable 和 Future Java 多线程(四)ThreadPoolExecutor 线程池各参数...
  • java 多线程基础之银行号排队系统

    万次阅读 2016-08-10 01:22:06
    多线程? 线程:可以理解为一个程序内部的顺序执行控制流。 多线程:也就是说一段代码的执行是有先后顺序的,只是看起来像同时执行的一样,假如线程a和线程b碰巧同时需要执行,那么在单核计算机的系统中的JVM...
  • java多线程同步编程

    千次阅读 2016-09-08 18:25:21
    学习java有一段时间了,一直对java多线程同步理解的不够深刻,今天将我学习的过程记录下来帮助大家一起来学习深刻理解java多线程同步策略 现实生活中多线程同步场景很多,比如说我的银行卡里面的money是100,...
  • Java 多线程总结笔记

    千次阅读 2018-09-19 10:21:56
    Java多线程总结笔记 实现多线程的方法 查看Thread类的源码,可以发现它实现了Runnable接口,然后在自己的run方法中调用了Runnable的run方法。这里其实就是静态代理这一设计模式,Thread是代理角色,而Runnable则是...
  • Java 多线程与并发——synchronized

    千次阅读 多人点赞 2016-06-01 10:35:42
    Java 多线程编程中,造成线程安全问题的原因主要是由于存在多条线程共同操作共享数据。解决线程安全问题的根本办法就是同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据...
  • Java 多线程 系列文章目录: Java 多线程(一)线程间的互斥和同步通信 Java 多线程(二)同步线程分组问题 Java 多线程(三)线程池入门 Callable 和 Future Java 多线程(四)ThreadPoolExecutor 线程池各参数...
  • Java 多线程 系列文章目录: Java 多线程(一)线程间的互斥和同步通信 Java 多线程(二)同步线程分组问题 Java 多线程(三)线程池入门 Callable 和 Future Java 多线程(四)ThreadPoolExecutor 线程池各参数...
  • 有一批数据需要做update,写了个多线程对其处理。5个并发,根据表里的status状态做更新,每次status=1的数据更新,因为数据量比较大,还加了个rownum=200,一次200条做。 在实际中发现,5个线程进来时,第一个...
  • java多线程技术

    千次阅读 2017-06-17 14:07:52
    多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法...
  • 使用Java多线程,实现模拟银行存款与取款操作的实例。
  • Java多线程-1

    千次阅读 2012-02-04 11:17:30
    Java中提供了各种工具类,关键字来实现多线程的。 多线程中最难的莫过于线程同步和线程协作,怎样实现同步?下面本人提出自己解决多线程的一些观点: 比如有个boss,多个玩家可以打他,这时候需要对boss进行...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 240,592
精华内容 96,236
关键字:

java多线程取数

java 订阅