一. 线程简介
1. 什么是线程
现代操作系统在运行一个程序时,会为其创建一个进程;
一个进程里可以创建多个线程,线程是现代操作系统调度的最小单元。
线程拥有各自的计数器、栈和局部变量等属性,能够访问共享的内存变量。
2. 线程的状态
Java线程的生命周期有6种可能的状态:
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,操作系统中就绪和运行两种状态的统称 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,进入该状态表示当前线程需要等待其他线程的特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,可以在指定的时间自行返回 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |

3. Daemon线程
Daemon线程是一种支持型线程,主要用于程序中后台调度以及支持性工作。
当Java虚拟机中除了Daemon线程没有其他线程的时候,Java虚拟机将退出。
当Java虚拟机退出时,Daemon线程中的finally块并不一定会执行,因此不能依靠finally块来确保关闭清理资源。
在线程启动前,可以利用thread.setDaemon(true)
将线程设置为Daemon线程。
二. 启动和终止线程
1. 构造线程
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
*/
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;
// 当前线程就是该线程的父线程
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;
// 将daemon、priority属性设置为父线程对应属性
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 (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();
}
新构造的线程对象的父线程就是当前线程。
2. 启动线程
线程对象初始化完成后,调用start()方法就可以启动这个线程。
线程start()方法的含义是:
当前线程(parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。
3. 中断线程
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。
线程通过方法isInterrupted()
判断是否被中断。
4. 安全的终止线程
- 利用interrupt()去中断线程A;
- 在线程A内利用isInterrupted()判断是否被中断,如果被中断了则清理资源,终止线程。
如果武断的终止一个线程(例如过期的stop()方法),可能会导致线程资源无法正常释放。
三. 线程间通信
1. volatile和synchronized关键字
-
volatile关键字用于修饰字段(成员变量)时,表示任何对该变量的访问均需从主内存中获取,而对它的修改必须同步刷新回主内存,保证了所有线程对该变量的内存可见性。
-
synchronized关键字修饰方法或以同步块的形式使用时,确保了多个线程在同一时刻,只能有一个线程处于方法或同步块中,且同步方法或同步块开始时必须从主内存中读取相应的共享变量,同步方法或同步块结束时必须同步对应的共享变量回主内存,保证了多个线程对该变量的内存可见性和排他性。

任意一个对象都拥有自己的监视器(Monitor)。
- 当这个对象的同步方法或包含该对象的同步块被调用时,执行线程需要先获取该对象的监视器;
- 如果成功获取了该对象的监视器,即给对象加锁成功,可以继续执行;
- 如果获取该对象的监视器失败,则执行线程进入同步队列,线程状态变为BLOCKED;
- 当加锁成功的线程释放了锁,则唤醒同步队列中被阻塞的线程,使其重新尝试获取该对象的监视器。
2. 等待/通知机制
任意一个对象都具备等待/通知的相关方法。
方法名称 | 描述 |
---|---|
notify() | 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该等待线程获取到了对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程 |
wait() | 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,调用wait()方法后,会释放对象的锁 |
wait(long) | 超时等待一段时间(毫秒),如果没有通知就超时返回 |
wait(long,int) | 对于超时时间更细粒度的控制,可以达到纳秒 |

- Thread A成功给对象加锁,执行相应的代码;
- 当Thread A执行到
Object.wait()
,则释放对象锁,进入等待队列,Thread A状态变为WAITING; - Thread B成功给对象加锁,执行相应的代码;
- 当Thread B执行到
Object.notify()/notifyAll()
,发送通知给等待队列,等待队列中Thread A出等待队列,进入同步队列,Thread A状态变为BLOCKED; - Thread B继续执行后续代码,执行完成后释放对象锁;
- 收到锁释放的通知,同步队列中Thread A尝试获取对象锁,如果获取成功,则从wait()方法处返回,继续执行后续代码,执行完成后释放对象锁。
3. Thread.join()
如果线程A执行thread.join()
语句,表示:
当前线程A将一直等待,直到thread线程终止之后,才会从thread.join()处返回。
对应的超时方法为:join(long millis)
4. ThreadLocal
ThreadLocal,即为线程变量,可以理解为类似于String、Integer这样变量类型。
利用set(T)方法设置一个值;在当前线程下利用get()方法获取到之前设置的值。
public static void main(String[] args) {
ThreadLocal<Long> threadLocal = new ThreadLocal<>();
threadLocal.set(System.currentTimeMillis());
System.out.println(threadLocal.get());
}
网友评论