系列文章目录
线程安全(一)java对象头分析以及锁状态
线程安全(二)java中的CAS机制
线程安全(三)实现方法sychronized与ReentrantLock(阻塞同步)
线程安全(四)Java内存模型与volatile关键字
线程安全(五)线程状态和线程创建
线程安全(六)线程池
线程安全(七)ThreadLocal和java的四种引用
线程安全(八)Semaphore
线程安全(九)CyclicBarrier
线程安全(十)AQS(AbstractQueuedSynchronizer)
0.前言
本文写了线程的状态与创建方式,以及如何在java和jvm里如何存储。
1.线程状态和Thread状态
1.1线程的五个状态
- 新建(NEW):新创建了一个线程对象。
- 可运行(RUNNABLE):当线程对象调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
- 运行(RUNNING):可运行状态(runnable)的线程获得了cpu,开始执行run方法的线程执行体。
- 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 - 死亡(DEAD):线程run()、或call() 方法执行结束,或者因异常退出了run()方法,或者直接调用了stop()的方法则该线程结束生命周期。死亡的线程不可再次复生。
1.2Thread类中定义了六种状态
不得不说注释很有用。下边就是注释的翻译
// 未运行
NEW,
// 可运行在虚拟机里的状态,不过有可能在等待资源(例如cpu)
RUNNABLE,
// 等待监视器(monitor)的锁。当因为获取不到锁而无法进入同步块时,线程处于 BLOCKED 状态。
一是第一次进入同步代码块时,因为没获取到锁,所以处以阻塞状态
二是本身在同步代码块中,当时调用了wait()方法,又被其它线程执行了唤醒操作,但是还是没有获取到锁(有可能其他线程抢先唤醒了),这时还是处于阻塞状态。
BLOCKED,
// 等待其他线程唤醒 Object.wait with no timeout、Thread.join with no timeout、LockSupport.park
WAITING,
// 具有时间的等待
Thread.sleep、Thread.wait with timeout, Thread.join with timeout、LockSupport.parkNanos、LockSupport.parkUntil
TIMED_WAITING,
// 线程终止
TERMINATED;
附:图来自网络,侵删。
1.3.JVM中的线程存放
当对象的锁级别升级为“重量级锁”时,JVM就开始采用Object Monitor机制控制各线程抢占对象的过程了。
内置锁(ObjectMonitor)
通常所说的对象的内置锁,是对象头Mark Word中的重量级锁指针指向的monitor对象。
//结构体如下
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; //线程的重入次数
_object = NULL;
_owner = NULL; //标识拥有该monitor的线程
_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁进入时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
线程大体会处于三个状态
Entry Set:没有操作权限,会待进入监控区部分(Entry Set),停留在这个区域的线程由于还没有获得对象操作权限的原因,停留在synchronized同步块以外,其线程状态被标识为BLOCKED。
Owner:持有对象操作权(only one),当前持有对象操作权限的线程互斥量将被记录在这个对象的对象头中。
Wait Set(待授权区):某个线程通过wait等相关方法释放了对象的操作权限,会被放置到待授权区域(Wait Set只有通过notify()或者相似方法被通知转移的线程能够参与线程抢占。
我看了下,也有说是同步队列,等待队列的概念:
- 线程如果占有锁,执行完同步方法或者代码块,同步队列的线程会开始抢占
- 线程如果占有锁,并执行wait,则当前线程释放对象的锁,进入等待队列,同步队列的线程会开始抢占
- 线程如果占有锁,并执行notify,不知道会唤醒谁,被唤醒的那个线程进入同步队列
- 线程如果占有锁,并执行notifyAll,所有等待线程被唤醒,被唤醒的线程进入同步队列,当前线程同步方法或代码块执行完成(synchronized结束),释放对象的锁
队列维度,等待对列要被唤醒才能进入同步队列,同步队列等待占有对象锁的线程释放时才开始争夺对象锁。
即,线程从wait状态->blocked状态->runnable状态
总结,notify或者notifyAll方法并不能保证线程一定能立马运行,只是让线程从等待对列到同步对列,谁能立马执行要从同步对列里选择。
1.4.JVM中的线程的启动和停止
线程的启动
Thread.start()
->start0()
->JVM_StartThread
->newJavaThread(&thread_entry,sz)
->os::create_thread
->os::start_thread(thread)
->JavaThread::run()回调run方法
线程的终止
线程的终止分为主动和被动。异常结束,或者执行完成是被动终止,主动是stop(),方法已经过期,不会保证线程资源正常释放
正确释放需要在重写run方法时,关注Thread.isInterrrupted的值
public class Test10 implements Runnable {
@Override
public void run() {
long i = 0L;
while (!Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("result:" + i);
for (int j = 0; j < 10000; j++) {
i++;
}
System.out.println("result:" + i);
}
public static void main(String[] args) throws InterruptedException {
Test10 test10 = new Test10();
Thread thread = new Thread(test10);
thread.start();
Thread.sleep(1000L);
thread.interrupt();
Thread.sleep(1000L);
System.out.println("result:gg");
}
}
线程终止,但是会让创建完的线程释放资源,并且,如果有sleep等方法,会更改线程终止的状态。
线程终止,实际上设置线程终止状态为true,并且sleep等方法会让线程终止状态变为false,再抛出InterruptedException 异常。抛出异常不代表线程终止
interrupt方法
void os::interrupt(Thread* thread) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
//本地线程对象
OSThread* osthread = thread->osthread();
if (!osthread->interrupted()) {//本地线程是否为中断
osthread->set_interrupted(true);//设置中断状态
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
//这里是内存屏障,目的是使得interrupted状态对其他线程立即可见
OrderAccess::fence();
//_SleepEvent相当于Thread.sleep,表示如果线程调用了sleep方法,则通过unpark唤醒
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
//_ParkEvent用于synchronized同步块和Object.wait(),这里相当于也是通过unpark进行唤醒
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
sleep方法
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
JVMWrapper("JVM_Sleep");
if (millis < 0) {
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
//判断并清除线程中断状态,如果中断状态为true,抛出中断异常
if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
// Save current thread state and restore it at the end of this block.
// And set new thread state to SLEEPING.
JavaThreadSleepState jtss(thread);
2.线程四种创建方式
实现线程的四种方式
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 实现Callable接口创建线程(有返回值)
- 使用线程池例如用Executor框架
2.1.具体实现
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.继承Thread类
new Thread(new ThreadTest(), "继承Thread类的线程").start();
// 2.实现Runnable接口
new Thread(new RunnableTest(), "实现Runnable接口的线程").start();
// 3.Callable、Future实现方式
FutureTask futureTask = new FutureTask<>(new CallableTest("a"));
new Thread(futureTask, "Callable、Future实现方式").start();
String result = (String) futureTask.get();
System.out.println("resultA:" + result);
// 4.Callable、Future实现方式2(利用线程池)
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//创建两个有返回值的任务
Callable c1 = new CallableTest("b1");
Callable c2 = new CallableTest("b2");
//执行任务并获取Future对象
Future f1 = pool.submit(c1);
Future f2 = pool.submit(c2);
//从Future对象上获取任务的返回值,并输出到控制台
System.out.println("resultB1:" + f1.get().toString());
System.out.println("resultB2:" + f2.get().toString());
//关闭线程池
pool.shutdown();
}
public static class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static class CallableTest implements Callable {
private String name;
public CallableTest(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "--" + name);
return Thread.currentThread().getName();
}
}
2.2.区别与联系
Thread部分源码
public class Thread implements Runnable {
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
构造方法如下
由源码可知
1.Thread也是实现Runnable接口。
2.为什么要继承Thread类,因为Thread里的run方法,只是调用了实例对象target的run方法,如果直接的newThread().run()去启动一个线程,没有意义。
3.除了无参构造和一个命名构造方法会调用init方法之外,其他的构造方法都会传输Runnable的实例,继而会调用target.run();
Runnable部分
实现了Runbale接口的实体类想要执行,也要实例化一个Thread类,继而会调用target.run();
Callable部分
Callable和Future都是接口,Callable定义了需要返回值,Futrue定义了如何获取线程返回值以及线程执行状态。
以FutureTask为例,分析部分源码
public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable;
private Object outcome;
private volatile Thread runner;
// run方法实现
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
// 把执行结果调用set方法
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 把结果赋值给outcome
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
// 获取值
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
// 调用report
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
// 返回outcome
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
返回结果的方法是阻塞的,会一直等待,所以可以先判断线程是否执行完成再取值。
ThreadPool
为了减少频繁创建线程和销毁线程带来的性能开销
3.线程方法
3.1.start()和run()方法区别
start() 方法最后调用的是native start0()方法,启动了线程,线程进入就绪状态
run() 方法为线程体,包含了要执行的内容。线程进入运行状态,run方法结束,此线程终止。就是普通方法调用,
3.2.sleep()和wait()和yield()方法区别
sleep() 方法属于Thread类,wait() 方法属于Object类。
sleep() 方法,线程不会释放锁。让程序暂时执行指定时间,让出cpu,监控状态依然保持,时间到了,自动恢复运行状态。用interrupt()来唤醒,
wait() 方法,线程会放弃对象锁。进入等待此对象的等待锁定池,只有对象调用notify()方法后,线程才进入对象锁定池准备获取对象锁进入运行状态。需要notify或者notifyAll来通知。
yield() 方法,不会释放锁。交出CPU的执行权。不需要指定具体时间,让CPU决定什么时候能再次被执行
3.2.join()方法
让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。
当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
即主线程会等待别join线程执行完。
public void joinDemo(){
//....
Thread t=new Thread(payService);
t.start();
//....
//其他业务逻辑处理,不需要确定t线程是否执行完
insertData();
//后续的处理,需要依赖t线程的执行结果,可以在这里调用join方法等待t线程执行结束
t.join();
}
3.3.interrupt()和方法
唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去notify
notify/notifyAll():是java.lang.Object类的方法,唤醒的是该实例的等待队列中的线程,而不能直接指定某个具体的线程。notify/notifyAll唤醒的线程会继续执行wait的下一条语句,另外执行notify/notifyAll时线程必须要获取实例的锁
interrupte():是java.lang.Thread类的方法,可以直接指定线程并唤醒,当被interrupt的线程处于sleep或者wait中时会抛出InterruptedException异常。执行interrupt()并不需要获取取消线程的锁。
总结:notify/notifyAll和interrupt的区别在于是否能直接让某个指定的线程唤醒、执行唤醒是否需要锁、方法属于的类不同
isInterrupted() :检查中断状态
interrupted():检查中断状态并清除当前线程的中断状态