多线程(三)


J.U.C 即 java.util.concurrent,是 JSR 166 标准规范的一个实现; JSR 166 以及 J.U.C 包的作者是 Doug Lea 。

CAS

核心

1、先比较后修改
2、没有获取锁,释放锁的操作
3、硬件层面的原子操作

原理

围绕3点:
1、当前内存值V
2、旧的预期值A
3、即将更新的值B
如图:
在这里插入图片描述
主内存中有num=10为当前内存值V
在这里插入图片描述
线程A将num拷贝到副本中时为旧的预期值A
在这里插入图片描述
线程A将num改成9为即将更新的值B,然后A值会和V值对比,相同就修改主内存的数据,若不相同就自旋等待并再次尝试,直到成功

多CPU的CAS处理

1、总线加锁

  • 处理器提供的一个LOCK#信号
  • 在锁定期间,其他处理器都不能其他内存地址的数据,其开销有点儿大。

2、缓存加锁

  • 同一个内存区域的数据仅能被一个处理器修改

CAS的实现原理

缺陷

1、ABA问题:(如图)
在这里插入图片描述
若线程A、B同时拷贝主内存的num值到副本中,线程A修改多次后结果仍然为10并同步到主内存中时,此时线程B修改为11,进行A值和V值对比后发现相同,并修改主内存的数据。该现象就是ABA问题(正常来说线程A已修改,根据CAS,线程B应该会修改失败,但因为线程A改后的值与改前一致,导致线程B可以修改成功)

解决办法:

使用AtomicStampedReference(基于乐观锁)

public class Demo4ABA {

    private static AtomicInteger ai = new AtomicInteger(100);
    private static AtomicStampedReference air = new AtomicStampedReference(100, 1);

    //ABA问题演示:
    //1. 线程1先对数据进行修改 A-B-A过程
    //2. 线程2也对数据进行修改 A-C的过程
    public static void main(String[] args) throws InterruptedException {

        // AtomicInteger可以看到不会有任何限制随便改
        // 线程2修改的时候也不可能知道要A-C 的时候,A是原来的A还是修改之后的A
        Thread at1 = new Thread(new Runnable() {
            public void run() {
                ai.compareAndSet(100, 110);
                ai.compareAndSet(110, 100);
            }
        });

        Thread at2 = new Thread(new Runnable() {
            public void run() {
                try {
                    //为了让线程1先执行完,等一会
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicInteger:" + ai.compareAndSet(100, 120));
                System.out.println("执行结果:" + ai.get());
            }
        });

        at1.start();
        at2.start();

        //顺序执行,AtomicInteger案例先执行
        at1.join();
        at2.join();

        //AtomicStampedReference可以看到每次修改都需要设置标识Stamp,相当于进行了1A-2B-3A的操作
        //线程2进行操作的时候,虽然数值都一样,但是可以根据标识很容易的知道A是以前的1A,还是现在的3A
        Thread tsf1 = new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1
                air.compareAndSet(100, 110, air.getStamp(), air.getStamp() + 1);
                air.compareAndSet(110, 100, air.getStamp(), air.getStamp() + 1);
            }
        });

        Thread tsf2 = new Thread(new Runnable() {
            public void run() {

                //tsf2先获取stamp,导致预期时间戳不一致
                int stamp = air.getStamp();

                try {
                    TimeUnit.MILLISECONDS.sleep(100);     //线程tsf1执行完
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicStampedReference:" + air.compareAndSet(100, 120, stamp, stamp + 1));
                int[] stampArr = {stamp + 1};
                System.out.public class Demo4ABA {

    private static AtomicInteger ai = new AtomicInteger(100);
    private static AtomicStampedReference air = new AtomicStampedReference(100, 1);

    //ABA问题演示:
    //1. 线程1先对数据进行修改 A-B-A过程
    //2. 线程2也对数据进行修改 A-C的过程
    public static void main(String[] args) throws InterruptedException {

        // AtomicInteger可以看到不会有任何限制随便改
        // 线程2修改的时候也不可能知道要A-C 的时候,A是原来的A还是修改之后的A
        Thread at1 = new Thread(new Runnable() {
            public void run() {
                ai.compareAndSet(100, 110);
                ai.compareAndSet(110, 100);
            }
        });

        Thread at2 = new Thread(new Runnable() {
            public void run() {
                try {
                    //为了让线程1先执行完,等一会
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicInteger:" + ai.compareAndSet(100, 120));
                System.out.println("执行结果:" + ai.get());
            }
        });

        at1.start();
        at2.start();

        //顺序执行,AtomicInteger案例先执行
        at1.join();
        at2.join();

        //AtomicStampedReference可以看到每次修改都需要设置标识Stamp,相当于进行了1A-2B-3A的操作
        //线程2进行操作的时候,虽然数值都一样,但是可以根据标识很容易的知道A是以前的1A,还是现在的3A
        Thread tsf1 = new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1
                air.compareAndSet(100, 110, air.getStamp(), air.getStamp() + 1);
                air.compareAndSet(110, 100, air.getStamp(), air.getStamp() + 1);
            }
        });

        Thread tsf2 = new Thread(new Runnable() {
            public void run() {

                //tsf2先获取stamp,导致预期时间戳不一致
                int stamp = air.getStamp();

                try {
                    TimeUnit.MILLISECONDS.sleep(100);     //线程tsf1执行完
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicStampedReference:" + air.compareAndSet(100, 120, stamp, stamp + 1));
                int[] stampArr = {stamp + 1};
                System.out.println("执行结果:" + air.get(stampArr));
            }
        });

        tsf1.start();
        tsf2.start();
    }
}println("执行结果:" + air.get(stampArr));
            }
        });

        tsf1.start();
        tsf2.start();
    }
}

运行结果充分展示了AtomicInteger的ABA问题和AtomicStampedReference解决ABA问题
2、循环时间太长
3、只能保证一个共享变量原子操作

AQS

AQS(AbstractQueuedSynchronizer),即队列同步器
在这里插入图片描述
AQS维护了一个volatile int类型的变量state表示当前同步状态。当state>0时表示已经获取了锁,当state = 0时表示释放了锁
入列
在这里插入图片描述
出列
在这里插入图片描述

Condition

作用:可以精确的唤醒某个线程

Condition提供了一系列的方法来对阻塞和唤醒线程:

  1. await() :造成当前线程在接到信号或被中断之前一直处于等待状态。

  2. **await(long time, TimeUnit unit) **:造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

  3. **awaitNanos(long nanosTimeout) **:造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout – 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。

  4. **awaitUninterruptibly() **:造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】。

  5. **awaitUntil(Date deadline) **:造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。

  6. signal():唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。

  7. signalAll():唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。

    Condition是一种广义上的条件队列(等待队列)。他为线程提供了一种更为灵活的等待/通知模式,线程在调用await方法后执行挂起操作,直到线程等待的某个条件为真时才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。

public class DemoCondition {

    private Lock reentrantLock = new ReentrantLock();
    private Condition condition1 = reentrantLock.newCondition();
    private Condition condition2 = reentrantLock.newCondition();

    public void m1() {
        reentrantLock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 已经进入执行等待。。。");
            condition1.await();
            System.out.println("线程 " + Thread.currentThread().getName() + " 已被唤醒,继续执行。。。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void m2() {
        reentrantLock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 已经进入执行等待。。。");
            condition1.await();
            System.out.println("线程 " + Thread.currentThread().getName() + " 已被唤醒,继续执行。。。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void m3() {
        reentrantLock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 已经进入执行等待。。。");
            condition2.await();
            System.out.println("线程 " + Thread.currentThread().getName() + " 已被唤醒,继续执行。。。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void m4() {
        reentrantLock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 已经进入发出condition1唤醒信号。。。");
            condition1.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void m5() {
        reentrantLock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 已经进入发出condition2唤醒信号。。。");
            condition2.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        final Demo11Condition useCondition = new Demo11Condition();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                useCondition.m1();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                useCondition.m2();
            }
        }, "t2");
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                useCondition.m3();
            }
        }, "t3");
        Thread t4 = new Thread(new Runnable() {
            public void run() {
                useCondition.m4();
            }
        }, "t4");
        Thread t5 = new Thread(new Runnable() {
            public void run() {
                useCondition.m5();
            }
        }, "t5");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(2000);
        t4.start();


        Thread.sleep(2000);
        t5.start();
    }
}

在这里插入图片描述

工具包

CyclicBarrier 同步屏障

作用

等待所有线程准备好再执行

案例

田径比赛,所有运动员准备好了之后,大家一起跑,代码如下

public class Demo1CyclicBarrier {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(new Athlete(cyclicBarrier, "运动员" + i));
            threadList.add(t);
        }

        for (Thread t : threadList) {
            t.start();
        }
    }

    static class Athlete implements Runnable {

        private CyclicBarrier cyclicBarrier;
        private String name;

        public Athlete(CyclicBarrier cyclicBarrier, String name) {
            this.cyclicBarrier = cyclicBarrier;
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(name + "就位");
            try {
                cyclicBarrier.await();
                System.out.println(name + "跑到终点。");
            } catch (Exception e) {
            }
        }
    }
}

CountDownLatch 计数器

作用

一个线程执行前必须由前面n个线程执行完了才可以执行(比如接力)

案例

public class Demo2CountDownLatch {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            CountDownLatch countDownLatch = new CountDownLatch(1);
            //起点运动员
            Thread t1 = new Thread(new Athlete(cyclicBarrier, countDownLatch, "起点运动员" + i));

            //接力运动员
            Thread t2 = new Thread(new Athlete(countDownLatch, "接力运动员" + i));

            threadList.add(t1);
            threadList.add(t2);
        }

        for (Thread t : threadList) {
            t.start();
        }
    }

    static class Athlete implements Runnable {

        private CyclicBarrier cyclicBarrier;
        private String name;

        CountDownLatch countDownLatch;

        //起点运动员
        public Athlete(CyclicBarrier cyclicBarrier, CountDownLatch countDownLatch, String name) {
            this.cyclicBarrier = cyclicBarrier;
            this.countDownLatch = countDownLatch;
            this.name = name;
        }

        //接力运动员
        public Athlete(CountDownLatch countDownLatch, String name) {
            this.countDownLatch = countDownLatch;
            this.name = name;
        }

        @Override
        public void run() {
            //判断是否是起点运动员
            if (cyclicBarrier != null) {

                System.out.println(name + "就位");
                try {
                    cyclicBarrier.await();
                    System.out.println(name + "到达交接点。");

                    //已经到达交接点
                    countDownLatch.countDown();
                } catch (Exception e) {
                }
            }

            //判断是否是接力运动员
            if (cyclicBarrier == null) {
                System.out.println(name + "就位");
                try {
                    countDownLatch.await();
                    System.out.println(name + "到达终点。");
                } catch (Exception e) {
                }
            }
        }
    }
}

Semaphore 信号量

作用

开辟一个空间,里面可以存放多个线程并允许同时执行(比如停车场)
线程池是生成多个线程,然后取出线程进行执行

ConcurrentHashMap

hashMap是线程不安全的,多线程下有可能会死循环
在这里插入图片描述
在1.8版本以前,ConcurrentHashMap采用分段锁的概念,使锁更加细化,但是1.8已经改变了这种思路,而是利用CAS+Synchronized来保证并发更新的安全,当然底层采用数组+链表+红黑树的存储结构。
在这里插入图片描述
Synchronized只给某个hash槽中的某个元素加锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值