02.并发编程(2)Thread类源码分析
概述
在说线程之前先说下进程,进程和线程都是一个时间段的描述,是CPU工作时间段的描述。
进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。
线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。
创建方式
线程的创建有三种方式:继承Thread,实现Runnable接口,利用Callable跟Future
继承Thread
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 (2)创建Thread子类的实例,即创建了线程对象。 (3)调用线程对象的start()方法来启动该线程。
public class FirstMethod extends Thread {@Overridepublic void run() {super.run();}}FirstMethod firstMethod = new FirstMethod();firstMethod.start();
复制代码
实现Runnable接口
- (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- (3)调用线程对象的start()方法来启动该线程。
public class SecondMethod implements Runnable{@Overridepublic void run() {}}
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();
复制代码
通过Callable跟FutureTask创建线程
1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。 (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class ThirdMethod implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName();}}ThirdMethod thirdMethod=new ThirdMethod();FutureTask<String> futureTask=new FutureTask<String>(thirdMethod);try {String threadName = futureTask.get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}
复制代码
对比分析
实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:
1、接口创建线程可以实现资源共享,比如多个线程可以共享一个Runnable资源 2、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。 3、接口创建线可以避免由于Java的单继承特性而带来的局限。
现在通过一个程序员改Bug的例子来描述一下,一共有15个bug,现在安排3个程序员去Debug:
通过Thread来实现
public class BugThread extends Thread {private volatile int bugNumber = 5;@Overridepublic void run() {for (int i = 0; i < bugNumber; i++) {System.out.println("bugNumber--->" + bugNumber--);}}
}public class Main {public static void main(String[] args) {new BugThread().start();new BugThread().start();new BugThread().start();}
}
复制代码
运行结果:
Thread-0-----5
Thread-1-----5
Thread-2-----5
Thread-0-----4
Thread-2-----4
Thread-1-----4
Thread-2-----3
Thread-0-----3
Thread-2-----2
Thread-1-----3
Thread-2-----1
Thread-0-----2
Thread-0-----1
Thread-1-----2
Thread-1-----1
复制代码
通过Runnable来实现
public class BugRunnable implements Runnable {private volatile int bugNumber = 15;@Overridepublic void run() {while (bugNumber > 0)System.out.println(Thread.currentThread().getName() + "-----" + bugNumber--);}
}public static void main(String[] args) {BugRunnable bugRunnable = new BugRunnable();new Thread(bugRunnable).start();new Thread(bugRunnable).start();new Thread(bugRunnable).start();}
复制代码
运行结果
Thread-0-----15
Thread-0-----14
Thread-0-----13
Thread-0-----12
Thread-1-----11
Thread-0-----10
Thread-1-----9
Thread-0-----8
Thread-1-----7
Thread-0-----6
Thread-1-----5
Thread-0-----4
Thread-1-----3
Thread-0-----2
Thread-1-----1
复制代码
源码分析
成员变量
private volatile char name[];//线程名称的字节数组private int priority;//线程优先级private boolean single_step; //线程是否单步private boolean daemon = false; //是否是守护线程private boolean stillborn = false; //JVM stateprivate Runnable target; //从构造方法传过来的Runnableprivate ThreadGroup group; //线程组private ClassLoader contextClassLoader; //类加载器private static int threadInitNumber; //线程编号private volatile int threadStatus = 0; //初始状态public final static int MIN_PRIORITY = 1; //最低优先级public final static int NORM_PRIORITY = 5; //默认优先级public final static int MAX_PRIORITY = 10; //最高优先级复制代码
线程状态
public enum State {//Thread state for a thread which has not yet started.NEW,//Thread state for a runnable thread. RUNNABLE,//Thread state for a thread blocked waiting for a monitor lock.BLOCKED,// Thread state for a waiting thread.WAITING,//Thread state for a waiting thread with a specified waiting time.TIMED_WAITING,//Thread state for a terminated threadTERMINATED;}
复制代码
线程的状态有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格
线程状态 | 解释 |
---|---|
New | 还未调用 start() 方法 |
RUNNABLE | 调用了 start() ,此时线程已经准备好被执行,处于就绪队列 |
BLOCKED | 线程阻塞于锁或者调用了 sleep |
WAITING | 线程由于某种原因等待其他线程 |
TIME_WAITING | 与 WAITING 的区别是可以在特定时间后自动返回 |
TERMINATED | 执行完毕或者被其他线程杀死 |
构造方法
Thread有很多构造方法,但是通过观察最终调用了如下方法:
/*** Initializes a Thread.** @param g //线程组* @param target //构造方法传过来的Runnable* @param name //线程名称* @param stackSize //给线程分配的栈的深度* @param acc //上下文加载器*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name.toCharArray();Thread parent = currentThread();SecurityManager security = System.getSecurityManager();//判断线程组参数是否为空if (g == null) {if (security != null) {g = security.getThreadGroup();}if (g == null) {g = parent.getThreadGroup();}}g.checkAccess();if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;//初始化线程组this.daemon = parent.isDaemon();//定义是否为守护线程this.priority = parent.getPriority();//设置优先级if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;//初始化targetsetPriority(priority);//设置优先级if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;//设置栈深度/* Set thread ID */tid = nextThreadID();//设置线程ID}
复制代码
start方法
public synchronized void start() {if (threadStatus != 0)//判断线程是否准备好group.add(this);//将启动的线程线程组boolean started = false;try {start0();//本地方法,JVM调用target的run方法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 */}}}复制代码
@Overridepublic void run() {if (target != null) {target.run();}}
复制代码
synchronized 关键字说明start方法是同步的,并且是启动这个线程进行执行,JVM将会调用这个线程的run方法,这样产生的结果是,两个线程执行着,其中一个是调用start()方法的线程执行,另一个线程是执行run方法的线程。
sleep()方法
public static void sleep(long millis, int nanos)throws InterruptedException {if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}sleep(millis);//调用本地方法}
复制代码
线程休眠一段时间,让其他线程有机会继续执行,需要捕捉异常。
yield()方法
public static native void yield();
复制代码
- yield是一个静态的原生(native)方法
- yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
- yield不能保证使得当前正在运行的线程迅速转换到可运行的状态 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态
join()方法
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);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}
复制代码
join方法是等待该线程执行,直到超时或者终止,可以作为线程通信的一种方式,A线程调用B线程的join(阻塞),等待B完成后再往下执行。
yield跟join
- join方法用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再执行。
- yield可以直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行。
interrupt()方法
public void interrupt() {if (this != Thread.currentThread())checkAccess();//检查权限
synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0(); b.interrupt(this);return;}}interrupt0();}
复制代码
interrupt()方法是中断当前的线程, 此外还有isInterrupt,以及interrupted方法
- interrupt():将线程置为中断状态
- isInterrupt():线程是否中断
- interrupted():返回线程的上次的中断状态,并清除中断状态。 一般来说,阻塞函数:如sleep()、join()、wait()等在检查到线程的中断状态的时候,会抛出InteruptedExeption, 同时会清除线程的中断状态。
线程间通信
前面说过,Java中的线程在底层是通过共享内存进行通信的,在应用层则是通过调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。 Object是所有类的超类,主要通过:notify()、notifyAll()、wait()、wait(long)和wait(long,int)这几个方法来进行线程间通信。
1、wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
复制代码
- 休眠当前线程,释放锁,直到接到通知或被中断为止
- 在调用wait()之前,线程必须要获得该对象的对象级别锁
2、notify()
public final native void notify() throws IllegalMonitorStateException
复制代码
- 通知那些调用了wait()方法的线程。
- 每次只能通知单个线程,单个线程等待,则通知当前线程,如果有多个,则随机抽取一个来进行通知
- 必须等到当前线程释放锁后,wait所在的线程也才可以获取该对象锁,但不惊动其他同样在等待被该对象notify的线程们。
- wait()等待的是被notify或notifyAll,而不是锁。
3、notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
复制代码
- 使所有原来在该对象上wait的线程统统退出wait的状态
- 所有被通知的线程都会去竞争对象锁。
- 获得锁的线程,会继续往下执行,释放锁后,wait中的线程继续竞争对象锁
wait()和sleep()的区别
- sleep()方法是线程类Thread的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用sleep 不会释放对象锁。
- wait()是Object 类的方法,对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。
总结
通过对线程源码的简单分析,可以看出线程也是有自己的生命周期的,但是由于源码中有很多native方法,导致了很难追踪源码,所以只能大致理解一下线程的各种状态以及通信过程,下面可以通过一副流程图来总结一下:
参考资料
Java编程思想
wangchangchung.github.io
www.jianshu.com/p/5b9fdae43…
02.并发编程(2)Thread类源码分析相关推荐
- 多线程高并发编程(8) -- Fork/Join源码分析
一.概念 Fork/Join就是将一个大任务分解(fork)成许多个独立的小任务,然后多线程并行去处理这些小任务,每个小任务处理完得到结果再进行合并(join)得到最终的结果. 流程:任务继承Recu ...
- Java并发编程笔记之FutureTask源码分析
FutureTask可用于异步获取执行结果或取消执行任务的场景.通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Fu ...
- 并发编程专题五-AbstractQueuedSynchronizer源码分析
PS:外号鸽子王不是白来的,鸽了好几天,也是因为比较忙,时间太少了,这篇东西有点多,需要慢慢消化.不知不觉居然写了4个多小时.... 一.什么是AQS aqs是AbstractQueuedSynchr ...
- [并发编程] - Executor框架#ThreadPoolExecutor源码解读02
文章目录 Pre 线程池的具体实现 线程池的创建 参数解读 corePoolSize maximumPoolSize keepAliveTime unit workQueue threadFactor ...
- [并发编程] - Executor框架#ThreadPoolExecutor源码解读03
文章目录 Pre execute源码分析 addWorker()解读 Worker解读 Pre [并发编程] - Executor框架#ThreadPoolExecutor源码解读02 说了一堆结论性 ...
- Java并发编程:Thread类的使用
为什么80%的码农都做不了架构师?>>> Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学 ...
- Thread类源码剖析
目录 1.引子 2.JVM线程状态 3.Thread常用方法 4.拓展点 一.引子 说来也有些汗颜,搞了几年java,忽然发现竟然没拜读过java.lang.Thread类源码,这次特地拿出来晒一晒. ...
- [并发编程] - Executor框架#ThreadPoolExecutor源码解读01
文章目录 Pre Thread Java线程与OS线程 生命状态 状态切换 线程池 why use case Advantage Executor框架 ThreadPoolExecutor 源码分析 ...
- Java Thread类源码详解
概述 Java所有多线程的实现,均通过封装Thread类实现,所以深入Thread类,对深入理解java多线程很有必要 构造函数: Thread的构造函数,采用缺省的方式实现: //传入Runnabl ...
最新文章
- 套接字选项SO_KEEPALIVE
- LeetCode 23. Merge k Sorted Lists
- redis-查看日志
- mysql 分组求和_MySql基础语法
- [渝粤教育] 中国地质大学 大学英语(6) 复习题
- kali BEEF-XSS启动报错解决
- 服务框架及服务治理组件——业界调研
- 据说有人面试栽在了Thread类的stop()方法和interrupt()方法上
- linux下补丁制作及打补丁实例
- Python提示ModuleNotFoundError: No module named ‘PIL‘,已解决
- MongoDB导出-导入-迁移
- 用 JMeter 测试 MySQL 数据库
- 一只刚学竞价两周的菜鸟
- 57. Attribute specified 属性
- ppt如何替换其他mo ban_如何制作PPT教程:PPT排版格式技巧汇总
- 7pin数码屏的使用
- 为什么现在的程序员那么卑微?青出于蓝而胜于蓝啊
- java栅栏_Java并发工具类(栅栏CyclicBarrier)
- 从羽泉演唱会大数据看在线演出前景
- Python format 使用实例
热门文章
- 555定时器回差电压计算公式_555时基电路引脚解析
- Django之orm查询
- poj2109 Power of Cryptography(数学题)
- 敏捷开发总结(1)软件研发过程
- 台式机共享笔记本的无线网络(只需要一根网线)
- CString原理介绍
- 一个简单的LINQ TO XML, AJAX 例子[译]
- XML 命名空间以及它们如何影响 XPath 和 XSLT (Extreme XML)
- cmd使用另一个Oracle的sid,(转发备用)Oracle SID在本机上已经存在,请指定一个不同的SID”的解决办法...
- php用json交换二维数组,PHP和Javascript的JSON交互(处理一个二维数组)