并发编程二Thread类源码分析

概述

在说线程之前先说下进程,进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。

线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。

创建方式

线程的创建有三种方式:继承Thread,实现Runnable接口,利用Callable跟Future

继承Thread

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。

1
2
3
4
5
6
7
8
public class FirstMethod extends Thread {
@Override
public void run() {
super.run();
}
}
FirstMethod firstMethod = new FirstMethod();
firstMethod.start();

实现Runnable接口

  • (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  • (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  • (3)调用线程对象的start()方法来启动该线程。
    1
    2
    3
    4
    5
    6
    7
    8
    public class SecondMethod implements Runnable{
    @Override
    public 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()方法来获得子线程执行结束后的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ThirdMethod implements Callable<String> {
@Override
public 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来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BugThread extends Thread {
private volatile int bugNumber = 5;

@Override
public 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();

}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BugRunnable implements Runnable {
private volatile int bugNumber = 15;

@Override
public 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();

}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

源码分析

成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
private volatile char  name[];//线程名称的字节数组
private int priority;//线程优先级
private boolean single_step; //线程是否单步
private boolean daemon = false; //是否是守护线程
private boolean stillborn = false; //JVM state
private Runnable target; //从构造方法传过来的Runnable
private 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; //最高优先级

线程状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 thread
TERMINATED;
}

线程的状态有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格
| 线程状态 | 解释 |
| :———-: | :——————————: |
| New | 还未调用 start() 方法 |
| RUNNABLE | 调用了 start() ,此时线程已经准备好被执行,处于就绪队列 |
| BLOCKED | 线程阻塞于锁或者调用了 sleep |
| WAITING | 线程由于某种原因等待其他线程 |
| TIME_WAITING | 与 WAITING 的区别是可以在特定时间后自动返回 |
| TERMINATED | 执行完毕或者被其他线程杀死 |

构造方法

thread_constructor
Thread有很多构造方法,但是通过观察最终调用了如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 /**
* 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();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;//初始化target
setPriority(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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 then
it will be passed up the call stack */
}
}
}
1
2
3
4
5
6
@Override
public void run() {
if (target != null) {
target.run();
}
}

synchronized 关键字说明start方法是同步的,并且是启动这个线程进行执行,JVM将会调用这个线程的run方法,这样产生的结果是,两个线程执行着,其中一个是调用start()方法的线程执行,另一个线程是执行run方法的线程。

sleep()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()方法

1
public static native void yield();
  • yield是一个静态的原生(native)方法
  • yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
  • yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
    它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

join()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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完成后再往下执行。

yieldjoin

  • join方法用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再执行。
  • yield可以直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行。

interrupt()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
    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()

1
public final void wait()  throws InterruptedException,IllegalMonitorStateException

  • 休眠当前线程,释放锁,直到接到通知或被中断为止
  • 在调用wait()之前,线程必须要获得该对象的对象级别锁

2、notify()

1
public final native void notify() throws IllegalMonitorStateException
  • 通知那些调用了wait()方法的线程。
  • 每次只能通知单个线程,单个线程等待,则通知当前线程,如果有多个,则随机抽取一个来进行通知
  • 必须等到当前线程释放锁后,wait所在的线程也才可以获取该对象锁,但不惊动其他同样在等待被该对象notify的线程们。
  • wait()等待的是被notify或notifyAll,而不是锁。

3、notifyAll()

1
public final native void notifyAll() throws IllegalMonitorStateException
  • 使所有原来在该对象上wait的线程统统退出wait的状态
  • 所有被通知的线程都会去竞争对象锁。
  • 获得锁的线程,会继续往下执行,释放锁后,wait中的线程继续竞争对象锁

wait()和sleep()的区别

  • sleep()方法是线程类Thread的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用sleep 不会释放对象锁。
  • wait()是Object 类的方法,对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。

总结

通过对线程源码的简单分析,可以看出线程也是有自己的生命周期的,但是由于源码中有很多native方法,导致了很难追踪源码,所以只能大致理解一下线程的各种状态以及通信过程,下面可以通过一副流程图来总结一下:
Thread_life

参考资料

Java编程思想
https://wangchangchung.github.io
http://www.jianshu.com/p/5b9fdae43335