天天看点

技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

前言

面试题中常见的题目之一,很多人都能解答出简单的线程问题。但往往面试官再深入,很多时候就哑口无言。同时线程也是我们开发中常用的,故我们必须要好好学习线程,深入了解原理。

要了解线程,请先记住以下这句话。

锁的机制:基于线程,不是基于方法

线程与进程的关系

https://www.zhihu.com/question/25532384

进程是资源最小调度单位,线程是进程最小调度单位,理论上一个进程可以有无数个线程。

进程之间互不影响,线程之间可以相互影响。

打个比喻:

系统=火车站

进程=火车

线程=车厢

一个火车站可以有多辆火车,一辆火车有多节车厢

火车站可以调度多辆火车,而且每辆火车是独立的,互不影响。

每辆火车的车厢资源可以是独立的,也可以是共享的,例如车站服务人员,安全员,食物等

 锁,线程与代码块的关系

打个比喻

场景:下雨天,人想要到房子里避雨,钥匙只有一把,每次只能有一个人拿着钥匙进屋避雨,而且每次只能进入房子五分钟

锁:钥匙

代码块:房子

线程:人

  1. 下雨天,很多人都想进屋避雨,但奈何钥匙只有一把,这时候这些人只能在进入区等待钥匙,他们的状态是就绪的
  2. 当某个人获得钥匙且进入房子时,这个人是在拥有者区,状态是运行的
  3. 五分钟过后,他只能出去屋子了,这时候他的状态是终止的,不属于任何区域中,
  4. 但如果这个人死活不出来,这时候就造成阻塞了。
  5. 但如何这个人跟其中某个人达成协议,先出去一块儿,后面再补上剩余时间,也是可以的,这时候这个人就处于等待状态,进入等待区。

线程的六种状态

  1. 初始(NEW)
  2. 就绪(READY)
  3. 运行(RUNNING)
  4. 阻塞(BLOCKED)
  5. 等待(WAIT/TIMED_WAITING)
  6. 终止(TERMINATED)
技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

上图出现比较多的关键词,都是很重要的

关键词:yield、sleep、wait、notify、notifyall、park、unpark、join

  1. yield:让出cpu调度权,优先让给同级或高级的线程,但也有可能下一个获得cpu调度权的是自己,不释放锁,让线程进入就绪状态
  2. sleep:让线程暂停运行一段时间,让出cpu调度权,无所谓高低之分的线程,不释放锁,进入等待状态
  3. join:并行改串行,A线程中优先运行B线程,不释放锁,也可以说释放锁(比较特殊)。
  4. wait:让线程暂停运行,进入等待区,等待notify/notifyAll唤醒,释放锁
  5. notify:唤醒处于某个等待状态的线程,仅限调用wait()方法的线程,随机唤醒一个!!!
  6. notifyall:唤醒所有处于等待状态的线程,仅限调用wait()方法的线程,全部!!!
  7. park:让线程处于阻塞状态,不释放锁
  8. unpark:让特定某个处于阻塞状态的线程重新运行

区别

所属方法 线程状态 是否释放锁 是否造成阻塞 作用 备注
sleep Thread 等待 一定时间停止运行线程,让出cpu调度权,让其他线程运行 任何等级的线程都可争夺cpu调度权
yield Thread 就绪 让出cpu调度权,进入就绪状态,让其他线程运行 让给同级或高级的线程,但也有可能下一个运行的线程是自己
join Thread 等待 是(也可以说否,视情况而定) 并行该串行,A线程里运行B线程,且等待B线程运行完毕

1. 当锁的类型是线程本身,则会释放锁,否则不释放锁

2. 低层调用wait()方法

wait Object 等待 释放锁,进入等待状态 与park不同,线程是释放锁,且进入等待状态
park LockSupport 阻塞 线程挂起,不释放锁 与wait不同,线程不释放锁,且进入阻塞状态
notify Object / / / 随机唤醒等待状态的线程,但仅限调用wait状态的线程 随机唤醒一条
notifyAll Object / / / 唤醒全部等待状态的线程,仅限调用wait状态的线程 唤醒全部
unPark LockSupport / / / 唤醒特定某条阻塞状态的线程 特定某条阻塞状态的线程

 线程的三个区域

  1. 进入区
  2. 拥有者区
  3. 等待区
技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

还有一个特殊区域

临界区:指的是同步代码块

上图可以看出

  1. 拥有者区(OWNER SET)域永远只有一个线程且是正在执行的线程,也就是代表是获得锁的线程
  2. 等待区(WAIT SET),区中有多个线程,这些线程曾经获得所,但都是调用wait()、join()方法,释放锁的线程,它们等待着被某种条件唤醒,重新获得锁,进入拥有者区
  3. 进入区(ENTRY SET),区中有多个线程,这些线程未曾获得所,都在等待拥有者区的线程释放锁
线程的六种状态里还有synchronized和lock这两个关键词,它们都是同步线程的标志。

线程

系统角度

守护线程:一般指jvm的日志记录线程,gc线程等含有daemon标识的线程(gc分析日志的常客)

用户线程:非守护线程

默认是Thread.setDaemon(false)
用户线程可以通过Thread.setDaemon(true)设置为守护线程
           

同步角度

同步线程:指的是使用synch和lock关键词的线程

非同步线程:顾名思义

jstack的线程日志,含有daemon标识

"http-nio-8080-Poller" #27 daemon prio=5 os_prio=0 cpu=0.00ms elapsed=625.33s tid=0x0000018711395800 nid=0x469c runnable  [0x0000004209dfe000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0([email protected]/Native Method)
	at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll([email protected]/WindowsSelectorImpl.java:339)
	at sun.nio.ch.WindowsSelectorImpl.doSelect([email protected]/WindowsSelectorImpl.java:167)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect([email protected]/SelectorImpl.java:124)
	- locked <0x0000000710b2ce48> (a sun.nio.ch.Util$2)
	- locked <0x0000000710b2cdc0> (a sun.nio.ch.WindowsSelectorImpl)
	at sun.nio.ch.SelectorImpl.select([email protected]/SelectorImpl.java:136)
	at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:787)
	at java.lang.Thread.run([email protected]/Thread.java:834)

   Locked ownable synchronizers:
	- None
           

synchronize和lock的区别

  1. synchronize是关键字,可以修饰成员变量,方法,lock是一个类
  2. synchronize线程运行结束,会自动释放锁,而lock需要手动释放
  3. synchronize线程异常时,会自动释放锁,而lock不会,且必须要在finally代码块释放
  4. synchronize是公平锁,lock可以设置成非公平锁,也可以是公平锁
  5. synchronize不支持非阻塞式获取锁,lock支持
  6. synchronize不可以响应中断,lock可以(使用interrupt()方法中断)
  7. synchronize使用wait()方法进入等待状态的线程,通过notify/notifyAll唤醒,而且只能是随机唤醒,而lock可以通过condition唤醒特定的等待线程
  8. synchronize是基于jvm实现的内置锁,每个对象都可以作为锁,对于同步方法,锁是当前示例对象,对于同步静态方法,锁是当前class对象,对于同步代码块,锁是当前synchronize代码内的对象,lock是编程语言层实现的锁
  9. synchronize和lock都是可重入锁
技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例
技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例
本质 运行结束是否自动释放锁 异常时是否自动释放锁 锁类型 锁的机制 是否支持非阻塞式获取锁 是否可响应中断 能否获取锁的状态 是否独享锁 调度机制 是否可重入锁
synchronize java的关键字 非公平锁 悲观锁 不支持 不可以 不能 通过notify/notifyAll唤醒,且只能随机唤醒
lock java的一个类 公平锁/非公平锁 乐观锁 支持 支持

分情况:

1.ReadWriteLock的writeLock是独享锁

2. ReadWriteLock的readLock是共享锁

可以通过condition唤醒特定的线程

额外点

lock的方法

  1. lock:获取锁,必须配合unlock使用
  2. unlock:释放锁,必须放到finally代码块
  3. lockinterrupt:中断线程等待锁状态
  4. trylock:一定次数尝试获取锁
  5. trylock(time):一定时间内尝试获取锁

synchronize

  1. 修饰方法:锁是当前this对象
  2. 修饰静态方法:锁是当前class的对象
  3. 修饰代码块:指定加锁对象,(代码块内的内容)

刚才也提到过锁的类型,其实锁还分公平锁/非公平锁,独享锁/共享锁,悲观锁/乐观锁,偏向锁/轻量级锁/重量级锁,可重入锁

锁的类型

公平锁/非公平锁(java代码)

公平锁:线程排队式获取锁,先到先得

非公平锁:线程竞争式获取锁,谁抢到是谁

synch是非公平锁,lock可以是公平锁,也可以是非公平锁

独享锁/共享锁(设计想法)

独享锁:一次只能一个线程获取

共享锁:一次可以被多个线程获取

synch是独享锁,lock分情况:ReadWriteLock的ReadLock是共享锁,WriteLock是独享锁

读写锁/互斥锁

如果说独享锁/共享锁是设计想法,那么读写锁/互斥锁就是实现

互斥锁:synchronize

读写锁:lock

技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

 乐观锁/悲观锁(设计思想)

乐观锁:认为线程获取锁操作时,不会对数据进行修改

悲观锁:与乐观锁想法,认为线程获取锁时,会对数据进行修改

分段锁(设计思想)

细化锁的颗粒度,实现高效的并发操作。例如concurrentHash,内部拥有一个entry数组,每个元素既是元素又是链表,同时又是一个ReentLock,操作时,会被元素进行加锁

可中断锁(java代码编写)

处于阻塞或等待状态的线程,不想继续等待,想处理其他事情,我们可以中断它的等待状态。但仅限lock类,synchronize不可中断

偏向锁/轻量级锁/重量级锁(java代码编写)

偏向锁:当锁一直只被同一个线程获取,则该锁是偏向锁

轻量级锁:当锁是偏向锁,这时候被其他线程获取,则该锁升级为轻量级锁

重量级锁:当锁是轻量级锁,且其他线程自旋一定次数都未能获取锁,该锁升级为重量级锁,jdk1.6之前只有重量级锁

自旋锁(java代码编写)

轻量级锁并不是马上升级为重量级锁,而是通过尝试一定次数获取锁,这时候是自旋锁

可重入锁(java代码编写)

先回顾锁的机制:基于线程,不是基于方法

当一个线程获取得到锁,调用一个方法,该方法又调用其他方法,则其他方法自动获取得到锁,不需要通过竞争获取。lock和synchronize都是可重入锁

lock示例

/**
 * @author Leo
 * @description 可重入锁案例1,lock示例
 * @createDate 2021/9/7 22:40
 **/
public class DemoThread1 {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            System.out.println("method1");
            method2();
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            System.out.println("method2");
            method3();
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            System.out.println("method3");
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

}
           

synchronize示例

/**
 * @author Leo
 * @description 可重入锁案例2,synchronize示例
 * @createDate 2021/9/7 22:40
 **/
public class DemoThread2 implements Runnable {

    private static Object o = new Object();

    public static void main(String[] args) {
        DemoThread2 demoThread2 = new DemoThread2();
        Thread thread = new Thread(demoThread2);
        thread.start();
    }

    public synchronized void method1(Object o) {
        System.out.println("method1" + o.toString());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2(Object o) {
        System.out.println("method2" + o.toString());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method3(Object o) {
        System.out.println("method3" + o.toString());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        synchronized (o) {
            System.out.println("method开始" + o.toString());
            method1(o);
            method2(o);
            method3(o);
            System.out.println("method结束" + o.toString());
        }
    }
}
           

同步线程和锁实现的原理(重点)

要理解原理,需要先知道对象的组成

对象的组成

  1. 对象头
  2. 类对象指针/填充字节
  3. 实例数据
技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

 对象头又分为Mark Word(标记字段)和Klass Point(类型指针)

  1. Klass Point:是对象指向它的类元数据的指针,顾名思义,是告诉jvm,本身对象是什么类型。
  2. Mark Word:是存储对象运行时状态变化的信息,锁就是与它相关,下面图片出现的指针指向的是与之对应的monitor的地址
技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

Monitor

每个对象的生成都有一个与之对应的monitor(监控器),线程获取锁,实际上获取的是monitor。

monitor是有ObjectMonitor实现的,其主要结构如下(位于虚拟机源码的ObjectMonitor.hpp文件,c++实现)

ObjectMonitor() {
    _count        = 0; //记录数
    _recursions   = 0; //锁的重入次数
    _owner        = NULL; //指向持有ObjectMonitor对象的线程 
    _WaitSet      = NULL; //调用wait后,线程会被加入到_WaitSet
    _EntryList    = NULL ; //等待获取锁的线程,会被加入到该列表
}
           

对应的java源码

技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

 从上面可以看出monitor拥有owner,count属性对应的作用是owner是谁拥有它,count是使用次数

monitor和mark word都已讲解完毕,正式开讲获取锁的过程

  1. 当对象无任何线程获取,此时owner=null,count=0。
  2. 有线程获取对象锁时,owner会记录线程id,count+1。
  3. 线程运行完毕,释放锁,owner会重新变成null,count-1。
结论:线程获取锁,实际上获取的monitor,目的是改变其属性,改变owner和count,这也是为什么java里任何对象都可以当锁

示例一

public class SyncCodeBlock {
    public int count = 0;
    public void addOne() {
        synchronized (this) {
            count++;
        }
    }
}
           
反编译后,详细看到了monitorenter,monitorexit两个关键词,进入同步代码块,monitorenter,退出同步代码块,monitorexit,为什么出现两次monitorexit,第一次是正常退出,第二次是异常退出(上面也说过synchronize异常时会自动释放锁)
public void addOne();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter // 进入同步方法
         4: aload_0
         5: dup
         6: getfield      #2                  // Field count:I
         9: iconst_1
        10: iadd
        11: putfield      #2                  // Field count:I
        14: aload_1
        15: monitorexit // 退出同步方法
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit // 退出同步方法
        22: aload_2
        23: athrow
        24: return
      Exception table
           

示例二

public class SyncMethod {
    public int count = 0;
    public synchronized void addOne() {
        count++;
    }
}
           
反编译后,这里没出现monitorenter,monitorexit两个关键词,但相反出现了acc_synchronized关键词,该词也能说明该类是同步类
public synchronized void addOne();
    descriptor: ()V
    // 方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field count:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field count:I
        10: return
      LineNumberTable:
           

说了这么久线程的运行,实现原理,线程的区分,锁的区分,锁的原理。如果我们想终止该线程,那么我们需要怎么办呢?

线程的终止和中断

线程终止:是马上停止运行线程

线程的中断:通知正在运行的线程,停止运行

区分:一个是马上终止,一个是通知终止

线程中断和终止的三个方法

  1. 标识法
  2. 调用stop(),destory()方法,(被废弃,jdk11甚至被删除,原因是造成数据不一致问题,没有起到真正的作用,JDK文档也说明过:《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?》)
  3. 调用interrupt()方法,其中调用需要谨慎,当调用时出现异常,该异常就会被捕捉,interrupt()方法生成的通知就会被清楚掉,故也要在异常处增加interrupt()方法。

额外知识

stop、destory、suspend、resume方法区别

  • void suspend() :暂停当前线程
  • void resume() :恢复当前线程
  • void stop() :结束当前线程
  • void destory():抛出一个NoSuchMethodError异常

stop和destory的区别详解:深入多线程四:停止多线程,你不会还以为是用stop和destroy吧?_一颗剽悍的种子的博客-CSDN博客

终止与中断的图片例子

举个例子,看下图

终止

当student获取对象锁,并且准备写入数据时,调用了stop方法,线程突然被终止,导致数据丢失,出现不准确问题

技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例

 中断

当student获取对象锁,并且准备写入数据时,调用了interrupt方法,通知线程停止运行,但线程不是马上运行,最后数据没有丢失,没有出现不准确问题

技术自查第四篇:线程基础篇前言线程与进程的关系 锁,线程与代码块的关系线程的六种状态 线程的三个区域线程synchronize和lock的区别锁的类型同步线程和锁实现的原理(重点)线程的终止和中断附:wait/join/yelid/park/notiy/notifyall/unpark例子经典消费者生产者示例
 线程终止导致了数据不安全的问题,而且基本上没有运用的场景,最后在jdk11,stop(),destory(),resume()方法都被删除了

代码示例

标识法

/**
 * 通过标志位终止线程
 */
public class FlagThread implements Runnable {

    private static boolean isFlag = true;

    private static int i = 0;

    @Override
    public void run() {
        while (isFlag) {
            System.out.println(i++);
        }
    }

    public static void main(String[] args) {
        FlagThread flagThread = new FlagThread();
        Thread thread = new Thread(flagThread);
        thread.start();
        try {
            // 由于过快,所以加上休眠时间,用于打印,效果
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isFlag = false;
    }
}
           

调用stop()方法

/**
 * 通过stop强制终止线程
 */
public class StopThread implements Runnable {

    private static boolean isFlag = true;

    private static int i = 0;

    @Override
    public void run() {
        while (isFlag) {
            System.out.println(i++);
        }
    }

    public static void main(String[] args) {
        StopThread stopThread = new StopThread();
        Thread thread = new Thread(stopThread);
        thread.start();
        try {
            // 由于过快,所以加上休眠时间,用于打印,效果
            Thread.sleep(5);
            thread.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

调用interrupt()方法

/**
 * 通过Interrupt终止线程,不能强制停止线程,只是通知jvm让该线程停止运行,具体看线程
 */
public class InterruptThread implements Runnable {

    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            System.out.println(j);
        }
    }

    public static void main(String[] args) {
        InterruptThread interruptThread = new InterruptThread();
        Thread thread = new Thread(interruptThread);
        thread.start();
        try {
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

打印日志

0
1
2
3
.........
98
99
           
interrupt方法使用注意,出现异常时解决办法,如果运行时,出现异常,interrupt将会失效,需要在异常处重新调用interrupt方法
/**
 * 通过Interrupt终止线程,不能强制停止线程,只是通知jvm让该线程停止运行,具体看线程
 */
public class Interrupt2Thread implements Runnable {

    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {
            System.out.println(Thread.currentThread().getName() + "...." + j);
            if (Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + "线程中断");
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //Thread.currentThread().interrupt();// 恢复这句注释即可运行成功
            }
        }
    }

    public static void main(String[] args) {
        Interrupt2Thread interruptThread = new Interrupt2Thread();
        Thread thread = new Thread(interruptThread,"interruptSleep线程");
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
           

异常时日志

interruptSleep线程....0
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.gc.demo.thread.zhongduan.Interrupt2Thread.run(Interrupt2Thread.java:17)
	at java.base/java.lang.Thread.run(Thread.java:834)
interruptSleep线程....1
interruptSleep线程....2
interruptSleep线程....3
interruptSleep线程....4
interruptSleep线程....5
....
interruptSleep线程....99
           

正常日志

interruptSleep线程....0
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.gc.demo.thread.zhongduan.Interrupt2Thread.run(Interrupt2Thread.java:17)
	at java.base/java.lang.Thread.run(Thread.java:834)
interruptSleep线程....1
interruptSleep线程线程中断
           

附:wait/join/yelid/park/notiy/notifyall/unpark例子

wait()例子

/**
 * @author Leo
 * @description wait()案例
 * @createDate 2021/8/31 10:39
 **/
public class WaitThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(currentThread().getName() + "...." + i);
        }
    }

    /**
     * wait()是让运行该行代码的线程处于wait状态,释放锁
     * main线程运行A线程,当运行到thread.wait()改行代码时,main线程处于wait状态,让A线程运行
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        Thread thread = new Thread(waitThread, "等待线程");
        synchronized (thread) {
            for (int i = 0; i < 20; i++) {
                if (i == 10) {
                    thread.start();
                    thread.wait();
                }
                System.out.println(currentThread().getName() + "...." + i);
            }
        }

    }
}
           

日志打印

main....0
main....1
main....2
main....3
main....4
main....5
main....6
main....7
main....8
main....9
等待线程....0
等待线程....1
等待线程....2
等待线程....3
等待线程....4
等待线程....5
等待线程....6
等待线程....7
等待线程....8
等待线程....9
等待线程....10
等待线程....11
等待线程....12
等待线程....13
等待线程....14
等待线程....15
等待线程....16
等待线程....17
等待线程....18
等待线程....19
main....10
main....11
main....12
main....13
main....14
main....15
main....16
main....17
main....18
main....19
           

join()-没有锁例子

/**
 * @author Leo
 * @description join案例,底层实际调用wait
 * @createDate 2021/9/1 16:44
 **/
public class JoinThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "..." + i);
        }
    }

    /**
     * 线程的join用法,运行结果是,完全输出线程1结果后,再输出main结果
     * 让权,底层调用的wait方法
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        JoinThread thread = new JoinThread();
        Thread t1 = new Thread(thread, "join线程");
        for (int j = 0; j < 20; j++) {
            System.out.println(Thread.currentThread().getName() + "..." + j);
            if (j == 10) {
                t1.start();
                t1.join();
            }
        }
    }
}
           

日志打印

main...0
main...1
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
main...10
join线程...0
join线程...1
join线程...2
join线程...3
join线程...4
join线程...5
join线程...6
join线程...7
join线程...8
join线程...9
join线程...10
join线程...11
join线程...12
join线程...13
join线程...14
join线程...15
join线程...16
join线程...17
join线程...18
join线程...19
main...11
main...12
main...13
main...14
main...15
main...16
main...17
main...18
main...19
           

join()-释放锁示例

/**
 * @author Leo
 * @description join-释放锁案例,底层实际调用wait
 * @createDate 2021/9/1 16:44
 **/
public class Join3Thread extends Thread {


    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
            }
        }
    }

    /**
     * 释放锁篇
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Join3Thread thread = new Join3Thread();
        thread.setName("join3线程");
        thread.start();
        synchronized (thread) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
                if (i == 1) {
                    thread.join();
                }
            }
        }
    }
}
           

日志

main...0
main...1
join3线程...0
join3线程...1
join3线程...2
join3线程...3
join3线程...4
join3线程...5
join3线程...6
join3线程...7
join3线程...8
join3线程...9
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
           

join-不会释放锁示例

/**
 * @author Leo
 * @description join-不会释放锁案例,底层实际调用wait
 * @createDate 2021/9/1 16:44
 **/
public class Join2Thread implements Runnable {

    private static Object o = new Object();

    @Override
    public void run() {
        synchronized (o) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
            }
        }
    }

    /**
     * 不会释放锁篇
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Join2Thread thread = new Join2Thread();
        Thread join2 = new Thread(thread, "join2线程");
        join2.start();
        synchronized (o) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "..." + i);
                if (i == 1) {
                    join2.join();
                }
            }
        }
    }
}
           

日志 

main...0
main...1
           

notify示例

/**
 * @author Leo
 * @description notify示例
 * @createDate 2021/8/31 10:39
 **/
public class NotifyThread implements Runnable {

    private String z = "锁";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    z.notify();
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    z.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * notify()是让运行该行代码的线程处于wait状态,释放锁
     * main线程运行A、B线程,由于A、B线程持有同一个锁,所以导致相互释放锁,争夺锁
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        NotifyThread notifyThread1 = new NotifyThread();
        NotifyThread notifyThread2 = new NotifyThread();
        Thread thread1 = new Thread(notifyThread1, "notify1线程");
        Thread thread2 = new Thread(notifyThread2, "notify2线程");
        thread1.start();
        thread2.start();
    }
}
           

日志

notify1线程....0
notify2线程....0
notify1线程....1
notify2线程....1
notify1线程....2
notify2线程....2
notify1线程....3
notify2线程....3
notify1线程....4
notify2线程....4
notify1线程....5
notify2线程....5
notify1线程....6
notify2线程....6
notify1线程....7
notify2线程....7
notify1线程....8
notify2线程....8
notify1线程....9
notify2线程....9
notify1线程....10
notify2线程....10
notify1线程....11
notify2线程....11
notify1线程....12
notify2线程....12
notify1线程....13
notify2线程....13
notify1线程....14
notify2线程....14
notify1线程....15
notify2线程....15
notify1线程....16
notify2线程....16
notify1线程....17
notify2线程....17
notify1线程....18
notify2线程....18
notify1线程....19
notify2线程....19
           

notifyAll示例

/**
 * @author Leo
 * @description notifyAll示例
 * @createDate 2021/8/31 10:39
 **/
public class NotifyAllThread implements Runnable {

    private String z = "锁";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    z.notifyAll();
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    z.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * notifyAll()是让运行该行代码的线程处于wait状态,争夺锁
     * main线程运行A、B、C线程,由于A、B、C线程持有同一个锁,所以导致相互释放锁,争夺锁,导致死锁
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        NotifyAllThread notifyThread1 = new NotifyAllThread();
        NotifyAllThread notifyThread2 = new NotifyAllThread();
        NotifyAllThread notifyThread3 = new NotifyAllThread();
        Thread thread1 = new Thread(notifyThread1, "notify1线程");
        Thread thread2 = new Thread(notifyThread2, "notify2线程");
        Thread thread3 = new Thread(notifyThread3, "notify3线程");
        thread1.start();
        thread2.start();
        thread3.start();
        return;
    }
} 
           

日志

notify1线程....0
notify3线程....0
notify2线程....0
notify3线程....1
notify2线程....1
notify3线程....2
notify2线程....2
notify3线程....3
notify2线程....3
notify3线程....4
notify2线程....4
notify3线程....5
notify2线程....5
notify3线程....6
notify2线程....6
notify3线程....7
notify2线程....7
notify3线程....8
notify2线程....8
notify3线程....9
notify2线程....9
notify3线程....10
notify2线程....10
notify3线程....11
notify2线程....11
notify3线程....12
notify2线程....12
notify3线程....13
notify2线程....13
notify3线程....14
notify2线程....14
notify3线程....15
notify2线程....15
notify3线程....16
notify2线程....16
notify3线程....17
notify2线程....17
notify3线程....18
notify2线程....18
notify3线程....19
notify2线程....19
notify1线程....1
           

sleep示例

/**
 * @author Leo
 * @description sleep示例
 * @createDate 2021/8/31 10:39
 **/
public class SleepThread implements Runnable {

    private String z = "锁";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * sleep()是让运行该行代码的线程暂时处于wait状态(),但并不会释放锁
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        SleepThread sleepThread1 = new SleepThread();
        SleepThread sleepThread2 = new SleepThread();
        SleepThread sleepThread3 = new SleepThread();
        Thread thread1 = new Thread(sleepThread1, "sleep1线程");
        Thread thread2 = new Thread(sleepThread2, "sleep2线程");
        Thread thread3 = new Thread(sleepThread3, "sleep3线程");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
           

日志

sleep1线程....0
sleep1线程....1
sleep1线程....2
sleep1线程....3
sleep1线程....4
sleep1线程....5
sleep1线程....6
sleep1线程....7
sleep1线程....8
sleep1线程....9
sleep1线程....10
sleep1线程....11
sleep1线程....12
sleep1线程....13
sleep1线程....14
sleep1线程....15
sleep1线程....16
sleep1线程....17
sleep1线程....18
sleep1线程....19
sleep2线程....0
sleep2线程....1
sleep2线程....2
sleep2线程....3
sleep2线程....4
sleep2线程....5
sleep2线程....6
sleep2线程....7
sleep2线程....8
sleep2线程....9
sleep2线程....10
sleep2线程....11
sleep2线程....12
sleep2线程....13
sleep2线程....14
sleep2线程....15
sleep2线程....16
sleep2线程....17
sleep2线程....18
sleep2线程....19
sleep3线程....0
sleep3线程....1
sleep3线程....2
sleep3线程....3
sleep3线程....4
sleep3线程....5
sleep3线程....6
sleep3线程....7
sleep3线程....8
sleep3线程....9
sleep3线程....10
sleep3线程....11
sleep3线程....12
sleep3线程....13
sleep3线程....14
sleep3线程....15
sleep3线程....16
sleep3线程....17
sleep3线程....18
sleep3线程....19
           

yield示例

/**
 * @author Leo
 * @description yield示例
 * @createDate 2021/8/31 10:39
 **/
public class YieldThread implements Runnable {

    private String z = "锁";

    @Override
    public void run() {
        synchronized (z) {
            for (int i = 0; i < 20; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "...." + i);
                    Thread.yield();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * yield()是让运行该行代码的线程暂时处于wait状态(),让锁出来,但也有可能会马上获取到锁
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        YieldThread yieldThread1 = new YieldThread();
        YieldThread yieldThread2 = new YieldThread();
        YieldThread yieldThread3 = new YieldThread();
        Thread thread1 = new Thread(yieldThread1, "yield1线程");
        Thread thread2 = new Thread(yieldThread2, "yield2线程");
        Thread thread3 = new Thread(yieldThread3, "yield3线程");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
           

日志

yield1线程....0
yield1线程....1
yield1线程....2
yield1线程....3
yield1线程....4
yield1线程....5
yield1线程....6
yield1线程....7
yield1线程....8
yield1线程....9
yield1线程....10
yield1线程....11
yield1线程....12
yield1线程....13
yield1线程....14
yield1线程....15
yield1线程....16
yield1线程....17
yield1线程....18
yield1线程....19
yield3线程....0
yield3线程....1
yield3线程....2
yield3线程....3
yield3线程....4
yield3线程....5
yield3线程....6
yield3线程....7
yield3线程....8
yield3线程....9
yield3线程....10
yield3线程....11
yield3线程....12
yield3线程....13
yield3线程....14
yield3线程....15
yield3线程....16
yield3线程....17
yield3线程....18
yield3线程....19
yield2线程....0
yield2线程....1
yield2线程....2
yield2线程....3
yield2线程....4
yield2线程....5
yield2线程....6
yield2线程....7
yield2线程....8
yield2线程....9
yield2线程....10
yield2线程....11
yield2线程....12
yield2线程....13
yield2线程....14
yield2线程....15
yield2线程....16
yield2线程....17
yield2线程....18
yield2线程....19
           

Park示例

/**
 * @author Leo
 * @description
 * @createDate 2021/9/4 21:52
 **/
public class  Park implements Runnable {

    @Override
    public void run() {
        System.out.println("park开始");
        LockSupport.park();
        System.out.println("park结束");
    }
}
           

UnPark

/**
 * @author Leo
 * @description
 * @createDate 2021/9/4 21:52
 **/
public class UnPark implements Runnable {

    private Thread thread;

    public UnPark(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        System.out.println("unPark开始");
        LockSupport.unpark(thread);
        System.out.println("unPark结束");
    }
}
           

运行

/**
 * @author Leo
 * @description park示例
 * @createDate 2021/8/31 10:39
 **/
public class ParkThread {

    /**
     * park()是让运行该行代码的线程暂时处于阻塞状态,且需要unpark让线程唤醒
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) {
        Park parkThread = new Park();
        Thread threadPark = new Thread(parkThread);
        threadPark.start();
        UnPark unPark = new UnPark(threadPark);
        Thread threadUnPark = new Thread(unPark);
        threadUnPark.start();
    }
}
           

日志

park开始
unPark开始
unPark结束
park结束
           

经典消费者生产者示例

商品

/**
 * @author Leo
 * @description 商品
 * @createDate 2021/9/4 16:07
 **/
public class SteamBread {
    int id;

    public SteamBread(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "SteamBread{" +
                "id=" + id +
                '}';
    }
}
           

货架

/**
 * @author Leo
 * @description 货架,最多只放六件商品
 * @createDate 2021/9/4 16:08
 **/
public class SyncStack {
    int index = 0;

    SteamBread[] stb = new SteamBread[6];

    public synchronized void push(SteamBread steamBread) {
        while (index == stb.length) {
            try {
                this.wait();
            } catch (Exception e) {

            } finally {

            }
        }
        this.notify();
        stb[index] = steamBread;
        this.index++;
    }

    public synchronized SteamBread pop() {
        while (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        this.index--;
        return stb[index];
    }
}
           

消费者

/**
 * @author Leo
 * @description 消费者
 * @createDate 2021/9/4 16:09
 **/
public  class Consumer implements Runnable {

    SyncStack ss = null;

    public Consumer(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            SteamBread steamBread = new SteamBread(i);
            ss.pop();
            System.out.println("消费了" + steamBread.toString());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
           

生产者

/**
 * @author Leo
 * @description 生产者
 * @createDate 2021/9/4 16:08
 **/
public class Producer implements Runnable {

    SyncStack ss = null;

    public Producer(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            SteamBread steamBread = new SteamBread(i);
            ss.push(steamBread);
            System.out.println("生产了" + steamBread.toString());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
           

购买场景

/**
 * @author Leo
 * @description 经典消费者,生产者多线程
 * @createDate 2021/8/31 10:39
 **/
public class NotifyCBThread {

    public static void main(String[] args) {
        SyncStack syncStack = new SyncStack();
        Consumer consumer = new Consumer(syncStack);
        Producer producer = new Producer(syncStack);
        Thread cT = new Thread(consumer);
        Thread pT = new Thread(producer);
        pT.start();
        cT.start();
    }


}
           

日志

生产了SteamBread{id=0}
消费了SteamBread{id=0}
消费了SteamBread{id=1}
生产了SteamBread{id=1}
生产了SteamBread{id=2}
消费了SteamBread{id=2}
生产了SteamBread{id=3}
消费了SteamBread{id=3}
生产了SteamBread{id=4}
消费了SteamBread{id=4}
生产了SteamBread{id=5}
消费了SteamBread{id=5}
生产了SteamBread{id=6}
消费了SteamBread{id=6}
生产了SteamBread{id=7}
消费了SteamBread{id=7}
消费了SteamBread{id=8}
生产了SteamBread{id=8}
生产了SteamBread{id=9}
消费了SteamBread{id=9}
生产了SteamBread{id=10}
消费了SteamBread{id=10}
生产了SteamBread{id=11}
消费了SteamBread{id=11}
消费了SteamBread{id=12}
生产了SteamBread{id=12}
生产了SteamBread{id=13}
消费了SteamBread{id=13}
生产了SteamBread{id=14}
消费了SteamBread{id=14}
生产了SteamBread{id=15}
消费了SteamBread{id=15}
生产了SteamBread{id=16}
消费了SteamBread{id=16}
生产了SteamBread{id=17}
消费了SteamBread{id=17}
生产了SteamBread{id=18}
消费了SteamBread{id=18}
生产了SteamBread{id=19}
消费了SteamBread{id=19}