• Java多线程基础


    1、多线程引入

    1.1 进程和线程

    进程(Process)计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

    线程(Thread)操作系统能够进行运算调度的最小单位。

    ——百度百科

    操作系统中,每打开一个应用程序(如QQ、网易云音乐),就是启动了一个进程,操作系统会为它们分配内存空间,并按照时间片算法来调度它们,由于CPU计算非常快,视觉感官上会认为是多个程序在同时运行。

    每个应用程序都可以同时执行不同的任务,如QQ的聊天、上传文件、下载文件等,每个任务都交给不同的线程处理。

    1.2 多线程

    首先,多个线程并发执行,在很多场景下都能大大提高CPU资源的利用率。

    • 对于IO密集型作业,一个任务很可能只执行1ms的CPU,然后花50ms的时间去请求外部资源了(如查数据库、调用RPC),如果没有多线程,程序只能大眼瞪小眼地干等待。而多线程的引入,在线程1请求外部资源时,可以让出CPU给其他线程,从而大大程序整体运行效率。

    • 对于CPU密集型作业,多线程的存在依然是十分重要的,若存在一个新来的小任务,只需要执行不到1ms,而当前CPU正在处理一个大任务,需要很长时间,这时候引入多线程,就能尽快把小任务处理完,避免小任务长时间等待。

    其次,目前CPU大多数都支持多核技术,多线程的引入能充分利用多核优势,进一步提高整体性能。

    2、线程基础

    2.1 线程状态转移图

    1. NEW:线程创建状态

    2. RUNNABLE:可运行状态,调用start()方法后进入该状态,可分为:

      1. RUNNING状态,即线程获取到CPU调度资源时

      2. READY状态,即就绪状态,可以随时被CPU调度执行

      3. yeild()方法,主动释放CPU资源,线程状态由RUNNING转为READY,也可能刚释放又获得CPU调度,线程状态由REDAY转为RUNNING

    3. WAITING:等待状态。

      1. Object.wait() && Object.notify()、Object.notifyAll()

        1. 线程只有在获取当前对象的锁时,才能调用该方法,否则抛出IllegalMonitorStateException异常

        2. 该方法调用后会释放当前的锁

        3. 只有在被其他线程notify()notifyAll()时唤醒

        4. 唤醒后进入BLOCKED状态,只有重新获得对象锁,才能继续往下执行

      2. thread.join()

        1. 调用该方法的线程(如main线程),会等待thread线程执行结束,thread线程执行结束后,会调用notifyAll()方法

        2. join()方法底层调用了wait()方法

        3. 注:上图的Object.join()是不对的,join()方法只属于Thread类

      3. LockSupport.park() && LockSupport.unpark(thread)

        1. park()方法阻塞当前线程,不释放锁

        2. unpark(thread)方法唤醒指定线程

    4. TIMED_WAITING:超时等待状态

      1. Thread.sleep(timeout)

        1. 不释放锁,睡一会,timeout后醒来

        2. 可被其他线程调用interrupt()中断

      2. Object.wait(timeout)

        1. 释放锁

        2. 等待timeout,若有notify()notifyAll(),马上醒来;否则timeout时间过后,自动醒来

      3. Thread.join(timeout)

        1. 会阻塞,释放锁

        2. timeout时间过后自动唤醒

        3. 当成功获取Thread对象的锁之后,才继续向下执行

      4. LockSupport.parkNanos(timeout) 、LockSupport.parkUntil(time)&&LockSupport.unpark(thread)

        1. parkNanos() :休眠timeout,除非被unpark(thread)唤醒

        2. parkUntil(time):休眠到time时间点

    5. BLOCKED:阻塞状态

      1. 当线程获取锁失败时,如synchronized方法、synchronized代码块

      2. 执行wait()/wait(timeout)方法的线程被唤醒后

      3. 执行join(timeout)方法的线程被唤醒后

    6. TERMINATED:终止状态

      1. 线程正常执行完

    2.2 Thread常用的方法

    • Thread.currentThread():获取当前执行的线程

    • isAlive():判断线程是否存活

    • StackTraceElement[] getStackTrace():返回线程的堆栈跟踪元素数组。如多层调用了方法,则将方法信息层层压栈

    • dumpStack():将当前线程的堆栈信息输出至标准错误流

    • getId():获取线程唯一数字标识。main线程的id是1,新建一个线程id从11开始递增,说明2~9由隐藏线程占有

    • getName():获取线程名称。常用于分析线程执行情况

    • holdsLock(Obj):若当前线程成功获取obj的锁时,返回true,否则返回false

    • 废弃的方法:

      • stop():停止线程。暴力停止,有可能造成资源未关闭

      • suspend()和resume():挂起和恢复。可能造成无法预料到结果。可使用LockSupport.park()LockSupport.unpark(thread)来实现线程的暂停与继续

    2.3 热点问题分析

    2.3.1 中断interrupt()对wait、sleep、join、LockSupport.park的影响

    wait、sleep、join都会抛出InterruptedException异常,而LockSupport.park会吞噬异常。

    2.3.2 volatile关键字

    可见性:一个线程对共享变量的修改,马上对其他使用该共享变量的线程可见

    禁止指令重排序:volatile变量的读写,会加上内存屏障,阻止编译器对volatile变量前后的指令进行重排序:

    代码块

    1. a = 12;
    2. b = 1;
    3. volatile c = 0; // 1~2行 与 4~5行不能重排序,但1~2内部、4~5内部可以重排序
    4. d = a;
    5. e = b;

    2.3.3 synchronized关键字

    synchronized是一个悲观锁、独占锁,一次只能由一个线程执行synchronized所在的代码块。

        1. 对象的实例方法

    1. public class Obj {
    2. public synchronized void test() {
    3. // 锁住的是当前Obj对象
    4. }
    5. public void test2() {
    6. synchronized(this) {
    7. // 锁住的是当前Obj对象
    8. }
    9. }
    10. }
     
    

    2. 对象的静态方法

    1. public class Obj {
    2. public static synchronized void test() {
    3. // 锁住的是当前Obj.class对象
    4. }
    5. public void test2() {
    6. synchronized(Obj.class) {
    7. // 锁住的是当前Obj.class对象
    8. }
    9. }
    10. }

    2.3.4 线程的优先级

    线程优先级分为1~10级,默认是5,当线程A创建另一个线程B时,也即A是B的父线程,B线程继承A线程的优先级。

    线程的优先级越高,被JVM调用的几率也会越大。

    2.3.5 守护线程和非守护线程

    守护线程:为非守护线程提供服务,如GC保洁线程

    非守护线程:实际工作处理任务的线程

    当JVM中只剩守护线程时,系统会停止,因为没有非守护线程可以服务了。

    3. 线程的通信与隔离

    3.1 wait/notify机制——经典的生产者/消费者模式

    当需要某种条件不满足时,执行wait方法等待;当条件满足时,由其它线程唤醒等待的线程。wait/notify机制就是为了控制线程“条件不满足则等待,条件满足则唤醒、重新获取锁、继续执行”。

    (1)一个生产者&一个消费者

    1. public class OneProducerOneConsumer {
    2. Object obj = new Object();
    3. Object resource = null;
    4. public void consume() {
    5. synchronized(obj) {
    6. if (resource == null) {
    7. try {
    8. obj.wait(); // 没有产品,则等待
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. System.out.println(Thread.currentThread().getName()+"消费了:"+resource);//消费一个产品
    14. resource = null;
    15. obj.notify();//通知生产
    16. }
    17. }
    18. public void produce() {
    19. synchronized(obj) {
    20. if (resource != null) {
    21. try {
    22. obj.wait();// 产品未消费完,则等待
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. resource = "demo_" + new Random().nextInt(); //生产一个产品
    28. System.out.println(Thread.currentThread().getName()+"生产了:"+resource);
    29. obj.notify();//通知消费
    30. }
    31. }
    32. static class Consumer extends Thread {
    33. private OneProducerOneConsumer demo;
    34. public Consumer(OneProducerOneConsumer demo) {
    35. this.demo = demo;
    36. }
    37. @Override
    38. public void run() {
    39. while(true) {
    40. demo.consume();
    41. try {
    42. Thread.sleep(1000);
    43. } catch (InterruptedException e) {
    44. e.printStackTrace();
    45. }
    46. }
    47. }
    48. }
    49. static class Producer extends Thread {
    50. private OneProducerOneConsumer demo;
    51. public Producer(OneProducerOneConsumer demo) {
    52. this.demo = demo;
    53. }
    54. @Override
    55. public void run() {
    56. while(true) {
    57. demo.produce();
    58. try {
    59. Thread.sleep(1000);
    60. } catch (InterruptedException e) {
    61. e.printStackTrace();
    62. }
    63. }
    64. }
    65. }
    66. public static void main(String[] args) {
    67. OneProducerOneConsumer demo = new OneProducerOneConsumer();
    68. new Consumer(demo).start();
    69. new Producer(demo).start();
    70. }
    71. }

    结果如下,每生产一个产品,就消费一个产品,生产与消费总是成对出现。

    1. Thread-1生产了:demo_1806630852
    2. Thread-0消费了:demo_1806630852
    3. Thread-1生产了:demo_240542868
    4. Thread-0消费了:demo_240542868
    5. Thread-1生产了:demo_-1755077240
    6. Thread-0消费了:demo_-1755077240
    7. Thread-1生产了:demo_-1253326727
    8. Thread-0消费了:demo_-1253326727

    (2)多个生产者&多个消费者

    1. public class ManyProducerManyConsumers {
    2. Object consumerLock = new Object(); // 一次一个消费者
    3. Object producerLock = new Object(); // 一次一个生产者
    4. Object notEmpty = new Object();
    5. Object notFull = new Object();
    6. Object lock = new Object();// 资源互斥访问container资源
    7. List container = new ArrayList<>(FULL_SIZE);
    8. final static int FULL_SIZE = 10;
    9. public void consume() {
    10. // 一次只有一个消费者消费
    11. synchronized (consumerLock) {
    12. try {
    13. // 判断容器是否空
    14. synchronized (notEmpty) {
    15. while (container.size() == 0) {
    16. notEmpty.wait();
    17. }
    18. }
    19. // 互斥访问资源
    20. synchronized (lock) {
    21. System.out.println(Thread.currentThread().getName() + "消费了:" + container.remove(container.size() - 1));
    22. }
    23. // 唤醒生产者
    24. synchronized (notFull) {
    25. notFull.notify();
    26. }
    27. } catch (InterruptedException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. public void produce() {
    33. // 一次只有一个生产者生产
    34. synchronized (producerLock) {
    35. try {
    36. // 若为满,则阻塞
    37. synchronized (notFull) {
    38. while (container.size() == FULL_SIZE) {
    39. notFull.wait();
    40. }
    41. }
    42. // 互斥访问
    43. synchronized (lock) {
    44. String str = "proId" + new Random().nextInt();
    45. container.add(str);
    46. System.out.println(Thread.currentThread().getName() + "生产了:" + str);
    47. }
    48. // 唤醒消费者
    49. synchronized (notEmpty) {
    50. notEmpty.notify();
    51. }
    52. } catch (InterruptedException e) {
    53. e.printStackTrace();
    54. }
    55. }
    56. }
    57. static class Consumer extends Thread {
    58. private ManyProducerManyConsumers demo;
    59. public Consumer(ManyProducerManyConsumers demo) {
    60. this.demo = demo;
    61. }
    62. @Override
    63. public void run() {
    64. while (true) {
    65. demo.consume();
    66. try {
    67. Thread.sleep(200);
    68. } catch (InterruptedException e) {
    69. e.printStackTrace();
    70. }
    71. }
    72. }
    73. }
    74. static class Producer extends Thread {
    75. private ManyProducerManyConsumers demo;
    76. public Producer(ManyProducerManyConsumers demo) {
    77. this.demo = demo;
    78. }
    79. @Override
    80. public void run() {
    81. while (true) {
    82. demo.produce();
    83. try {
    84. Thread.sleep(200);
    85. } catch (InterruptedException e) {
    86. e.printStackTrace();
    87. }
    88. }
    89. }
    90. }
    91. public static void main(String[] args) throws InterruptedException {
    92. ManyProducerManyConsumers demo = new ManyProducerManyConsumers();
    93. for(int i=0;i<5;i++) {
    94. new Producer(demo).start();
    95. }
    96. for (int i = 0; i < 5; i++) {
    97. new Consumer(demo).start();
    98. }
    99. }
    100. }

    3.2 ThreadLocal实现每个线程的数据隔离

    每个线程都有自己的ThreadLocalMap变量,因此数据是隔离的。

    需要注意的是,使用ThreadLocal后,应当手动调用remove方法清楚entry对象,这是因为Entry类将key(ThreadLocal)设置为弱引用,当GC发现它时,就会回收ThreadLocal对象,而value并没有回收,这就导致ThreadLocalMap中存在很多key为null,value不为null的Entry对象。

    应用场景:登陆的用户数据,数据库连接,传参数。

    1. public class ThreadLocalDemo {
    2. ThreadLocal threadLocal = new ThreadLocal<>();
    3. public static void main(String[] args) throws InterruptedException {
    4. ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
    5. System.out.println(Thread.currentThread().getName()+" put value: main");
    6. threadLocalDemo.threadLocal.set("main");
    7. Thread.sleep(1000);
    8. Thread thread1 = new Thread(() -> {
    9. System.out.println(Thread.currentThread().getName() + " put value: AAAA");
    10. threadLocalDemo.threadLocal.set("AAAA");
    11. try {
    12. Thread.sleep(500);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
    17. });
    18. Thread thread2 = new Thread(() -> {
    19. System.out.println(Thread.currentThread().getName() + " put value: ZZZZZ");
    20. threadLocalDemo.threadLocal.set("ZZZZZ");
    21. try {
    22. Thread.sleep(500);
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
    27. });
    28. thread1.start();
    29. thread2.start();
    30. System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
    31. }
    32. }

    3.2 InheritableThreadLocal实现子线程继承父线程的值

    InheritableThreadLocal能实现子线程继承父线程的值,也即做了一份浅拷贝。

    应用场景:子线程需要获取父线程的数据信息。

    1. public class InheritableThreadLocalDemo {
    2. static InheritableThreadLocal local = new InheritableThreadLocal<>();
    3. public static void main(String[] args) throws InterruptedException {
    4. System.out.println(Thread.currentThread().getName()+" set Value: hello");
    5. local.set("hello");
    6. Thread.sleep(100);
    7. new Thread(() -> System.out.println(Thread.currentThread().getName()+" getValue:"+local.get())).start();
    8. }
    9. }

    执行结果:

    1. main set Value: hello
    2. Thread-0 getValue:hello

  • 相关阅读:
    java将list转化成树
    智云通CRM:产品销量和价格有什么关系?
    Path-Ranking:KBQA中path生成、召回、粗排与精排
    react中使用腾讯地图
    大模型概念解析 | Prompt Engineering
    电源管理ISL95869HRTZ、ISL95808HRZ概述、规格和应用
    案例:Ajax实现省市联动,选择省后动态显示市和区
    解决flume采集日志使用KafkaChannel写不到hdfs的问题
    高中物理:力、物体和平衡
    VisualStudio2017社区版安装完毕后,找不到stdio.h等头文件的解决方案
  • 原文地址:https://blog.csdn.net/qianmojl/article/details/133036595