• Java多线程-线程生命周期(一)


    如果要问我Java当中最难的部分是什么?最有意思的部分是什么?最多人讨论的部分是什么?那我会毫不犹豫地说:多线程。

    Java多线程说它难,也不难,就是有点绕;说它简单,也不简单,需要理解的概念很多,尤其是很多底层知识,如数据结构、操作系统的部分。

    Java多线程掌握得好,不仅仅只是对Java,对任何其他具有并发特性的编程语言,甚至是操作系统,都能有更全面和准确的认识。

    Java多线程最大的特点,而且也是唯一确定的一件事,那就是:在多线程环境下,程序的运行结果是无法预料的,但这也正是它最有趣的地方。

    在了解多线程之前,最好先知道什么是并发,什么是并行。不然很容易迷糊。

    总的来说,就是这样:

    并行:同一时刻可以同时发生/执行多个任务。

    并发:同一时刻只能发生/执行一个任务。


    学习多线程最好从如下六个方面循序渐进(纯粹个人经验和建议,可无视):

    1、线程生命周期:NEW、RUNNABLE(READY、RUNNING)、BLOCKED、WAITING、TIMED_WAITING、TERMINATED状态

    2、关键字:synchronized和volatile

    3、线程池:ThreadPoolExecutor

    4、锁(AQS):悲观锁/乐观锁、轻量级锁/重量级锁、自旋锁/可重入锁等各种锁

    5、CAS:各种原子类

    6、并发工具类:ArrayBlockingQueue、CountDownLatch、CyclicBarrier、Semaphore等


    Java多线程用一句话总结就是「6类5法」。

    所谓「6类」,就是多状态的状态分为这6类:

    1、新建(NEW):新创建了一个线程,但还没调用start方法

    2、运行(RUNNABLE)

    2.1、就绪(ready):运行start方法后,线程位于可运行线程池中,等待被调度

    2.2、运行中(RUNNING):就绪的线程获得CPU的时间片就变为运行中

    3、阻塞(BLOCKED):线程等待获取锁

    4、等待(WAITING):接收事件通知后或系统中断后进入等待

    5、超时(TIMED_WAITING):等待指定时间后会自行返回

    6、终止(TERMINATED):线程已执行完毕

    这是线程生命周期的状态变化图:

     

     

     简单来说,就是这样:

     

     

     

    而所谓「5法」就是线程的核心方法是这么5个:

    1、wait:当前线程调用锁对象的wait方法,当前线程释放锁,进入等待状态,由其他线程接着执行

    2、notify/notifyAll:唤醒任意一个或全部等待的线程后接着执行,但并不释放锁

    3、join:当前线程调用其他线程的join方法,调用后当前线程进入等待状态

    4、yield:当前线程调用,调用后暂停执行(可能无效),变为就绪态

    5、sleep:当前线程调用,调用后进入TIME_WAITING状态


     

    用代码来解释一下会更直观一些。

     

    第一种wait/notify的情况:

    复制代码
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t1 start");
                    try {
                        // t1释放锁
                        "锁".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 end");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t2 start");
                    try {
                        // 通知t1进入等待队列
                        "锁".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t2 end");
                }
            }
        });
    
        t1.start();
        t2.start();
    }
    复制代码

    此时代码执行流程(两种可能):

    1、T1先执行

      1.1、T1启动,wait让出锁,让出CPU,T2获得CPU,T2启动,notify了通过object锁等待的线程

      1.2、T1被唤醒后等待启动,T2继续执行,T2执行完,T1获得CPU后继续执行

    2、T2先执行

      2.1、T2执行完,T1启动,让出CPU,由于没有线程再来执行notify,程序无限期等待

    这里要强调的重点是:

    1、wait会让出CPU而notify不会

    2、wait重点在通知其它同用一个object的线程“我暂时不用了”,并且让出CPU

    3、notify重点在于通知使用object的对象“我用完了!”

    如果说只有两个线程的时候,还能尝试着分析一下结果,那么当有四个线程的时候会如何呢?看看代码:

    复制代码
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    System.out.println("t1 start");
                    try {
                        // t1释放锁
                        "锁".wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 end");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    try {
                        System.out.println("t2 start");
                        // 随机通知一个等待的线程进入等待队列
                        "锁".notify();
                        System.out.println("t2 end");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    try {
                        System.out.println("t3 start");
                        // 随机通知一个等待的线程进入等待队列
                        "锁".notify();
                        System.out.println("t3 end");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁") {
                    try {
                        System.out.println("t4 start");
                        // t4释放锁
                        "锁".wait();
                        System.out.println("t4 end");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
    复制代码

    然后同时开启这四个线程,但结果是无法预料!为什么?因为只有两种可能的流程(要么wait先执行完,要么notify先执行完),至于每种流程里面怎么执行的?不知道!不清楚!无法预料!这就是多线程让人困惑的地方和魅力所在。

    而且线程还有一个无赖的行为就是:虽然你有优先级,但我不保证有用!

    复制代码
    public class MyThread extends Thread {
        MyThread(String s) {
            super(s);
        }
    
        @Override
        public void run() {
            for (int i = 0; i <= 10; i++) {
                System.out.println(getName() + " : " + i);
                if (i == 5) {
                    Thread.yield();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("主线程启动");
            Thread t1 = new MyThread("t1");
            Thread t2 = new MyThread("t2");
            t1.setPriority(Thread.MIN_PRIORITY);
            t1.start();
            t2.setPriority(Thread.MAX_PRIORITY);
            t2.start();
            t1.join();
            t2.join();
            System.out.println("主线程结束");
        }
    }
    复制代码

    这里不管怎么设置t1或者t2的优先级,都没有用,运行的结果每次都可能不一样。

    线程的生命周期6类5法算是比较简单的,是基础中的基础。但是用好很难,关键在于多练多想,多多尝试各种组合。

     

  • 相关阅读:
    2021春招Java面试题大全(精华)
    python自动创建sqlserver表并上传dataframe到创建的表中
    Redis持久化实战
    C++建造者模式管理内存问题
    uniapp获取cilinet
    简述会话跟踪技术——Cookie和Session
    CAS号:1676104-79-2
    一款集百家之所长的黑客浏览器,好用到飞起
    日本率先研发成功6G设备,刺痛了谁?为何日本能率先突破?
    莫能菌素人血清白蛋白纳米粒Monensin-HSA|合成路线
  • 原文地址:https://www.cnblogs.com/sun-10387834/p/16847209.html