文章目录
线程和进程的区别
进程就是计算机中正在执行的程序,每个进程都有自己独立的一块内存空间和一组资源,比如Windows操作系统可同时运行多个程序,这里每个运行的程序都是一个进程,这些程序使用的内存空间和系统资源都是独立的,互不干扰。
线程可以被看成是进程的一部分,也就是把进程完成的任务分成一个个更小的子任务,每一个子任务就是一个线程,他们共享进程的内存空间和资源。
Thread
Thread类是Java种对于线程的实现方式,线程分为如下几种状态:
根据线程的状态分析其源码的实现原理
创建线程
Thread类的构造函数如下:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
从源代码中可以看出,所有的构造函数最终都调用了init方法
/**
* Initializes a Thread.
*
* @param g 线程组
* @param target 线程真正要执行的任务
* @param name 线程的名称
* @param stackSize
* @param acc
* @param inheritThreadLocals
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
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;
setPriority(priority);
if (inheritThreadLocals && 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();
}
这段源代码中出现了几个新得知识点:ThreadGroup(线程组)、ThreadLocal(线程本地变量)、Runnable。Runnable实际上就是真正的任务,同时还有Callable,将在后面进行介绍。
ThreadGroup
线程组和线程池一样,都是用来管理一堆线程,但是线程组主要是方便对一组线程的属性进行管理,例如设置线程组中的所有线程为守护线程,线程组中线程的最大优先级,统一处理线程组中线程抛出的异常等,而线程池主要是管理线程的生命周期。
创建线程组的时候,可以指定其父线程,默认在创建该线程组的线程所在的线程组,创建线程的时候,可以指定线程所在的线程组,默认是创建该线程所在的线程组
例如在main方法中创建一个线程appThread和一个线程组spiderGroup都没有指定线程组,则它们的关系如下:
ThreadLocal
ThreadLocal的类图如下
线程局部变量,为每一个使用变量的线程都提供一个变量值的副本,每个线程都可以独立地改变自己的副本,而不会和其他线程冲突。
ThreadLocal类有一个protected方法initialValue()和三个public方法get(),set(T),remove(),其他的私有方法暂不讨论,主要看一下3个公有方法的实现原理:
set(T)
public void set(T value) {
Thread t = Thread.currentThread();
# 每个线程有自己维护的一个ThreadLocalMap
ThreadLocalMap map = getMap(t);
# 如果存在就把当前值放入线程的Map中,key为当前ThreadLocal类的实例
if (map != null)
map.set(this, value);
else
# 如果不存在就创建这个Map,然后将ThreadLocal实例作为key
createMap(t, value);
}
T get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
# 根据当前的ThreadLocal实例获得存入的value值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
# 如果不存在就返回一个初始值
return setInitialValue();
}
remove()
public void remove() {
# 获得当前线程的Map
ThreadLocalMap m = getMap(Thread.currentThread());
# 根据当前ThreadLocal实例移除Map中的值
if (m != null)
m.remove(this);
}
ThreadLocal(以空间换时间)和线程同步机制(以时间换空间)都是为了解决多线程中相同变量的访问冲突问题。
ThreadLocalMap
ThreadLocalMap实现了散列表,用来保存不同线程的信息,与HashMap解决哈希冲突的方式不一样。HashMap采用的拉链法,即当地址发生冲突时采用链表的方式,java8之后,当链表长度超过8之后转换为红黑树;ThreadLocalMap采用的开放地址法,即当地址冲突时不采用链表的方式,然后在数组中向后寻找空闲地址
InheritableThreadLocal
从上面已经知道ThreadLocal主要是依据Thread.currentThread()拿到线程的ThreadLocalMap变量,进而得到之前set的值。但是在某些情况下,需要在线程中获得其他线程set的值,就可以使用InheritableThreadLocal,从名字看出来这个就是用来继承的。
Thread类中存在两个变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
对于一个线程而言,threadLocals是给自己用的,而inheritableThreadLocals是给其子线程使用的。
查看new Thread()的源码:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
......
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
......
}
解释:线程A中新建一个子线程B,这时会判断线程A的inheritableThreadLocals的变量是否为null,如果不为空,就将线程A的inheritableThreadLocals变量赋给线程B的inheritableThreadLocals。
再看InheritableThreadLocal类的源码:
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
其实和ThreadLocal中的方法差不多,起主要作用的是重写的getMap(Thread)方法,获得的是inheritableThreadLocals。
线程的生命周期
根据线程的状态转换图,主要看下面几个方法:start()、yield()、sleep()、join()、interrupt()、stop(),另外一些由于设计缺陷已经标记删除的方法不做分析。
start
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
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();
解释:当线程状态为0,即为NEW时才能调用start()方法,star()t方法主要是将该线程加入线程组,然后采用Native方法去真正启动一个线程,给线程分配资源,当线程得到CPU执行权的时候就开始执行任务,实际上就是调用实现了Runnable接口的任务的run()方法。最后当线程失败的时候,使用所有线程组的异常处理方式统一处理。
启动线程的方式:
- 因为Thread本身就实现了Runnable接口,因此可以直接继承Thread类,然后调用start()方法启动一个线程
- 实现Rannable接口,然后通过Thread()构造函数将其封装为线程,然后调用start()方法启动一个线程
- 实现Callable接口,然后通过ExecutorService的submit方法去启动一个线程
yield
当线程获得CPU执行权时状态变为运行,此时调用yield()方法可以释放CPU的执行权,回到就绪状态。
public static native void yield();
sleep
当线程在运行过程中调用sleep方法线程从运行状态变为阻塞状态,Object类中wait()方法也可以将线程从运行状态变为阻塞状态,不同的地方是wait()方法会释放同步锁,而sleep()方法不会释放同步锁
public static native void sleep(long millis) throws InterruptedException;
join
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()方法其实就是调用了wait()方法去阻塞了调用该方法的线程,例如在线程A中调用了线程B的join方法,b对象成为了锁,线程A被阻塞,通过对象b的isAlive()判断线程B是否还在运行,知道线程B执行完毕,线程A才能继续执行
当sleep()方法的时间到,或者调用Object类的notify()或者notifyAll()方法,线程状态会从阻塞再次变为就绪
interrupt
interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
解释:interrupt0()方法仅仅是给线程设置一个中断标志,interrupted()和isInterrupted()方法都是用来判断线程的中断状态的,不同的是interrupted()会清空中断标志,将其恢复成false,但是isInterrupted()不会
如何中断线程
如果一个线程处于了阻塞状态(如线程调用了sleep、join、wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
注意,synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
Runnable
Thread类实现Runnable接口,这个接口中只有一个run()方法,调用了线程start()方法后,当线程获得CPU执行权之后就会执行具体任务的run方法。
run()和start()的区别:
- run()只是一个普通的方法,并不会启动一个线程,具体的任务还是在当前线程中执行
- start()方法才是真正的启动一个线程
Callable
Callable接口中只存在一个call方法,与run方法不一样的是call方法会抛出异常,具有返回结果,它是JUC包中的一个接口,主要是采用Future模式,具体的详情在下一个章节介绍。