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

java,多线程,线程,生命周期 · 浏览次数 : 244

小编点评

**最难的部分:** * **底层知识:**多线程涉及到数据结构、操作系统的概念,需要理解这些概念才能理解多线程的相关技术。 * **并发和并行:**理解并行和并发的概念对于理解多线程的性能和正确性至关重要。 * **锁:**锁的使用和释放对于实现并发至关重要, understanding锁的实现方式对于理解多线程的关键。 **最有趣的部分:** * **多线程的结果不可预知:**在多线程环境下,程序的运行结果是无法预料的,这与单线程的并发执行结果不同。 * **“5法”:**线程的核心方法是“wait、notify、notifyAll、yield”这5个关键操作,对于理解和实现多线程非常重要。 * **“6类5法”:**线程的生命周期可以被分为6个状态,每个状态代表线程在不同状态下的行为。 **最多人讨论的部分:** * **多线程的性能:**多线程的性能受多个因素的影响,例如线程数量、代码效率、硬件条件等。 * **多线程的并发问题:**当多个线程同时访问同一个资源时,可能会发生并发问题,导致数据一致性问题。 * **多线程的应用:**多线程在并发编程、游戏开发、数据分析等领域有着广泛的应用。

正文

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

 

与Java多线程-线程生命周期(一)相似的内容:

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

如果要问我Java当中最难的部分是什么?最有意思的部分是什么?最多人讨论的部分是什么?那我会毫不犹豫地说:多线程。 Java多线程说它难,也不难,就是有点绕;说它简单,也不简单,需要理解的概念很多,尤其是很多底层知识,如数据结构、操作系统的部分。 Java多线程掌握得好,不仅仅只是对Java,对任何

Java多线程

一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括: Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建

Java多线程-ThreadPool线程池-1(三)

开完一趟车完整的过程是启动、行驶和停车,但老司机都知道,真正费油的不是行驶,而是长时间的怠速、频繁地踩刹车等动作。因为在速度切换的过程中,发送机要多做一些工作,当然就要多费一些油。 而一个Java线程完整的生命周期就包括: 1、T1:创建(启动) 2、T2:运行(行驶) 3、T3:销毁(停车) 而T

Java多线程-JUC-1(八)

前面把线程相关的生命周期、关键字、线程池(ThreadPool)、ThreadLocal、CAS、锁和AQS都讲完了,现在就剩下怎么来用多线程了。而要想用好多线程,其实是可以取一些巧的,比如JUC(好多面试官喜欢问的JUC,就是现在要讲的JUC)。JUC就是java.util.concurrent的

[转帖]Java中线程的生命周期

https://blog.51cto.com/u_15773567/5832430 1 介绍 本篇文章我们讨论下Java中的一个非常核心的概念:线程的生命周期。在Java编程语言中,多线程编程非常重要。线程从创建到销毁是有生命周期的,在线程的生命周期中,线程会经历多种状态(state)。 2 线程状

[Java]线程生命周期与线程通信

出于个人知识掌握程度与时间成本等多方面整体考虑,本篇文章对线程生命周期与线程通信的阐述并非非常详细,故可能并不适合所有博友,一些知识点在文中给出的【启发博文】中可能更容易找到答案。 如果文中阐述有不妥或不对的,多多交流。

Java多线程-线程关键字(二)

Java中和线程相关的关键字就两:volatile和synchronized。 volatile以前用得较少,以后会用得更少(后面解释)。它是一种非常轻量级的同步机制,它的三大特性是: 1、保证可见性,即强制将CPU高速缓存的数据立即写入主存,会导致其他CPU核中对应的高速缓存内容无效,就像这样:

Java多线程-ThreadPool线程池-2(四)

线程池是个神器,用得好会非常地方便。本来觉得线程池的构造器有些复杂,即使讲清楚了对今后的用处可能也不太大,因为有一些Java定义好的线程池可以直接使用。但是(凡事总有个但是),还是觉得讲一讲可能跟有助于理解后面的常用线程池,所以该打脸还是打吧 因为直接结合代码看会更清楚一些,所以我把带注释的代码贴出

Java多线程-ThreadPool线程池-3(五)

除了可以通过ThreadPoolExecutor自定义线程池外,同Stream API中的Collectors一样,多线程里的Executors类也提供了一组相关的线程池工具,可以直接拿来用,不用考虑用什么队列合适的问题。 Javac除了传统的四大线程池工具: 1、newFixedThreadPoo

java多线程编程:你真的了解线程中断吗?

java.lang.Thread类有一个 interrupt 方法,该方法直接对线程调用。当被interrupt的线程正在sleep或wait时,会抛出 InterruptedException 异常。事实上, interrupt 方法只是改变目标线程的中断状态(interrupt status),...