首页 > 编程语言 >Java多线程(1):线程生命周期

Java多线程(1):线程生命周期

时间:2022-10-22 07:55:06浏览次数:48  
标签:Java Thread t1 start 线程 println 多线程

您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~

 

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

之前说过I/O是比较裹人概念之二,那么多线程就是那个之一,除了它也没谁了!如果一定要找另外一个特性,那就非JVM莫属吧。

涉及Java多线程的书籍、文章、日志,包括面试问题,非常非常非常多,但真正能深刻理解并掌握这个有趣特性的人,很少(我并不是这部分很少的人之一,只是能有幸站在前人的肩膀上用自己一点浅薄的经验来发表见解)。

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

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

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

在了解多线程之前,最好先知道什么是并发,什么是并行(这在我之前写的《CPU的分身术:千手观音与齐天大圣》里面有提到过)。不然很容易迷糊。

总的来说,就是这样:

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

 

 

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

 

 

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

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

2、关键字:synchronized和volatile

3、线程池:ThreadPoolExecutor

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

5、CAS:各种原子类

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

 

就如同每个人在不同的年龄阶段有不同的特征和生活状态一样,也可以用“生命周期”来描述线程在不同阶段下的不同特征。线程生命周期涉及到不同线程状态间的转换,这种状态变换,最早可以追溯到《操作系统》中的PV操作与信号灯。PV操作是一种实现进程互斥与同步的方法,P(passeren)表示通过,V(vrijgeven)表示释放。信号灯其实就是交通信号灯,为了避免出现冲突(交通事故),同时更有效地利用公路资源,通过信号灯来实现交通管理:

 

 

 

再说回Java,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先执行

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法算是比较简单的,是基础中的基础。但是用好很难,关键在于多练多想,多多尝试各种组合。

 

 


 

 

感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

 

标签:Java,Thread,t1,start,线程,println,多线程
From: https://www.cnblogs.com/xiangwang1111/p/16815106.html

相关文章

  • JavaScript中的Generator生成器的基本用法
    ES6入门-阮一峰:Generator函数1.介绍Generator生成器是ES6提供的一种异步编程解决方案。是一个极为灵活的结构,拥有在函数块中暂停和恢复代码执行的能力。执行Gen......
  • Java语言程序设计第五讲,异常处理
    学习Java变成过程中我们遇到过许多程序报错现象之后我们会查看报错原因,对代码进行有针对性的修改从而使其恢复正常 这就是异常处理的目的和用途:计算机提供准确的错徐信......
  • java基础知识
    JAVA基础知识标识符和关键字关键字标识符无论类名、方法名、变量名都是标识符命令规则:所有标识符都应该以大小写字符、美元符号、或下划线开始首字符后可以......
  • Java程序设计语言第四讲,继承与多态
    Java中通过extends实现继承并且,Java中只允许单继承,从而避免里多继承中可能产生的父类属性冲突问题。子类自动拥有父类声明的public和protected的成员。 继承条件下的......
  • 不妨试试更快更小更灵活Java开发框架Solon
    @目录概述定义性能架构实战SolonWeb示例SolonMybatis-Plus示例SolonWebSocket示例SolonRemotingRPC示例SolonCloudNacos示例概述定义Solon官网地址https://so......
  • 手写基于Java RMI的RPC框架
    留给读者其中最大的区别就是ZooKeeper注册中心,注册中心可以有读写监听器,这是一个优势,可以用来实现订阅通知,也能做数据的同步,甚至可以做基于读写分离的RPC框架,而且它是基......
  • JavaScript实现数据结构 -- 集合
    集合集合是一种无序且唯一的数据结构,在ES6中有集合Set。集合的常用操作去重使用Set结合展开运算符实现数组去重。判断元素是非在集合中使用Set的has方法判断元素是......
  • JavaScript实现数据结构 -- 字典
    字典字典与集合类似,也是一种存储唯一值的数据结构,字典以键值对的形式进行存储,在ES6中有字典Map。字典的常用操作增使用set()方法可以向字典中添加新成员,可连续添加。......
  • 各种线程状态的打断方法
    一、方法方法名static功能说明注意run()新线程启动后会调用的方法,线程之间串行如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnabl......
  • JAVA基本类型和包装类型
    JAVA基本类型和包装类型前言Java语言中的数据类型分为基本数据类型和引用类型,而我们进行Java开发的时候都听说过基本数据类型和包装类型,今天我们就来详细聊一聊Java中的......