线程
一个程序的运行就叫做进程,进程是系统资源的分配,是不可控的;同时一个进程可以拥有多个线程;控制线程是我们能够做的。
线程表示一条单独的流,它有自己的程序计数器,有自己的栈
创建线程方式
(1)继承Thread,也就是构造Thread类
实现线程的执行单元(3种方式)
(1)继承Thread,重写run方法
(2)实现Runnable接口,实现run方法
(3)实现Callable接口,实现run方法
线程的资源共享
简单来说:就是多个线程多同一个资源的访问
银行叫号
package threadDemo;
/**
* 银行叫号,模拟资源共享,线程安全问题
* 不考录资源共享问题
*/
public class threadDemo4 extends Thread{
private final String name;
private int index = 1;//此时不做static修饰
private final static int max = 50;
public threadDemo4(String name) {
this.name = name;
}
@Override
public void run() {
while (index <= max) {
System.out.println("柜台:"+name+"当前的号码是"+(index++));
try {
Thread.sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
threadDemo4 t = new threadDemo4("1号柜台");
t.start();
threadDemo4 t2 = new threadDemo4("2号柜台");
t2.start();
threadDemo4 t3 = new threadDemo4("3号柜台");
t3.start();
threadDemo4 t4 = new threadDemo4("4号柜台");
t4.start();
}
}
考虑共享资源唯一性问题
package threadDemo;
/**
* 银行叫号,模拟资源共享,线程安全问题
* 对共享资源用static修饰
*/
public class threadDemo5 extends Thread{
private final String name;
private static int index = 1;//此时不做static修饰
private final static int max = 50;
public threadDemo5(String name) {
this.name = name;
}
@Override
public void run() {
while (index <= max) {
System.out.println("1柜台:"+name+"当前的号码是"+(index++));
try {
Thread.sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
threadDemo5 t = new threadDemo5("1号柜台");
t.start();
threadDemo5 t2 = new threadDemo5("2号柜台");
t2.start();
threadDemo5 t3 = new threadDemo5("3号柜台");
t3.start();
threadDemo5 t4 = new threadDemo5("4号柜台");
t4.start();
}
}
package threadDemo;
public class threadDemo6 implements Runnable{
private int index = 1;
private final static int max = 50;
@Override
public void run() {
while(index <= max) {
System.out.println(Thread.currentThread()+":的号码是:"+(index++));
try {
Thread.sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final threadDemo6 t = new threadDemo6();
Thread thread = new Thread(t, "1号窗口");
Thread thread1 = new Thread(t, "2号窗口");
Thread thread2 = new Thread(t, "3号窗口");
Thread thread3 = new Thread(t, "4号窗口");
thread.start();
thread1.start();
thread2.start();
thread3.start();
}
}
**注意:**无论是static,还是Runnable接口,他们都存在缺陷,当资源数过大时,都会存在某个资源被重复使用,或者某个资源没有使用到的情况。这就是:线程安全问题
**线程有一些基本属性和方法,包括id,name,优先级(priority),状态,是否daemon线程,sleep方法,wait方法,yield方法,join方法,过时的方法等 **
常用的线程方法
1.让调用join的线程等待该线程的结束,但是在等待的过程中,这个等待可能会被中断,抛出:中断异常
2.join方法的变体:可以限定等待的最长时间,单位为毫秒,如果为 0 ,则表示无限期
出现的设计模式
Thread类的start方法和run方法
1.当start方法被调用时,这时会开辟start方法的线程,用来执行它下面的方法,比如:run方法;同时main方法也在执行
优点
缺点
首先是关于执行流(线程),内存,和程序代码之间的关系。
1)当多条执行流执行相同的程序时,每条流都有自己的栈,方法中的参数和局部变量都有自己的一份。但是当多条流可以操作相同的变量时,可能会出现一些意料之外的情况,包括竟态条件和内存可见性问题。
竞态条件
竞态条件(race condition):当多个线程访问和操作同一个对象时,最终执行结果与执行顺寻有关,可能正确可能不正确。
package threadDemo;
//竟态条件示例
public class threadDemo10 extends Thread{
private static int counter = 0;
//synchronized
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter++;
}
}
public static void main(String[] args) throws InterruptedException {
int num = 10000;
Thread[] threads = new Thread[num];
for (int i = 0; i < num; i++) {
threads[i] = new threadDemo10();
threads[i].start();
}
for (int i = 0; i < num; i++) {
threads[i].join();
}
System.out.println(counter);
}
}
//结果:期待值1000万,结果只有900多万
解决方法:
2.内存可见性
多个线程可用共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到。
示例
package threadDemo;
public class threadDemo7 {
private static boolean shutDown = false;
static class HelloThread extends Thread {
@Override
public void run() {
while (!shutDown) {
//do-nothing
System.out.println("............");
}
System.out.println("exit hello");
}
}
public static void main(String[] args) throws InterruptedException {
new HelloThread().start();
Thread.sleep(1000);
shutDown = true;
System.out.println("exit main");
}
}
内存可见性问题:
在计算机系统中,除了内存,数据,还有被缓存到cpu寄存器以及各级缓存中,当访问一个变量时,可能直接从寄存器或者cpu缓存中获取,而不一定到内存中取,当修改一个变量时,也可能先写到缓存中,稍后才会更新到内存中。在单线程的程序中,这一般不是问题,但是在多线程的程序中,尤其时在有多cpu的情况下,这就是严重的问题。一个线程对内存的修改,另一个线程看不到,一是修改没有即时同步到内存,二是另一个线程根本就没有从内存读。
解决方法:
synchronized可以用于修饰类的实例方法,静态方法和代码块。
1)synchronized修饰实例方法
示例:
synchronized修饰计数方法
package threadDemo;
public class Count {
private int count;
public synchronized void incr() {
count++;
}
public synchronized int getCount() {
return count;
}
}
package threadDemo;
public class threadDemo8 extends Thread{
Count counter;
public threadDemo8(Count counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter.incr();
}
}
public static void main(String[] args) throws InterruptedException {
int num = 10000000;
Count counter = new Count();
Thread[] threads = new Thread[num];
for (int i = 0; i < num; i++) {
threads[i] = new threadDemo8(counter);
threads[i].start();
}
for (int i = 0; i < num; i++) {
threads[i].join();
}
System.out.println(counter.getCount());
}
}
多线程可以同时执行同一个synchronized实例方法,只要他们访问的对象是不同的即可。所以synchronized实例方法保护的是当前实例对象,即this,this对象有一个锁一个等待队列,锁只能被一个线程持有,其他尝试获得同样锁的线程需要等待。
执行synchronized实例方法的过程如下:
强调:
synchronized保护的是对象而非代码,只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被同步顺序访问。
所以一般在保护变量时,需要在所有访问变量的方法上加上synchronized
2)synchronized修饰静态方法
private int count;
public synchronized synchronized void incr() {
count++;
}
public synchronized synchronized int getCount() {
return count;
}
synchronized修饰实例方法,保护的是当前实例对象this;对于静态方法,保护的是类对象。
实际上,每个对象都有一个锁和一个等待队列,类对象也不例外。
synchronized静态方法和synchronized实例方法保护的是不同的对象,不同的两个线程,可以一个执行synchronized静态方法,另一个执行synchronized实例方法。
3)synchronized修饰代码块
public class Count {
private int count;
public void incr() {
synchronized(this) {
count++;
}
}
public int getCount() {
synchronized(this) {
return count;
}
}
}
synchronized括号里修饰的就是要保护的对象,对于实例方法来说,就是this。
public class Count {
private int count;
public static void incr() {
synchronized(Count.class) {
count++;
}
}
public static int getCount() {
synchronized(Count.class) {
return count;
}
}
}
这是对于静态方法。
synchronized同步的对象可以是任意的对象,任意对象都有一个锁和等待队列,或者说,任何对象都可以作为锁对象。
public class Count {
private int count;
private Object lock = new Object();
public static void incr() {
synchronized(lock) {
count++;
}
}
public static int getCount() {
synchronized(lock) {
return count;
}
}
}
1.可重入性
可重入性:就是如果一个执行线程,在它获得了锁之后,在调用其他需要获取锁的代码时,可以直接调用。但是不是所有的锁都是可重入的。
可重入是通过记录锁的持有线程和持有数量来实现的。
当调用被synchronized保护的代码时,检查对象是否已被锁,如果是,再检查是否被当前线程锁定,如果是,增加持有数量,如果不是被当前线程锁定,才加入等待队列,当释放时,减少持有量,当数量变成0时才释放整个锁。
2.内存可见性
对于一些复杂操作,synchronized可以实现原子操作,避免出现竞态事件。
synchronized除了保证原子操作外,还有一个重要作用就是:保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读取最新数据。
但是使用synchronized来保证内存可见性成本有点高;轻量级的方式,使用volatile给变量做修饰。
private volatile int counter;
加入了volatile之后,java会在操作对应变量时插入特殊的指令,保证读写到内存最新值,而非缓存值。
3)死锁
死锁:有a,b两个线程,a持有锁A,在等待锁B;b持有锁B,在等待锁A;a和b陷入了互相等待。
package threadDemo;
//死锁
public class threadDemo9 {
private static Object lockA = new Object();
private static Object lockB = new Object();
private static void startThreadA() {
Thread threadA = new Thread() {
@Override
public void run() {
synchronized (lockA) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
}
}
}
};
threadA.start();
}
private static void startThreadB() {
Thread threadB = new Thread() {
@Override
public void run() {
synchronized (lockB) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA) {
}
}
}
};
threadB.start();
}
public static void main(String[] args) {
startThreadA();
startThreadB();
}
}
解决:
首先,应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁
显式锁接口Lock,它支持尝试获取锁(tryLock)和带时间限制的获取锁方法,使用这些方法可以在获取不到锁的时候释放已经持有的锁,然后再次尝试获取锁或者干脆放弃,以避免死锁。
同步容器:Collection中有synchronized修饰的容器。
但是也不是绝对安全,需要注意以下情况:
1.复合操作
2.伪同步
同步错对象,所有的方法必须使用相同的锁。
3.迭代
对于同步容器对象,虽然单个操作是安全的,但是迭代并不是。
示例
创建一个同步List对象,一个线程修改list,另一个遍历
package threadDemo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class threadDemo11 {
private static void startMotifyThread(final List<String> list) {
Thread motifyThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
list.add("item " + i);
try {
Thread.sleep((int) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
motifyThread.start();
}
private static void startIteratorThread(final List<String> list) {
Thread iteratorThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
for (String s : list) {
}
}
}
});
iteratorThread.start();
}
public static void main(String[] args) {
final List<String> list = Collections.synchronizedList(new ArrayList<String>());
startIteratorThread(list);
startMotifyThread(list);
}
}
抛出异常
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at threadDemo.threadDemo11$2.run(threadDemo11.java:31)
at java.lang.Thread.run(Thread.java:748)
意思是:在遍历的同时容器发送了结构性变化,就会抛出该异常。同步容器并没有解决这个问题;
解决:
需要在遍历的时候给整个容器对象加锁
private static void startIteratorThread(final List<String> list) {
Thread iteratorThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (list) {
for (String s : list) {
}
}
}
}
});
iteratorThread.start();
}
4)并发容器
线程的基本协作机制:wait/notify(Object类的方法)
wait
wait能做什么?
notify
从条件队列中选出一个线程,将其从队列中移除并唤醒
移除条件队列中所有的线程并全部唤醒
注意:wait/notify方法只能在synchronized代码块中被调用
虽然是在synchronized方法内,但调用wait方法时,线程会释放锁
wait/notify流程
注意:线程从wait调用返回后,不代表其等待的条件就一定成立了,它需要重新检查其等待的条件,一般的调用模式是:
调用notify会把在条件队列中等待的线程唤醒并从条件队列中移除,但它不会释放对象锁,也就是说,只有在包括notify的synchronized代码块执行完后,等待的线程才会从wait调用中返回。
他们被不同的线程调用,但共享相同的锁和条件等待队列(相同对象的synchronized代码块内),他们围绕一个共享的条件变量进行协作,这个条件变量是程序自己维护的,当条件不成立时,线程调用wait进入条件等待队列,另一个线程修改了条件变量后调用notify,调用wait的线程唤醒后需要重新检查条件变量。从多线程的角度看,他们围绕共享变量进行协作,从调用wait的线程角度看,它阻塞等待一个条件成立。我们在设计多线程协作时,需要想清楚协作的共享变量和条件是什么,这是协作的核心。
案例
生产者消费者模式
阻塞队列:
在实际开发中,应优先使用这些类。
场景
等待共享变量成立是,同时唤醒
使用join方法让主线程等待子线程结束,join实际上就是使用了wait方法,当线程 isAlive() = true ,join就一直等待下去;当线程结束时,java系统调用notifyAll来通知。
但是使用join有时比较麻烦,需要主线程逐一等待每个子线程
可以使用主线程与各个子线程协作的共享变量是一个数,这个数表示未完成的线程个数,初始值为子线程个数,主线程等待该值为0,而每个子线程结束后都将改值减一,当减为0时调用 notifyAll()
在主从模式中,手工创建线程往往比较麻烦,一种常见的模式是异步调用,异步调用返回一个一般称为Future的对象,通过它可以获得最终的结果。在java中,表示子任务的接口是Callable。
java中相关接口和类
表示异步结果的接口Future和实现类FutureTask
用于异步执行任务的接口Executor,以及有更多功能的子接口ExecutorService
用于创建Executor和ExecutorService的工厂方法类Executors
集合点
各个线程先是分头行动,各自到达一个集合点,在集合点需要集齐所有线程,交换数据,然后再进行下一步动作。怎么表示这种协作呢?协作的共享变量依然是一个数,这个数表示未到达集合点的线程个数,初始值为子线程个数,每个线程到达集合点后将该值减一,如果不为0,表示还有别的线程未到,进行等待,如果变为0,表示自己是最后一个到的,调用notifyAll 唤醒所有的线程。
前要
首先要知道如何在java中取消或者关闭一个线程?然后是哪些场景需要取消关闭线程,再是取消/关闭的机制,以及线程对中断的反应和如何正确的取消/关闭线程。
start启动线程,开始执行run方法,执行完run方法,线程退出。
在java中,停止一个线程的主要机制是中断,中断并不是强迫终止一个线程,他是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。
Thread中定义的关于中断的方法:
public boolean isInterrupted()
public void interrupt()
public static boolean interrupted()
isInterrupted,和 interrupt 是实例方法,调用他们需要通过线程对象;
interrupted 是静态方法,实际上会调用 Thread.currentThread() 操作当前对象。
每个线程都有一个中断标志位,表示该线程是否被中断了
interrupt() 对线程的影响与线程的状态和在进行的IO操作有关。我们主要考虑线程的状态
1.runnable
如果线程在运行过程中,且没有执行io操作, interrupt()只是会设置线程的中断标志位,没有任何其他作用。线程应该在运行过程中合适的位置检查中断标志位,比如,如果主体代码是一个循环,可以在循环处进行检查。
while(!Thread.currentThread().isInterrupted()) {
}
2.waiting/timed_waiting
线程调用 join/wait/sleep方法会进入waiting/timed_waiting状态,在这些状态时,对线程对象调用 interrupt() 会使得线程抛出InterruptedException。抛出异常后,中断标志位会被清空,而不是设置
捕获到InterruptedException异常,通常表示希望结束该线程,线程大致有两种处理方式:
1)向上传递该异常,这使得该方法也变成了一个可中断的方法,需要调用者进行处理。
2)有些情况,不能向上传递异常,比如Thread的run方法,它的声明是固定的,不能抛出任何受检异常,这时,应该捕获异常,进行核市的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,使得其他代码有办法知道它发生了中断。
3,blocked
如果线程在等待锁,对线程对象调用 interrupt()只是会设置线程的中断标志位,线程依然会处于blocked状态,也就是说:interrupt()并不能使一个在等待锁的线程真正”中断“。
在使用synchronized 关键字获取锁的过程中不响应中断请求,这时synchronized 的局限性。如果这对程序是一个问题,应该使用显式锁。
new/terminate
如果线程尚未启动(new),或者已经结束(terminate)。则调用interrupt()对它没有任何效果,中断标志位也不会被设置。
interrupt方法不一定会正真”中断“线程,它只是一种协作机制,如果不明白线程在做什么,不应该贸然地调用线程的interrupt方法。以为这样就能取消线程。
对于线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给掉哟个者,外部调用者应该调用这些方法,而不是直接调用interrupt。java并发库的一些代码就提供了单独的取消/关闭方法,比如,Future接口提供了如下方法以取消任务:
boolean cancel(boolean mayInterruptIfRunning);
ExecutorService
void shutdown();
List<Runnable> shutdownNow();
首先:线程的取消/关闭,主要依赖中断,但它只是一种协作机制,不会强制终止线程。作为线程的实现者应该提供明确的取消/关闭方法;作为线程的调用者,应该使用其取消/关闭方法,而不是贸然调用 interrupt
原子变量:就是和原子性的变量,保证在对变量操作时的安全性,且成本较低。
常用的原子变量
之所以称为原子变量,是因为它包含一些以原子方式实现组合操作的方法。
这些方法都依赖一个public方法:
public final boolean compareAndSet(int expect,int update);
compareAndSet是一个非常重要的方法,比较并设置,我们称之为CAS。有两个参数 expect,update;如果当前值等于 expect ,则更新为 update,否则就不跟新,如果更新成功,就返回 true,否则返回 false。
AtomicInteger可以在程序中做一个计数器,多个线程并发更新,也总能实现正确性。
2.基本原理和思维
AtomicInteger的主要内部成员是:
private volatile int value;
它的声明中使用了volatile,则会使必须的,以保证内存可见性。
incrementAndGet方法:
public final int incrementAntGet() {
for(;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current,next))
return next;
}
}
代码主体是个死循环,先获取当前值,计算期望值next,然后调用CAS方法进行更新,如果没有更新成功,说明value被别的线程改了,则再去取最新值并尝试更新直到成功为止。
与synchronized锁相比,这种原子更新方式代表一种不同的思维方式。synchronized是悲观的。它假定更新很可能冲突,所以先获取锁,得到锁才更新。原子变量的更新逻辑是乐观的,它假定冲突比较少,但使用CAS更新,也就是进行冲突检测,如果确实冲突了,那也没关系,继续尝试好了。synchronized代表一种阻塞式算法,得不到锁的时候,进入锁等待队列,等待其他线程唤醒,有上下文切换开销。对于大部分比较简单的操作,无论是在低并发还是高并发情况下,这种乐观非阻塞方式的性能都远高于悲观阻塞式方式。
原子变量相对比较简单,但对于复杂一些的数据结构和算法,非阻塞方式往往难以实现和理解。然而java并发包提供了一些非阻塞容器。
compareAndSet的实现:
public final boolean compareAndSet(int expect,int update) {
return unsafe.compareAndSwapInt(this,valueOffset,expect,update);
}
unsafe:
private static final Unsafe unsafe = Unsafe.getUnsafe();
它是Sun的私有实现,从名字上看,表示的也是”不安全“,一般应用程序不应该直接使用。原理上,一般的计算机系统都是在硬件层次上直接支持CAS命令。而java的实现都会利用这些特殊指令。从程序的角度看,可以将compareAndSet视为计算机的基础操作,直接接纳就好。
使用CAS方式跟新有一个ABA问题:假设当前值为A,如果另一个线程将A修改成B,再修改回A,当前线程的CAS操作无法分辨当前值发生过变化。
ABA是不是一个问题与线程的逻辑有关,一般不是问题。而如果确实有问题,解决方法是使用AtomicStampedReference,在修改值的同时附加一个时间戳,只有值和时间都相同才进行修改,其CAS方法声明为:
public boolean compareAndSet(
V expectedReference,V newReference,int expectedStamp,int newStamp)
对于原子变量,以及其原理CAS,在并发环境中的计数,产生序列号等需求应该使用原子变量而非锁。CAS是java并发包的基础,基于它可以实现高效的,乐观,非阻塞式数据结构和算法,它也是并发包中锁,同步工具和各种容器的基础。
java并发包中的显式锁可以解决synchronized的限制。
java并发包中的显式锁接口和类位于包java.util.concurrent.locks下,主要接口和类有:
显式锁接口Lock的定义:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(Long time,Timeout unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
解释:
1)lock()/unlock():就是普通的获取锁和释放锁的方法,lock()会阻塞直到成功。
2)lockInterruptibly():与lock()不同的是,它可以响应中断,如果被其他线程中断了,则抛出InterruptedException。
3)tryLock():只是尝试获取锁,立即返回,不阻塞,如果获取成功,返回true,否则返回false。
4)tryLock(Long time,Timeout unit):先尝试获取锁,如果能够成功则立即返回true,否则阻塞等待,但等待的最长时间由指定的参数设置,在等待的同时响应中断,如果发生了中断,抛出InterruptedException,如果等待的时间内获取了锁,返回true,否则返回false。
5)newCondition:新建一个条件,一个Lock可以关联多个条件,关于多个条件.
Lock接口的主要实现类是ReentrantLock
ReentrantLock的两个构造方法:
public ReentrantLock()
public ReentrantLock(boolean fair)
参数 fair 表示是否保证公平,不指定的情况下,默认false,表示不保证公平.所谓公平是指,等待时间最长的线程优先获得锁.保证公平会影响性能,一般也不需要,所以默认不保证,synchronized锁也是不保证公平的.
使用显式锁,一定要记得调用unlock.一般而言,应该将lock之后的代码包装到try语句内,在finally语句内释放锁.
public void incr() {
lock.lock();
try {
count ++;
} finally {
lock.unlock();
}
}
使用tryLock(),可以避免死锁.
在持有一个锁获取另一个锁而获取不到的时候,可以是释放已持有的锁,给其他线程获取锁的机会,然后重试获取所有锁。
在最底层它依赖CAS方法,另外,它依赖与类LockSupport中的一些方法
基本方法:
public static void park();
public static void parkNanos(Long nanos);
public static void parkUntil(Long deadline);
public static void unpark(Thread thread);
park 使得当前线程放弃cpu,进入等待状态(waiting),操作系统不再对它进行调度;当有其他线程对它调用了 unpark,unpark 使指定参数的线程恢复可运行状态。
public static void main(String [] main) throws InterruptedException {
Thread t = new Thread(){
public void run() {
LockSupport.park(); //放弃cpu
System.out.println("exit");
}
};
t.start(); //启动子线程
Thread.sleep(1000); //睡眠1秒确保子线程先运行到放弃cpu
LockSupport.unpark(t);
};
park 不同于Thread.yield(),yield只是告诉操作系统可以先让其他线程运行,但自己依然是可以运行状态,而park会放弃调度资格,使线程进入waiting状态。
park是响应中断的,当有中断发生时,park会返回,线程的中断状态会被设置。另外还需要说明的是,park可能会无缘无故有地返回,程序应该检查park等待条件是否满足。
park有两个变体:
当等待超时的时候,它们也会返回。
这些park方法还有一些变体,可以指定一个对象,表示是由该对象而进行等待的,以便于调试,通常传递的值是this。
public static void park(Object blocker);
LockSupport有一个方法,可以返回一个线程的blocker对象:
public static Object getBlocker(Thread t);
这些park/unpark方法是怎么实现的,与CAS一样,他们也调用了Unsafe类中的对应方法。
Unsafe类最终调用了操作系统的API,从程序员的角度,我们可以认为Lock—Support中的这些方法就是基本操作。