并发编程(七)中断机制以及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);
    }

 

posted @ 2021-04-21 08:59  蒙恬括  阅读(108)  评论(0编辑  收藏  举报