多线程java_敞开心扉,一起聊聊Java多线程
敞开心扉,一起聊聊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); } @Override public 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 { @Override public 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 { // Callable接口支持指定泛型,对应call返回值类型为指定泛型 @Override public 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 task1 = new FutureTask<>(new MyCallable()); new Thread(task1, "callable_1").start(); System.out.println(task1.get()); FutureTask 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 99s TimeUnit.SECONDS.sleep(99); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Time_Waiting_Thread"); /** * Thread -> WAITING:线程等待状态 */ Thread waiting = new Thread(() -> { while (true) { synchronized (ThreadStatus.class) { try { // wait ThreadStatus.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 { @Override public 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 then it will be passed up the call stack */ } } } private native void start0(); // -.- 目光集聚这里
public class Thread implements Runnable { /* Make sure registerNatives is the first thing 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 JNICALLJava_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_23 os::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; @Override public 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()默认为false while (!Thread.currentThread().isInterrupted()) { i++; } System.out.println("i:"+i); }); thread.start(); TimeUnit.SECONDS.sleep(1); // 将isInterrupted()设置为true thread.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()设置为true thread.interrupt(); }}
执行流程如下:
- 执行main()方法,标识"main"主线程启动,代码自上而下执行
- "Thread_Interrupted"线程启动,while循环开启,isInterrupted()默认false,…当前处在死循环中;
- 与此同时,"main"主线程sleep 1s结束后,将 isInterrupted()设置为true;
- 此时,"Thread_Interrupted"线程中,由于 isInterrupted()当前被设置为true,执行 if块代码,before -> true;
- 通过 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()设置为true thread.interrupt(); }}
执行流程如下:
- 执行main()方法,标识"main"主线程启动,代码自上而下执行
- "Thread_InterruptedException"线程启动,while循环开启,isInterrupted()默认false,…当前处在死循环中;
- 与此同时,"main"主线程sleep 1s结束后,将 isInterrupted()设置为true;
- 此时,"Thread_InterruptedException"线程中,由于 isInterrupted()当前被设置为true,执行 if块代码,before -> true;
- 接着执行sleep(),抛出InterruptedException异常进行复位,after -> false,最终执行 break,结束循环结束线程。
执行流程如下:
多线程java_敞开心扉,一起聊聊Java多线程相关推荐
- 敞开心扉,一起聊聊Java多线程
目录 敞开心扉,一起聊聊Java多线程(结尾有福利~) 一.线程的实现方式 1.继承Thread类,重写run方法 2.实现Runnable接口,重写run方法 3.实现Callable接口重写run ...
- JavaSE基础二十:Java 多线程(线程基础知识、Java 多线程、Java 实现多线程(继承 Thread 类、实现 Runnable 接口、实现 Callable 接口))
本章目录 1.基础知识准备 2.Java 多线程概述 3.Java 实现多线程 3.1.继承 Thread 类 如何开启新线程 Thread 类常用方法 多线程中的同步 Thread 类同步方法 多线 ...
- java线程深入_深入聊聊Java多线程
一.背景 在没有学习Java多线程以前,总觉得多线程是个很神秘的东西,只有那些大神才能驾驭,新年假期没事就来学习和了解一下Java的多线程,本篇博客我们就来从头说一下多线程到底是怎么回事. 二.概述 ...
- 多线程java_由浅入深地介绍Java多线程,让你如何快速进入Java多线程的学习
多线程是Java语言很重要的一个组成部分,今日稍微整理整理,若有缺失不足错误之处,还望各位指出 读者对象 本篇适合所有Java程序员阅读,尤其适合以下读者: Java多线程开发者 Java并发开发师 ...
- Java多线程干货系列—(一)Java多线程基础
前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...
- java多线程编程同步方法_实践【Java多线程编程核心技术】系列:同步方法造成的无限等待...
本文实践来自于[Java多线程编程核心技术]一书! 同步方法容易造成死循环,如-- 类Service.java: package service; public class Service { syn ...
- java多线程遇到的问题_关于Java多线程遇到的问题.
1.最近在学习Java多线程,看到视频中要实现一个类似闹钟和小明的情景, 要求闹钟响,小明关闹钟, 三秒后闹钟再响,小明再关, 重复10次程序结束. 不知道为什么我的程序小明只能输出一次. publi ...
- java多线程方式轮询,深入理解JAVA多线程之线程间的通信方式
一,介绍 本总结我对于JAVA多线程中线程之间的通信方式的理解,主要以代码结合文字的方式来讨论线程间的通信,故摘抄了书中的一些示例代码. 二,线程间的通信方式 ①同步 这里讲的同步是指多个线程通过sy ...
- java 多线程 卖票_编写一个Java 多线程程序,完成三个售票窗口同时出售20张票(如下图所示);...
编写一个Java 多线程程序,完成三个售票窗口同时出售20张票(如下图所示); 程序分析:(1)票数要使用同一个静态值: (2)为保证不会出现卖出同一个票数,要java多线程同步锁. 设计思路: (1 ...
- java多线程同步与死锁_浅析Java多线程中的同步和死锁
Value Engineering 1基于Java的多线程 多线程是实现并发机制的一种有效手段,它允许编程语言在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间相互独立,且与进程一样拥有独立 ...
最新文章
- missing LSB tags and overrides
- 批量生成销售订单、退货订单
- Django Cookie于Session
- [Leetcode][第120题][JAVA][三角形最小路径和][动态规划][递归]
- C++ TBB concurrent_unordered_map find() at() return static_cast<size_t>( t ) * internal::hash_multip
- step14. ubuntu18.04下载安装scala(转)
- XPS Viewer 无法设置权限账户 - 无法激活此计算机上的任何权限管理账户
- Flink DataStream的Operator State、Keyed State、checkpoint、Savepoint、State Backends的使用和讲解
- linux中dns服务故障,Linux DNS服务器故障解决
- bootstrap栅格化框架
- Unreal Engine 4 渲染目标(Render Target)教程 之 实现雪地足迹(下)
- Motion Planning中的问题与挑战
- java批量添加文件到ZIP压缩包并下载,文件名相同导致的异常
- 低调,中国的FPGA到底有多强?!
- 单点登录 Ucenter 分析
- python 多线程采集amac
- MATLAB 数据类型中的结构体类型,及其构造方法
- 炉石android更新日志,炉石传说新版本一览_炉石传说更新内容
- 2.2 齐次方程组基础解系
- Process的使用
热门文章
- Model to Text工具Acceleo使用教程(六)——模板服务
- 路由器故障排除的思路与理论
- 3.Linux/Unix 系统编程手册(上) -- 系统编程概念
- 1.卷1(套接字联网API)---简介
- linux 按时间查找文件,linux 文件三种时间 和 find 按时间查找
- java中float double利用BigDecimal运算
- Django2.1.1与xadmin0.6.0遇到的坑
- 剑指Offer--二维数组中的查找
- Python-运算符和其优先级
- N32903系列的基础知识(1)