1.线程概述


声明:本系列文章以动力节点视频为主,及其他文章资源汇总
动力节点视频(推荐)
后续文章不再声明哈,文章内容也会继续补充修改,有问题地方请评论指出。
学习视频b站有的,此处声明为尊重作者,无任何商业推广。

大佬地址:
并发与并行的区别


1.1线程相关概念

1.1.1进程

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

可以把进程简单的理解为在操作系统中运行的一个程序。

  • 进程我们把运行中的程序叫做进程(概念)。每个进程都会占用内存与CPU资源(动态性)。进程与进程之间各自占用各自的内存资源,互相独立(独立性)。

  • 线程:线程就是进程中的一个执行单元,负责当前进程中程序的执行。一个进程可以包含多个线程。一个进程包含了多个线程就是多线程。多线程可以提高程序的并行运行效率。

    线程简述:

    线程是进程的执行单元,用来执行代码。

1.1.2线程

线程(thread)是进程的一个执行单元,一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支。

进程是线程的容器,一个进程至少有一个线程,一个进程中也可以有多个线程。

在操作系统中是以进程为单位分配资源,如虚拟存储空间、文件描述符等。每个线程都有各自的线程栈,自己的寄存器环境,自己的线程本地存储。

1.1.3主线程与子线程

JVM 启动时会创建一个主线程,该主线程负责执行 main 方法,主线程就是运行 main 方法的线程。
Java 中的线程不是孤立的,线程之间存在一些联系,如果在 A 线程中创建了 B 线程,则称 B 线程为 A 线程的子线程,相应的 A 线程就是 B 线程的父线程。

1.1.4串行、并发与并行

并发可以提高对事物的处理效率,即在一段时间内可以处理或者完成更多的事情。

并发是一种现象:同时运行多个程序或多个任务需要被处理的现象

这些任务可能是并行执行的,也可能是串行执行的,和CPU核心数无关,是操作系统进程调度和CPU上下文切换达到的结果

解决大并发的一个思路是将大任务分解成多个小任务:

可能要使用一些数据结构来避免切分成多个小任务带来的问题

可以多进程/多线程并行的方式去执行这些小任务达到高效率

或者以单进程/单线程配合多路复用执行这些小任务来达到高效率

举个🌰:

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。

并行的关键是你有同时处理多个任务的能力。

所以我认为它们最关键的点就是:是否是『同时』

作者:知乎用户
链接:https://www.zhihu.com/question/33515481/answer/58849148
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

并行是一种更为严格,理想的并发。

小贴士:

​ 并行:两个或两个以上的事件在同一时刻发生(同时发生)

​ 并发:两个或两个以上的事件在一个时间段内发生(交替执行)

1.1.5 线程调度

​ 计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令。所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务。那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度。JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。

线程调度简述:

在单核CPU中,同一个时刻只有一个线程执行,根据CPU时间片算法依次为每个线程服务,这就叫线程调度。

1.2线程的创建与启动

在 Java 中,创建一个线程就是创建一个 Thread 类(子类)的对象(实例) 。

Thread 类有两个常用 的构造方法:

Thread()

Thread(Runnable target)

对应的创建线程的两种方式:

定义 Thread 类的子类
定义一个 Runnable 接口的实现类

这两种创建线程的方式没有本质的区别。

1.2.1继承Thread类

public class ThreadTest extends Thread {
    /*
    * 继承Thread类,重写run()方法
    * run()方法体中就是子线程执行的任务
    * */
    @Override
    public void run() {
        System.out.println("子线程执行打印内容。。。");
    }
}
public class MainThread {
    public static void main(String[] args) {
        System.out.println("主线程main()方法执行。。。");

        // 创建子线程对象
        ThreadTest threadTest = new ThreadTest();

        /*
        * 启动子线程
        *
        * 调用线程的start()方法来启动线程,启动线程的实质就是请求JVM运行相应的线程,
        * 这个线程具体在什么时候运行由线程调度器(Scheduler)决定
        *
        * 注意:
        *   1.start()方法调用结束并不意味着子线程开始运行
        *   2.新开启的线程会执行run()方法
        *   3.如果开启了多个线程,start()调用顺序并不一定就是线程启动的顺序
        *   4.多线程运行结果与代码执行顺序或调用顺序无关
        * */
        threadTest.start();

        System.out.println("主线程中其他逻辑内容。。。");
    }
}

运行结果:

1.2.2实现Runnable接口

/**
 * 实现Runnable接口,重写run()方法
 */
public class RunnableTest implements Runnable {
    @Override
    public void run() {
        // 方法体中,子线程执行的任务内容
        System.out.println("subThread implements Runnable...");
    }
}
public class MyRunnable {
    public static void main(String[] args) {
        // 方式1:创建Runnable实现类对象
        RunnableTest runnableTest = new RunnableTest();
        // 创建线程
        Thread thread = new Thread(runnableTest);
        // 启动线程
        thread.start();

        // 方式2:使用Thread(Runnable target)构造方法,通过匿名内部类实现
        Thread anonymity = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("anonymity thread...");
            }
        });
        // 启动线程
        anonymity.start();

        System.out.println("main thread...");
    }
}

运行结果:

上面两个哪个好?
Thread类继承存在单继承的局限性,而接口不会体现数据共享的概念(JMM内存模型图),代码可以被多个线程共享,代码和数据独立。
Runnable实现线程可以对线程进行复用,因为runnable是轻量级对象,而Thread不行,它是重量级对象

1.2.3使用匿名内部类

public static void main(String[] args) {
        // 使用匿名内部类方式创建Runnable实例
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("输出"+i);
                }
            }
        });
        t1.start();

        // lambda 表达式简化语法
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                System.out.println("输出"+i);
            }
        });
        t2.start();
}

1.2.4实现Callable接口

可以得到执行结果

public class A3Callable {
    public static void main(String[] args) {
        //FutureTask包装我们的任务,FutureTask可以用于获取执行结果  
        FutureTask<Integer> ft = new FutureTask<>(new MyCallable());   /ˈfjuːtʃə(r)/  /tɑːsk
        //创建线程执行线程任务
        Thread thread = new Thread(ft);
        thread.start();
        try {
            //得到线程的执行结果
            Integer num = ft.get();
            System.out.println("得到线程处理结果:" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    // 实现Callable接口,实现带返回值的任务
    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int num = 0;
            for (int i = 0; i < 1000; i++) {
                System.out.println("输出"+i);
                num += i;
            }
            return num;
        }
    }
}

1.3线程的常用方法

方法名称 说明
start() 启动线程
getId() 获取当前线程ID Thread-编号 该编号从0开始
getName() 获取当前线程名称
stop() 停止线程(已废弃)
getPriority(); 返回线程的优先级
boolean isAlive() 测试线程是否处于活动状态
isDaemon(): 测试线程是否为守护线程
isInterrupted(); 测试线程是否已经中断
interrupt(); 设置当前线程为终止状态
Thread.currentThread() 获取当前线程对象
Thread.state getState() 获取线程的状态
构造函数 说明
Thread() 分配一个新的 Thread 对象
Thread(String name) 分配一个新的 Thread对象,具有指定的 name正如其名。
Thread(Runable runnable) 分配一个新的 Thread对象

1.3.1 currentThread()方法

Thread.currentThread()方法可以获得当前线程,Java 中的任何一段代码都是执行在某个线程当中的。执行当前代
码的线程就是当前线程。
同一段代码可能被不同的线程执行,因此当前线程是相对的,Thread.currentThread()方法的返回值是在代码实际运行时候的线程对象。

public class CurrentThreadTest extends Thread {

    public CurrentThreadTest() {
        System.out.println("subConstructor == "+Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("subThread == " + Thread.currentThread().getName());
    }
}
public class CurrentTest {
    public static void main(String[] args) {
        System.out.println("当前主线程:" + Thread.currentThread().getName());

        // 创建子线程实例
        CurrentThreadTest threadTest = new CurrentThreadTest();
        // 启动线程
        threadTest.start();
    }
}

运行结果:

1.3.2 setName()、getName()

设置线程名称

thread.setName("线程名称")

返回线程名称

thread.getName()

通过设置线程名称,有助于程序调试,提高程序的可读性,建议为每个线程都设置一个能够体现线程功能的名称。

1.3.3 isAlive()

判断当前线程是否处于活动状态

thread.isAlive()

活动状态就是线程已启动并且尚未终止

1.3.4 sleep()

让当前线程休眠指定的毫秒数,当前线程进入阻塞状态,sleep()不会释放锁,wait()会释放锁

/*
该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会重写进入Runnable状态,等待分配时间片
*/
static void sleep(long ms)

当前线程是指 Thread.currentThread()返回的线程

public class SubThread4 extends Thread {
    @Override
    public void run() {
        try {
            long before = System.currentTimeMillis();
            System.out.println("run, threadname=" + Thread.currentThread().getName() + " ," +
                    "begin= " + before);
            
            // 当前线程睡眠 2000 毫秒
            Thread.sleep(2000);
            
            System.out.println("run, threadname=" + Thread.currentThread().getName()
                    + " ,end= " + System.currentTimeMillis() 
                    + ", 共执行总时长:" + (System.currentTimeMillis() - before));
            
        } catch (InterruptedException e) {
            /*
            * 在子线程的 run 方法中, 如果有受检异常(编译时异常)需要处理,只有选择捕获处理,不能抛出处理
            * 因为父类中 run 方法没有抛出异常
            * */
            e.printStackTrace();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        SubThread4 t4 = new SubThread4();
        System.out.println("main__begin = " + System.currentTimeMillis());
        t4.start(); //开启新的线程
        System.out.println("main__end = " + System.currentTimeMillis());
    }
}

运行结果:

1.3.5 getId()

可以获得线程的唯一标识

thread.getId()

注意:
某个编号的线程运行结束后,该编号可能被后续创建的线程使用,重启的 JVM 后,同一个线程的编号可能不一样。

1.3.6 yield()

线程让步,放弃当前的 CPU 资源

  1. thread.yield() 让出CPU的时间片尽量切换其他线程去执行
  2. 使正在运行中的线程重新变成就绪状态,并重新竞争 CPU 的调度权。它可能会获取到,也有可能被其他线程获取到。
// 该方法用于使当前线程主动让出当次CPU时间片,等待重新分配时间片
static void yield()

1.3.7 setPriority()

设置线程的优先级

void setPriority(int priority)

线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们通过提高线程的优先级来最大程度的改善线程获取时间片的几率。

线程的优先级被划分为10级,值分别为1 ~ 10,其中1最低,10最高;线程提供了3个常量来表示最低、最高,以及默认优先级:

Thread.MIN_PRIORITY    1
Thread.MAX_PRIORITY    10
Thread.NORM_PRIORITY   5

java 线程的优先级取值范围是 1 ~ 10,如果超出这个范围会抛出异常 IllegalArgumentException。

在操作系统中,优先级较高的线程获得 CPU 的资源越多,线程优先级本质上是只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程。

注意:

不能保证优先级高的线程先运行

Java 优先级设置不当或者滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿。

线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,即在开发时不必设置线程的优先级。

线程的优先级具有继承性,在 A 线程中创建了 B 线程,则 B 线程的优先级与 A 线程是一样的。

1.3.9 setDaemon()

Java 中的线程分为用户线程与守护线程。守护线程是为其他线程提供服务的线程,如 垃圾回收器(GC)就是一个典型的守护线程。

守护线程不能单独运行,当 JVM 中没有其他用户线程,只有守护线程时,守护线程会自动销毁,JVM 会退出。

设置守护线程的代码要放在启动线程之前,否则会抛出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。

thread.setDaemon(true);
thread.start();
/**
 * 守护线程
 */
public class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("守护线程执行===");
        }
    }
}
/**
 * 子线程
 */
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("子线程执行===" + i);
        }
    }
}
public class MainThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setDaemon(true);
        myThread.start();

        for (int i = 0; i <= 100; i++) {
            System.out.println("主线程执行===" + i);
        }

        MyThread2 myThread2 = new MyThread2();
        myThread2.start();
    }
}

执行结果:

主线程执行结束后,守护线程依然在执行,子线程执行完成后,守护线程才结束。这里想验证了,是不是哪个线程创建了守护线程,当这个线程结束后守护线程就不再执行,这里得到的结果是,只要有线程在执行,守护线程就会一直执行,直到没有线程执行,守护线程才会终止执行。验证了:守护线程不能单独运行,当 JVM 中没有其他用户线程,只有守护线程时,守护线程会自动销毁,JVM 会退出。

1.3.10 join()

  1. join介绍

    ​ t.join()方法会使主线程进入等待池并等待 t 线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。

    ​ t.join()中的 t 优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行。

    线程调用了join方法,那么就要一直运行到该线程运行结束,才会运行其他进程,这样可以控制线程执行顺序。

  2. join() 内部实现调用了wait(),会占用锁。线程结束,锁释放。

  3. join(long millis)

    1. 如果为0表示永远等待,其实是等到 t 结束后。
    2. 传入指定的时间会调用wait(millis), 时间到锁释放。不再等待。
void join();
void join(long millis);
void join(long millis, int nanos);

大佬地址请优先查看!请优先查看!请优先查看!

JAVA多线程中join()方法的详细分析:原文链接

虽然关于讨论线程join()方法的博客已经非常极其特别多了,但是前几天我有一个困惑却没有能够得到详细解释,就是当系统中正在运行多个线程时,join()到底是暂停了哪些线程,大部分博客给的例子看起来都像是t.join()方法会使所有线程都暂停并等待t的执行完毕。当然,这也是因为我对多线程中的各种方法和同步的概念都理解的不是很透彻。通过看别人的分析和自己的实践之后终于想明白了,详细解释一下希望能帮助到和我有相同困惑的同学。

首先给出结论:t.join()方法只会使主线程(或者说调用t.join()的线程)进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。

下面则是分析过程。

之前对于join()方法只是了解它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行。

public class TestJoin {
 
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		ThreadTest t1=new ThreadTest("A");
		ThreadTest t2=new ThreadTest("B");
		t1.start();
		t2.start();
	}
 
}
class ThreadTest extends Thread {
	private String name;
	public ThreadTest(String name){
		this.name=name;
	}
	public void run(){
		for(int i=1;i<=5;i++){
				System.out.println(name+"-"+i);
		}		
	}
}

运行结果:

A-1
B-1
B-2
B-3
A-2
B-4
A-3
B-5
A-4
A-5

可以看出A线程和B线程是交替执行的。

而在其中加入join()方法后(后面的代码都略去了ThreadTest类的定义)

public class TestJoin {
 
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		ThreadTest t1=new ThreadTest("A");
		ThreadTest t2=new ThreadTest("B");
		t1.start();
		t1.join();
		t2.start();
	}
}

运行结果:

A-1
A-2
A-3
A-4
A-5
B-1
B-2
B-3
B-4
B-5

显然,使用t1.join()之后,B线程需要等A线程执行完毕之后才能执行。需要注意的是,t1.join()需要等t1.start()执行之后执行才有效果,此外,如果t1.join()放在t2.start()之后的话,仍然会是交替执行,然而并不是没有效果,这点困扰了我很久,也没在别的博客里看到过。

为了深入理解,我们先看一下join()的源码。

/**
 * Waits for this thread to die.
 *
 * <p> An invocation of this method behaves in exactly the same
 * way as the invocation
 *
 * <blockquote>
 * {@linkplain #join(long) join}{@code (0)}
 * </blockquote>
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final void join() throws InterruptedException {
    join(0);            //join()等同于join(0)
}
/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
 
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
 
    if (millis == 0) {
        while (isAlive()) {
            wait(0);           //join(0)等同于wait(0),即wait无限时间直到被notify
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

可以看出,join()方法的底层是利用wait()方法实现的。可以看出,join方法是一个同步方法,当主线程调用t1.join()方法时,主线程先获得了t1对象的锁,随后进入方法,调用了t1对象的wait()方法,使主线程进入了t1对象的等待池,此时,A线程则还在执行,并且随后的t2.start()还没被执行,因此,B线程也还没开始。等到A线程执行完毕之后,主线程继续执行,走到了t2.start(),B线程才会开始执行。

此外,对于join()的位置和作用的关系,我们可以用下面的例子来分析

public class TestJoin {
 
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+" start");
		ThreadTest t1=new ThreadTest("A");
		ThreadTest t2=new ThreadTest("B");
		ThreadTest t3=new ThreadTest("C");
		System.out.println("t1start");
		t1.start();
		System.out.println("t2start");
		t2.start();
		System.out.println("t3start");
		t3.start();
		System.out.println(Thread.currentThread().getName()+" end");
	}
 
}

运行结果为

main start
t1start
t1end
t2start
t2end
t3start
t3end
A-1
A-2
main end
C-1
C-2
C-3
C-4
C-5
A-3
B-1
B-2
B-3
B-4
B-5
A-4
A-5

A、B、C和主线程交替运行。加入join()方法后

public class TestJoin {
 
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+" start");
		ThreadTest t1=new ThreadTest("A");
		ThreadTest t2=new ThreadTest("B");
		ThreadTest t3=new ThreadTest("C");
		System.out.println("t1start");
		t1.start();
		System.out.println("t1end");
		System.out.println("t2start");
		t2.start();
		System.out.println("t2end");
		t1.join();
		System.out.println("t3start");
		t3.start();
		System.out.println("t3end");
		System.out.println(Thread.currentThread().getName() + " end");
	}
 
}

运行结果:

main start
t1start
t1end
t2start
t2end
A-1
B-1
A-2
A-3
A-4
A-5
B-2
t3start
t3end
B-3
main end
B-4
B-5
C-1
C-2
C-3
C-4
C-5

多次实验可以看出,主线程在t1.join()方法处停止,并需要等待A线程执行完毕后才会执行t3.start(),然而,并不影响B线程的执行。因此,可以得出结论,t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。

PS:join源码中,只会调用wait方法,并没有在结束时调用notify,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有的资源和锁。

1.3.11 yield 和 sleep 的异同

1、yield、sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。
2、yield、sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。
3、yield 不能被中断,而 sleep 则可以接受中断。

1.4 线程的生命周期

在 JVM 运行中,线程一共有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种状态,这些状态对应 Thread.State 枚举类中的状态。

线程的生命周期是线程对象的生老病死,即线程的状态。
线程生命周期可以通过 getState()方法获得,线程的状态是Thread.State 枚举类型定义的,有以下几种:

public class Thread implements Runnable {
    public static enum State {
            NEW,
            RUNNABLE,
            BLOCKED,
            WAITING,
            TIMED_WAITING,
            TERMINATED;

            private State() {
            }
        }
}

NEW

新建状态,创建了线程对象,线程还没有开始运行,此时线程处在新建状态,调用 start()启动之前的状态;

RUNNABLE

可运行状态,它是一个复合状态,包 含:READY 和 RUNNING 两个状态。

READY 状态(就绪状态)

该线程可以被线程调度器进行调度使它处于 RUNNING 状态;

一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由运行时系统的线程调度程序(thread scheduler)来调度的。

RUNING 状态(运行状态)

当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。

Thread.yield()方法可以把线程由 RUNNING 状态转换为 READY 状态

BLOCKED

阻塞状态,线程发起阻塞的 I/O 操作,或者申请由其他线程占用的独占资源,线程会转换为 BLOCKED 阻塞状态;

处于阻塞状态的线程不会占用 CPU 资源,当阻塞 I/O 操作执行完,或者线程获得了申请的资源,线程可以转换为 RUNNABLE;

在运行态中的线程进入 synchronized 同步块或者同步方法时,如果获取锁失败,则会进入到 BLOCKED 状态。当获取到锁后,会从 BLOCKED 状态恢复到就绪状态。

线程运行过程中,可能由于各种原因进入阻塞状态:
1> 线程试图得到一个锁,而该锁正被其他线程持有;
2> 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3> 线程在等待某个触发条件;

WAITING

等待状态,线程执行了 Object.wait()、thread.join() 方法会把线程转换为 WAITING 等待状态;

执行 Object.notify()方法,或者加入的线程执行完毕,当前线程会转换为 RUNNABLE 状态

TIMED_WAITING

与 WAITING 状态类似,都是等待状态,区别在于处于该状态的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为 RUNNABLE;

TERMINATED

终止状态,线程结束处于终止状态。

有两个原因会导致线程死亡:

1)run方法正常退出而自然死亡;

2)一个未捕获的异常终止了run()方法而使线程终止;

1.4.1 状态转换

Java线程的状态及转换传送门

BLOCKED 和 WAITING 状态的区别和联系

简单来说,处于BLOCKED状态的线程,还是在竞争锁的,一旦cpu有时间,它竞争到了锁、就会执行。
但是WAITING状态的线程则不去竞争锁,需要等待被动通知、或者自己定的闹钟(等待时间)到了、再去竞争锁。

1.4.3 查看进程堆栈

使用jstack可查看指定进程(pid)的堆栈信息,用以分析线程执行状态:

1.5 多线程编程的优势与存在的风险

多线程编程具有以下优势:
1.提高系统的吞吐率,多线程编程可以使一个进程有多个并发,即同时进行的操作
2.提高响应性,Web 服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间
3.充分利用多核处理器资源,通过多线程可以充分的利用 CPU 资源的优势

多线程的常见应用场景:

1.后台任务,例如:定时向大量(100w以上)的用户发送邮件;

2.异步处理,例如:统计结果、记录日志、发送短信等;

3.分布式计算、分片下载、断点续传

多线程编程存在的问题与风险:
1.线程安全问题,多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数据(过期的数据),如丢失数据更新。
2.线程活性问题,由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非 RUNNABLE 状态,这就是线程活性问题,
常见的活性故障有以下几种:

(1) 死锁(Deadlock)
(2) 锁死(Lockout)
(3) 活锁(Livelock)
(4) 饥饿(Starvation)

3.上下文切换,处理器从执行一个线程切换到执行另外一个线程

4.可靠性,可能会由一个线程导致 JVM 意外终止,其他的线程也无法执行

posted @ 2022-02-22 23:05  Lz_蚂蚱  阅读(71)  评论(0)    收藏  举报