目录

  • 敞开心扉,一起聊聊Java多线程(结尾有福利~)
    • 一、线程的实现方式
      • 1.继承Thread类,重写run方法
      • 2.实现Runnable接口,重写run方法
      • 3.实现Callable接口重写run方法,通过FutureTask包装器获取返回值
    • 二、线程的生命周期
    • 三、线程状态转换
    • 四、线程的启动
      • 经典面试题
      • 源码剖析
    • 五、线程的终止
      • 1.暴力终止法:stop()
      • 2.自定义标志位终止
      • 3.优雅中断法:interrupt()
    • 六、线程的复位
      • 1.Thread.interrupted()
      • 2.通过抛出InterruptedException异常
  • 福利:梁博-《出现又离开》

敞开心扉,一起聊聊Java多线程(结尾有福利~)

今天!我们来聊一聊 多线程 ~

我们都知道,不论在是面试还是工作中,多线程都是一些老生常谈的话题,

相信正在阅读得你,脑海中已然浮现出多线程的相关知识,那么,我们来一起回顾下吧 ~

注意:本片博文前面内容重点在于回顾,后面内容重点讲解线程的生命周期以及线程的源码剖析

一、线程的实现方式

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法
  • 实现Callable接口重写run方法,通过FutureTask包装器获取返回值

1.继承Thread类,重写run方法

/*** 多线程*      继承 Thread方式* @author zhaojun*/
public class MyThread extends Thread {public MyThread(String name) {// 支持自定义线程名称super(name);}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " -> " + Thread.currentThread().getId());}/*** 测试*/public static void main(String[] args) {new MyThread("thread_1").start();new MyThread("thread_2").start();System.out.println(Thread.currentThread().getName() + " -> " + Thread.currentThread().getId());}}

测试结果如下:

2.实现Runnable接口,重写run方法

/*** 多线程*      实现 Runnable方式* @author zhaojun*/
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " -> " + Thread.currentThread().getId());}/*** 测试*/public static void main(String[] args) {new Thread(new MyRunnable(), "runnable_1").start();new Thread(new MyRunnable(), "runnable_2").start();System.out.println(Thread.currentThread().getName() + " -> " + Thread.currentThread().getId());}}

测试结果如下:

3.实现Callable接口重写run方法,通过FutureTask包装器获取返回值

/*** 多线程*      实现 Callable方式, 利用 FutureTask获取返回值* @author zhaojun*/
public class MyCallable implements Callable<String> {// Callable接口支持指定泛型,对应call返回值类型为指定泛型@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName() + " -> " + Thread.currentThread().getId());return Thread.currentThread().getName() + "线程当前运行状态为:" + Thread.currentThread().getState();}/*** 测试*/public static void main(String[] args) throws Exception {FutureTask<String> task1 = new FutureTask<>(new MyCallable());new Thread(task1, "callable_1").start();System.out.println(task1.get());FutureTask<String> task2 = new FutureTask<>(new MyCallable());new Thread(task2, "callable_2").start();System.out.println(task2.get());System.out.println(Thread.currentThread().getName() + " -> " + Thread.currentThread().getId());}}

测试结果如下:

二、线程的生命周期

  • 线程初始状态:NEW
  • 线程运行状态:RUNNABLE
  • 线程阻塞状态:BLOCKED
  • 线程等待状态:WAITING
  • 超时等待状态:TIMED_WAITING
  • 线程终止状态:TERMINATED

这并不是笔者胡乱编造的,而是jdk源码中定义的(Thread类中维护的一个枚举类),源码如下并加以翻译:

/*** 多线程*      源码定义 - 翻译* @author zhaojun*/
public enum State {/*** 线程初始状态*      线程被构建,还未调用 start方法*/NEW,/*** 线程运行状态*      JAVA线程把操作系统中的(就绪和运行)两种状态统一称为 运行中*/RUNNABLE,/*** 线程阻塞状态*      表示线程进入等待状态,即线程因为某种原因放弃了 CPU使用权,阻塞也分为几种情况:*       1.等待阻塞:运行的线程执行 wait方法,JVM会把当前线程放入到等待队列*       2.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么 JVM会把当前的线程放入到锁池中*       3.其他阻塞:运行的线程执行 Thread.sleep或者 join方法,或者发出了 I/O请求时,*                  JVM会把当前线程设置为阻塞状态,当 sleep结束/join线程终止、I/O处理完毕则线程恢复*/BLOCKED,/*** 线程等待状态*/WAITING,/*** 线程超时等待状态*      超时之后自动返回*/TIMED_WAITING,/*** 线程终止状态*      表示当前线程执行完毕*/TERMINATED;
}

三、线程状态转换

此处重点讲下,线程状态如何变更为:
       1.TIME_WAITING:线程超时等待状态
       2.WAITING:线程等待状态
       3.BLOCKED:线程阻塞状态

/*** 多线程*      状态转换 - 代码演示* @author zhaojun*/
public class ThreadStatus {public static void main(String[] args) {/*** Thread -> TIME_WAITING:线程超时等待状态*/Thread timeWaiting = new Thread(() -> {while (true) {try {// sleep 99sTimeUnit.SECONDS.sleep(99);} catch (InterruptedException e) {e.printStackTrace();}}}, "Time_Waiting_Thread");/*** Thread -> WAITING:线程等待状态*/Thread waiting = new Thread(() -> {while (true) {synchronized (ThreadStatus.class) {try {// waitThreadStatus.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}, "Waiting_Thread");/*** Thread -> BLOCKED:线程阻塞状态*/Thread blocked_thread_01 = new Thread(new BlockedThread(), "Blocked_Thread_01");Thread blocked_thread_02 = new Thread(new BlockedThread(), "Blocked_Thread_02");/*** 启动*/timeWaiting.start();waiting.start();blocked_thread_01.start();blocked_thread_02.start();}/*** 定义阻塞线程类*/static class BlockedThread extends Thread {@Overridepublic void run() {synchronized (BlockedThread.class) {while (true) {try {TimeUnit.SECONDS.sleep(99);} catch (InterruptedException e) {e.printStackTrace();}}}}}}

测试结果如下:

首先运行main方法,在当前测试类任意位置右击,点击Open in Terminal

输入jps -l命令,查看所有java进程对应pid,查找ThreadStatus对应 pid

输入 jstack pid命令,查看其堆栈信息:


从上图中,可以很清晰的看出:
线程Waiting_Thread 通过 wait();,状态 -> 等待状态;
线程Time_Waiting_Thread 通过sleep(),状态 -> 超时等待状态;
线程Blocked_Thread_01先获得锁,然后通过sleep(),状态 -> 超时等待状态;
线程Blocked_Thread_02未获得锁,状态 -> 阻塞状态;

四、线程的启动

经典面试题

为什么启动一个线程调用 start方法,而不是run方法呢?

源码剖析

接下来,带大家来看一下 start()方法,在源码中如何定义的:

    public synchronized void start() {if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */group.add(this);boolean started = false;try {start0(); // -.- 目光集聚这里started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}private native void start0(); // -.- 目光集聚这里
public class Thread implements Runnable {/* Make sure registerNatives is the first thing <clinit> does. */private static native void registerNatives();static {registerNatives();}}

我们发现 start()实际上调用了 start0()来启动线程,而且 start0()是由native修饰的本地方法
这里先记住,start0()这个方法是在 Thread 的静态块中来注册的。

到这里,我需要给各位开发小伙伴科普一个文件 Thread.c
该文件定义了各个操作系统平台要用的关于线程的公共数据以及操作,代码如下:

#include "jni.h"
#include "jvm.h"#include "java_lang_Thread.h"#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))static JNINativeMethod methods[] = {{"start0",           "()V",        (void *)&JVM_StartThread},  // -.- 目光集聚这里{"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},{"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},{"suspend0",         "()V",        (void *)&JVM_SuspendThread},{"resume0",          "()V",        (void *)&JVM_ResumeThread},{"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},{"yield",            "()V",        (void *)&JVM_Yield},{"sleep",            "(J)V",       (void *)&JVM_Sleep},{"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},{"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},{"interrupt0",       "()V",        (void *)&JVM_Interrupt},{"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},{"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},{"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},{"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};#undef THD
#undef OBJ
#undef STEJNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls) // 发现 registerNatives()方法定义在这里
{(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

从代码中看到,start0()会执行JVM_StartThread这个方法,那么问题来了,JVM_StartThread又是什么呢?俗话说,码如其名,先从名字推断应该是在 JVM层启动一个线程,既然有了猜想,我们不妨去验证下。

注意:这里需要下载 hotspot的源码,它是JVM的具体实现,有兴趣的小伙伴可以自行下载

不过,大家莫慌,针对线程启动的源码我会附上
我们找到 jvm.cpp文件,源码如下:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))JVMWrapper("JVM_StartThread");JavaThread *native_thread = NULL;...native_thread = new JavaThread(&thread_entry, sz);...

从代码中可以看出,JVM_ENTRY用来定义JVM_StartThread函数的,在这个函数中创建了一个真正的和平台有关的本地线程,之后new了一个javaThread,看看其具体做了什么:

再找到 thread.cpp文件,源码如下:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread()
#if INCLUDE_ALL_GCS, _satb_mark_queue(&_satb_mark_queue_set),_dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{if (TraceThreadEvents) {tty->print_cr("creating thread %p", this);}initialize();_jni_attach_state = _not_attaching_via_jni;set_entry_point(entry_point);// Create the native thread itself.// %note runtime_23os::ThreadType thr_type = os::java_thread;thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :os::java_thread;os::create_thread(this, thr_type, stack_sz);_safepoint_visible = false;}

此函数有两个参数:

1.函数名称,线程创建成功之后根据函数名称调用对应函数;

2.当前进程内已有的线程数量

在上述代码19行,os::create_thread,是调用平台创建线程的方法从而来进行创建线程,接下来就是线程的启动了,代码如下:

void Thread::start(Thread* thread) {trace("start", thread);// Start is different from resume in that its safety is guaranteed by context or// being called from a Java method synchronized on the Thread object.if (!DisableStartThread) {if (thread->is_Java_thread()) {// Initialize the thread state to RUNNABLE before starting this thread.// Can not set it after the thread started because we do not know the// exact thread state at that time. It could be in MONITOR_WAIT or// in SLEEPING or some other state.java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),java_lang_Thread::RUNNABLE);}os::start_thread(thread);}
}

上述代码14行,os::start_thread(thread)就是平台启动线程的方法,最终会调用 Thread.cpp文件中的 JavaThread::run()方法,至此,一个线程的启动就完成了。

五、线程的终止

  • 暴力终止法:stop()
  • 自定义标志位终止
  • 优雅中断法:interrupt()

1.暴力终止法:stop()


如图所示,官方已将 stop()定义为过时方法,并不建议使用;
stop()方法在结束一个线程时并不会保证线程的资源正常释 放,因此会导致程序可能出现一些不确定的状态。

JDK官方文档中定义弃用这些方法的原因,链接如下:
Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?

2.自定义标志位终止

首先我们知道,一个线程结束与否取决于当前线程的 run()方法是否执行完毕,我们可自定义标志位来控制线程的结束,代码如下:

/*** 多线程*      线程终止:自定义标志位*/
public class ThreadFlagDemo extends Thread {// 标志位private volatile boolean isFinish = false;private static int i;@Overridepublic void run() {while (!isFinish) {i++;}System.out.println("i:"+i);}/*** 测试*/public static void main(String[] args) throws InterruptedException {ThreadFlagDemo thread = new ThreadFlagDemo();thread.start();Thread.sleep(1000);// 变更标志位,结束线程thread.isFinish = true;}}

如代码所示,我们可以通过自定义一个标志位,来结束死循环致使run()结束,从而来控制线程的结束。

3.优雅中断法:interrupt()

这里需明确一点,调用 interrupt() 方法,表示向当前线程打个招呼,告诉其可以中断线程了,至于什么时候终止,取决于当前线程自己,其实原理跟自定义标志位相似,只是打一个停止的标志,并不会去真的停止线程。

/*** 多线程*      线程终止:interrupt()方法*/
public class ThreadInterruptDemo {private static int i;/*** 测试*/public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {// isInterrupted()默认为falsewhile (!Thread.currentThread().isInterrupted()) {i++;}System.out.println("i:"+i);});thread.start();TimeUnit.SECONDS.sleep(1);// 将isInterrupted()设置为truethread.interrupt();}}

这种通过标志位或中断操作的方式能够使线程在终止时可以继续执行内部逻辑,而不是立即停止线程,所以,这种中断线程的方式更加的优雅安全,推荐此种方式

六、线程的复位

  • Thread.interrupted()
  • 通过抛出InterruptedException异常

1.Thread.interrupted()

/*** 多线程*      线程复位:Thread.interrupted()方法*/
public class ThreadInterruptedReset {/*** 测试*/public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {String threadName = Thread.currentThread().getName();while (true) {if(Thread.currentThread().isInterrupted()) {System.out.println(threadName + ":before -> " + Thread.currentThread().isInterrupted());// 线程复位Thread.interrupted();System.out.println(threadName + ":after -> " + Thread.currentThread().isInterrupted());break; // 结束}}}, "Thread_Interrupted");thread.start();TimeUnit.SECONDS.sleep(1);// 将isInterrupted()设置为truethread.interrupt();}}

执行流程如下:

  1. 执行main()方法,标识"main"主线程启动,代码自上而下执行
  2. "Thread_Interrupted"线程启动,while循环开启,isInterrupted()默认false,…当前处在死循环中;
  3. 与此同时,"main"主线程sleep 1s结束后,将 isInterrupted()设置为true;
  4. 此时,"Thread_Interrupted"线程中,由于 isInterrupted()当前被设置为true,执行 if块代码,before -> true;
  5. 通过 Thread.interrupted()进行复位,after -> false,最终执行 break,结束循环结束线程。

执行流程如下:

2.通过抛出InterruptedException异常

/*** 多线程*      线程复位:InterruptedException异常*/
public class ThreadExeptionReset {/*** 测试*/public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {String threadName = Thread.currentThread().getName();while (true) {if(Thread.currentThread().isInterrupted()) {try {System.out.println(threadName + ":before -> " + Thread.currentThread().isInterrupted());TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {System.out.println(threadName + ":after -> " + Thread.currentThread().isInterrupted());e.printStackTrace();break; // 结束}}}}, "Thread_InterruptedException");thread.start();TimeUnit.SECONDS.sleep(1);// 将isInterrupted()设置为truethread.interrupt();}}

执行流程如下:

  1. 执行main()方法,标识"main"主线程启动,代码自上而下执行
  2. "Thread_InterruptedException"线程启动,while循环开启,isInterrupted()默认false,…当前处在死循环中;
  3. 与此同时,"main"主线程sleep 1s结束后,将 isInterrupted()设置为true;
  4. 此时,"Thread_InterruptedException"线程中,由于 isInterrupted()当前被设置为true,执行 if块代码,before -> true;
  5. 接着执行sleep(),抛出InterruptedException异常进行复位,after -> false,最终执行 break,结束循环结束线程。

执行流程如下:

福利:梁博-《出现又离开》

今日推荐单曲,梁博先生的《出现又离开》,放松心情,努力学习~
 
差点忘记件比写博客还要重要的事情 ~
这里有 博哥(梁博)的粉丝么, 听说今年有博哥的演唱会, 私信我,约起哈 ~ ❤

敞开心扉,一起聊聊Java多线程相关推荐

  1. java线程深入_深入聊聊Java多线程

    一.背景 在没有学习Java多线程以前,总觉得多线程是个很神秘的东西,只有那些大神才能驾驭,新年假期没事就来学习和了解一下Java的多线程,本篇博客我们就来从头说一下多线程到底是怎么回事. 二.概述 ...

  2. Java多线程面试准备:聊聊Executor框架

    点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 精彩内容 java实战练习项目教程 2018微服务资源springboot.s ...

  3. java多线程总结图_Java多线程总结之Queue

    标签:多线程(52)JAVA(605) 上个星期总结了一下synchronized相关的知识,这次将Queue相关的知识总结一下,和朋友们分享. 在Java多线程应用中,队列的使用率很高,多数生产消费 ...

  4. Java多线程闲聊(四):阻塞队列与线程池原理

    Java多线程闲聊(四)-阻塞队列与线程池原理 前言 复用永远是人们永恒的主题,这能让我们更好地避免重复制造轮子. 说到多线程,果然还是绕不开线程池,那就来聊聊吧. 人们往往相信,世界是存在一些规律的 ...

  5. Java多线程闲聊(二):活锁和死锁

    Java多线程闲聊(二):活锁和死锁 这两个情况其实都是应该需要避免的情况,为了便于自己的回顾,我还是希望通过尽可能简单的表达来进行简要的归纳. 何谓死锁,就是正正紧紧按照Java的规范进行编程依然会 ...

  6. java基础提升篇:深入浅出Java多线程

    初遇 Java给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特别的形式,但多线程 ...

  7. java书籍_还搞不定Java多线程和并发编程面试题?你可能需要这一份书单!

    点击蓝色"程序员书单"关注我哟 加个"星标",每天带你读好书! ​ 在介绍本书单之前,我想先问一下各位读者,你们之前对于Java并发编程的了解有多少呢.经过了1 ...

  8. Java多线程:synchronized | Volatile 和Lock和ReadWriteLock多方位剖析(一)

    前言 本文站在多线程初中级学习者的角度,较为全面系统的带你一起了解多线程与锁相关的知识点.带你一起解开与锁相关的各种概念.用法.利弊等.比如:synchronized.Volatile.Lock.Re ...

  9. java多线程并发之旅-09-java 生产者消费者 Producer/Consumer 模式

    生产者消费者模式 在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等).产生数据的模块,就形象地称为生产 ...

最新文章

  1. SDWebImage使用——一个可管理远程图片加载的类库
  2. 直接引用arXiv论文不规范?试试这个小工具,秒变正式发表链接,上交大校友开发...
  3. Rails源码笔记-ActiveSupport-core_ext-date
  4. PLSQL重点问题理解和实战
  5. 第七天2017/04/14(C++对C的扩充,C++与C的区别,C++的基础知识)
  6. 京东业务增长10倍背后的敏捷开发秘籍【案例+分析】
  7. python3精要(32)-生成器表达式
  8. java thread_Java(多线程Thread)
  9. redhat6 使用raid5的系统安装
  10. Audacity Mac版教程,使用Audacity编辑音频波形图的方法步骤
  11. matlab length_【重点】最优化计算与matlab实现(20)——遗传算法
  12. elementui中给input框赋值成功后input框不能进行编辑问题
  13. 不知道这十项Linux常识,就别说自己玩过Linux!
  14. 旅游管理系统项目java设计_基于JSP的旅游管理系统设计与实现(MyEclipse,SQL)
  15. python加粗线宽代码_python-在matplotlib中同时更改线宽和颜色
  16. 2019开发者调查报告出炉
  17. java创建工厂方法_Java设计模式(八) 之创建型模式(工厂方法模式)
  18. 计算机毕业设计(附源码)python医院人事及科室病区管理
  19. JVM调优前置知识-深堆Retained Heap和浅堆Shallow Heap
  20. QPainter 画扇形

热门文章

  1. 12星座超级独家!通过星座完美你自己!不断加新中……
  2. [xhr4412][extension 4] u-boot-2020.07 DM9621 网卡驱动移植
  3. “代理服务出现问题,或者地址有误“解决方案
  4. matlab绘制引力场_玩引力场和漂亮的色彩
  5. css实现文字左右添加横线
  6. Python爬虫爬取动态网页
  7. 数字图像处理学习笔记(三)——空间分辨率和灰度分辨率、等偏爱曲线
  8. Hashtable简述
  9. pc前端js调起电脑本地应用程序(需要客户端配合 自定义URL Protocol 协议 )
  10. android 使用iphone线控耳机,耳机 篇一:关于安卓手机怎么用苹果专用耳机的问题...