并发编程(七)中断机制以及CAS记录
等待唤醒机制:
1:我们知道Object中有wait/notify机制,但是这种机制是基于Monitor机制实现,使用Monitor Object操作的,所以wait/notify方法是在sychronized作用域范围内才能使用的,不然会报错。
而且notify要在wait方法调用之后进行调用才有用,提前调用不会唤醒任何线程。所以有时候如果两个线程无法保证执行的先后顺序可能会出问题。
2:使用LockSupport.park/unpark,是基于线程的,可以控制线程来操作等待唤醒。而且可以先调用unpark。
线程的中断机制
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而是需要被中断的线程自己处理中断。
注意不要使用Thread类中的stop方法,这个方法在jdk1.8中已经设置为过期而且直接抛出来异常:
API使用:
interrupt(): 将线程的中断标志位设置为true。(并不会中断线程)
isInterrupted(): 判断当前线程的中断标志位是否为true
Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为false。
中断代码示例:
public static void main(String[] args) {
Thread thread1= new Thread(()->{
int i=0;
while (true){
i++;
System.out.println(i);
if (Thread.interrupted()){
System.out.println("======================");
}
if (i==10)break;
}
});
thread1.start();
//中断方法
thread1.interrupt();
}
调用了 thread1.interrupt();,但是线程thread1不会马上停止运行,它只是设置了中断标志位为true, 线程是否退出需要我们自己根据这个中断标志位进行判断。
我们上诉代码中使用: Thread.interrupted() 来判断线程是否中断,其实就是判断中断标志位是否为true.
从运行结果看,输出语句只输出了一句,这是因为Thread.interrupted() 返回中断标志位之后会重置中断标志位。
还有一个API判断中断标志位,可以不清除中断标志位;
Thread.currentThread().isInterrupted()
sleep过程中是否可以被中断?
public static void main(String[] args) { Thread thread1= new Thread(()->{ int i=0; while (true){ i++; System.out.println(i); try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } if (Thread.currentThread().isInterrupted()){ System.out.println("======================"); } if (i==10)break; } }); thread1.start(); //中断方法 thread1.interrupt(); }
从运行结果可以看出,sleep过程可以被中断,而且是抛出异常来终止的,并且下面的输出语句没有执行,说明sleep中断后会清除中断标志位。
wait(500L) 方法是否可以被中断呢?
@Override public synchronized void run() { int i=0; while (true){ i++; System.out.println(i); try { wait(500L); // Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } if (Thread.currentThread().isInterrupted()){ System.out.println("======================"); } if (i==10)break; } }
从运行结果看,wait方法也是可以被中断的,中断也是抛出一个异常,并且会清除中断标志位。但是我们看到输出的异常在数字之间,这是因为System.out底层是stdout标准输出流,这个流是有缓冲的,所以不是那种实时输出的。如果是System.error--stderr标准错误输出流是没有缓冲的。
LockSupport.park是否可以被中断呢?
@Override public synchronized void run() { int i=0; while (true){ i++; System.out.println(i); LockSupport.park(); if (Thread.currentThread().isInterrupted()){ System.out.println("======================"); } if (i==10)break; } }
从运行结果看,LockSupport.park可以被中断,但是不会抛出异常,并且,被中断之后不会清除中断标志位。其实这里中断操作可以通过LockSupport.unpark(thread1)来操作了。
CAS
CAS是一种乐观锁的实现方式,Java原子类中的递增操作就是通过CAS自旋锁实现的,CAS全称为:Compare and Swap (比较与交换)是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。
unsafe.compareAndSwapObject(this, tailOffset, expect, update);
LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" lock cmpxchgl
unsafe.compareAndSwapObject(this, tailOffset, expect, update);
this: 需要改变的对象
tailOffset: 改变对象变量value的内存偏移地址
except: 期望更新的值
update: 要更新的最新的值
如果原子变量中的value值等于except,则使用update值更新该值并返回true,否则返回false。
缺点:
只能对一个变量的原子性操作
长时间自旋会给CPU带来压力
ABA的问题
我们以 AtomicInteger自增来看下CAS.
private static AtomicInteger atomicInteger = new AtomicInteger(0); public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread threa = new Thread(() -> { for (int j = 0; j < 10000; j++) { atomicInteger.getAndIncrement(); } }); threa.start(); } try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicInteger.get()); }
结果是:
从结果看,执行过程是原子性的。但是要注意,这只是一个操作时原子性的,如果有两个操作就不是原子性了。
执行过程:
Unsafe:
上面在Java层面获取内存中的实际数据var5, 调用compareAndSet方法会调用JVM底层的方法,在c++会再次获取内存中的值,下面的addr指针就是,这是从Java到底层还有时间差,其它线程可能修改了内存中的值。
ABA问题
其实使用上面的 AtomicInteger,会有ABA的问题,至于ABA是什么问题以及复现就不再详述了,网上也有很多,这里记录下,怎么解决ABA问题,通过增加版本号。
java.util.concurrent.atomic包下有 AtomicStampedReference类,这个类在进行 compareAndSet的时候会增加版本号的比较。
它把值和版本号封装了一个Pair类,进行cas之前首先进行reference的比较也就是值的比较,再进行stamp的比较,这个就是版本号,两个都相同了再往下进行。
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
casePair 直接比较替换两个Pair对象。
private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }