各种锁策略,JUC里面的信号量、计数器、循环屏障,HashMap总结

一、锁策略:

1.乐观锁

乐观锁认为一般情况下不会出现问题,所以在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有则会返回失败信息给用户自行处理。

乐观锁经典使用场景:CAS(Compare And Swap)对比并且替换
 
CAS实现    V[内存中的值], A[预期的旧值], B[新值]
V == A   ? V修改为B  : 不能修改

对比失败之后不是直接返回给用户错误信息,而是先进行自旋

面试题:CAS底层实现的原理是什么?
各种锁策略,JUC里面的信号量、计数器、循环屏障,HashMap总结
答:CAS在java中通过Unsafe实现的,Unsafe 是本地类或者本地方法,它是C或C++实现的原生方法,最终通过调用操作系统的Atomic::cmpxchg(原子指令)来实现的(实现对比和替换)


CAS在java中的应用:AtomicInteger/Atomic*(都是通过乐观锁实现的)

在多线程中实现i++、i-- 保证线程安全的方法:

  1. 加锁
  2. ThreadLocal
  3. AtomicInteger

AtomicInteger 保证线程安全的演示

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo91 {

    private static AtomicInteger count = new AtomicInteger(0);

    private static final int MAXSIZE = 100000;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAXSIZE; i++) {
                    count.getAndIncrement();   //表示i++
                    //count.incrementAndGet();   //++i
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAXSIZE; i++) {
                    count.getAndDecrement();   //i--
                    //count.decrementAndGet();     //--i
                }
            }
        });
        t2.start();

        t1.join();
        t2.join();

        System.out.println("最终的结果:"+count);
    }
}
最终的结果:0

但是!!!!!
乐观锁存在一个问题:ABA问题

各种锁策略,JUC里面的信号量、计数器、循环屏障,HashMap总结

面试题:ABA如何处理?
答:使用版本号,每次修改的时候判断预期的旧值和版本号,每次修改成功后更改版本号,这样即使预期的值和V值相等,因为版本号不同,所以也不能进行修改,从而解决了ABA问题。

java中处理ABA: AtomicStampedReference

转账代码进行演示:正常的情况:如下:

import java.util.concurrent.atomic.AtomicReference;

/**
 * 使用代码的方式演示ABA
 */
public class ThreadDemo92 {

    private static AtomicReference money = new AtomicReference(100);  //100是内存种的旧值

    public static void main(String[] args) throws InterruptedException {
        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0);   //预期值     修改值
                System.out.println("线程1执行转账"+result);
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0);   //预期值     修改值
                System.out.println("线程1执行转账"+result);
            }
        });
        t2.start();

        t1.join();
        t2.join();
    }
}

线程1执行转账true
线程1执行转账false

产生ABA的代码:

import java.util.concurrent.atomic.AtomicReference;

/**
 * 使用代码的方式演示ABA
 */
public class ThreadDemo92 {

    private static AtomicReference money = new AtomicReference(100);

    public static void main(String[] args) throws InterruptedException {
        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0);   //预期值     修改值
                System.out.println("线程1执行转账"+result);
            }
        });
        t1.start();
        t1.join();


        //中途账户多了一笔钱
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(0,100);
                System.out.println("线程3转入了100元"+result);
            }
        });
        t3.start();

        t1.join();
        t3.join();


        //不小心点错了
        //转账 -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0);   //预期值     修改值
                System.out.println("线程1执行转账"+result);
            }
        });
        t2.start();
    }
}
线程1执行转账true
线程3转入了100元true
线程1执行转账true

解决ABA的代码:

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 使用代码的方式演示ABA
 */
public class ThreadDemo93 {

    private static AtomicStampedReference money = new AtomicStampedReference(100,1);  //初始化值V    版本号

    public static void main(String[] args) throws InterruptedException {
        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                 * 需要传递四个值:
                 * expectedReference   预期旧值
                 * newReference    新值
                 * expectedStamp   旧版本号
                 * newStamp        新版本号
                 */
                boolean result = money.compareAndSet(100,0,1,2);
                System.out.println("线程1执行转账  "+result);
            }
        });
        t1.start();
        t1.join();


        //中途账户多了一笔钱
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(0,100,2,3);
                System.out.println("线程3转入了100元  "+result);
            }
        });
        t3.start();

        t1.join();
        t3.join();


        //不小心点错了
        //转账 -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0,1,2);   //预期值     修改值
                System.out.println("线程1执行转账  "+result);
            }
        });
        t2.start();
    }
}

线程1执行转账  true
线程3转入了100元  true
线程1执行转账  false

注意事项:AtomicStampedReference【可以解决ABA,有版本号】 和 AtomicReference 【有ABA问题】


当把金额全部改为1000的时候:

import java.util.concurrent.atomic.AtomicStampedReference;

public class ThreadDemo94 {

    private static AtomicStampedReference money = new AtomicStampedReference(1000,1);  //初始化值V    版本号

    public static void main(String[] args) throws InterruptedException {
        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                 * 需要传递四个值:
                 * expectedReference   旧值
                 * newReference    新值
                 * expectedStamp   旧版本号
                 * newStamp        新版本号
                 */
                boolean result = money.compareAndSet(1000,0,1,2);
                System.out.println("线程1执行转账  "+result);
            }
        });
        t1.start();
        t1.join();


        //中途账户多了一笔钱
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(0,1000,2,3);
                System.out.println("线程3转入了100元  "+result);
            }
        });
        t3.start();

        t1.join();
        t3.join();


        //不小心点错了
        //转账 -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(1000,0,1,2);   //预期值     修改值
                System.out.println("线程1执行转账  "+result);
            }
        });
        t2.start();
    }
}
线程1执行转账  false
线程3转入了100元  false
线程1执行转账  false

AtomicStampedReference 注意事项:里面的旧值它对比的是引用,并不是实际的值。

Integer 高速缓存:-128~127(当取值为此范围的时候会使用缓存值,不会重新new对象,超过这个范围就是引用)
各种锁策略,JUC里面的信号量、计数器、循环屏障,HashMap总结
解决方案:将缓存的边界设置的大一点
各种锁策略,JUC里面的信号量、计数器、循环屏障,HashMap总结

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 使用代码的方式演示ABA
 */
public class ThreadDemo94 {

    private static AtomicStampedReference money = new AtomicStampedReference(1000,1);  //初始化值V    版本号

    public static void main(String[] args) throws InterruptedException {

        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                 * 需要传递四个值:
                 * expectedReference   旧值
                 * newReference    新值
                 * expectedStamp   旧版本号
                 * newStamp        新版本号
                 */
                boolean result = money.compareAndSet(1000,0,1,2);
                System.out.println("线程1执行转账  "+result);
            }
        });
        t1.start();
        t1.join();


        //中途账户多了一笔钱
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(0,1000,2,3);
                System.out.println("线程3转入了100元  "+result);
            }
        });
        t3.start();

        t1.join();
        t3.join();


        //不小心点错了
        //转账 -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(1000,0,1,2);   //预期值     修改值
                System.out.println("线程1执行转账  "+result);
            }
        });
        t2.start();
    }
}

线程1执行转账  true
线程3转入了100元  true
线程1执行转账  false

2.悲观锁

悲观锁认为只要执行多线程,就会出现问题,所以在进入方法之后会直接加锁

悲观锁的实现:synchronized

面试题:讲一下 synchronized ?synchronized是如何实现的(也可以是实现原理)

  1. 在java层面,将锁标识放在对象头(每个对象都会有对象头)里面,然后每次去判断对象头的标识和归属判断锁是属于谁的,判断有没有加锁,如果没有加锁,直接使用,并且将标识改为已有锁,加上归属人
  2. 在JVM层面,是监视器锁
  3. 在操作系统层面,是互斥锁

面试题:synchronized在1.7的时候有什么优化(synchronized做了哪些优化)(也可以是实现原理)
在1.7之前都是重量级锁
无锁 -> 偏向锁 -> 轻量级锁(自旋) -> 重量级锁

面试题:什么是偏向锁
在线程初次访问的时候,将线程的id放到对象头中的偏向锁id的字段中,每次线程访问时,判断一下线程的id是否等于对象头中的偏向锁id,如果相等则表明这个线程拥有此锁,就可以正常执行代码了,否则就表明线程不拥有此锁,通过自旋的方式尝试获取锁。

3.公平锁和非公平锁(Java锁默认的策略时非公平锁)

  • 公平锁:获取锁的顺序按照线程访问的先后顺序获取 new ReentrantLock(true) -> 公平锁
  • 非公平锁:不会按照线程的先后访问顺序按序获取锁 (java里面的默认策略,性能比较高)

4. 独占锁和共享锁

  • 独占锁:指的是这一把锁只能被一个线程拥有 synchronized,Lock
  • 共享锁:指的是一把锁可以被多个线程同时拥有 ReadWriteLock 读写锁,读锁就是共享的(读的时候不会发生线程安全的问题)
    读写锁的优势:将锁的粒度更加的细化,从而提高锁的性能

读锁是共享锁,写锁是独占锁,读锁和写锁之间是互斥的

代码演示:

import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁示例
 */
public class ThreadDemo95 {
    public static void main(String[] args) {
        //创建读写锁  可重入的读写锁   可以设置是否为公平锁,默认是非公平锁
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        //得到读锁
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();

        //得到写锁
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

        //创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));

        //任务1  读锁演示
        executor.execute(new Runnable() {
            @Override
            public void run() {
                readLock.lock();  //加锁
                try {
                    System.out.println(Thread.currentThread().getName()+" 线程进入了读锁,时间:"+new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readLock.unlock();  //释放锁
                }
            }
        });

        //任务2  读锁演示
        executor.execute(new Runnable() {
            @Override
            public void run() {
                readLock.lock();  //加锁
                try {
                    System.out.println(Thread.currentThread().getName()+" 线程进入了读锁,时间:"+new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readLock.unlock();  //释放锁
                }
            }
        });
        //如果两个锁的打印时间间隔不超过1秒,说明读锁是可共享的,否则是不可共享的


        //任务3   写锁
        executor.execute(new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+" 执行了写锁,时间:"+new Date());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    writeLock.unlock();
                }
            }
        });

        //任务4   写锁
        executor.execute(new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+" 执行了写锁,时间:"+new Date());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    writeLock.unlock();
                }
            }
        });
        //如果两个锁的打印时间间隔不超过1秒,说明写锁是可共享的,否则是不可共享的(独占锁)
    }
}

pool-1-thread-1 线程进入了读锁,时间:Fri May 28 22:29:53 CST 2021
pool-1-thread-2 线程进入了读锁,时间:Fri May 28 22:29:53 CST 2021
pool-1-thread-3 执行了写锁,时间:Fri May 28 22:29:56 CST 2021
pool-1-thread-4 执行了写锁,时间:Fri May 28 22:29:57 CST 2021

5. 可重入锁

可重入锁指的是一个线程在拥有了一把锁之后,可以重复的进入( synchronized)

/**
 * 可重入锁演示
 */
public class ThreadDemo96 {
    //声明锁
    private static Object object = new Object();

    public static void main(String[] args) {
        synchronized (object) {
            System.out.println("进入了主方法");
            synchronized (object) {
                System.out.println("重复进入了方法");
            }
        }
    }
}
进入了主方法
重复进入了方法

典型使用场景:synchronized、ReentrantLock

6. 自旋锁

自旋锁相当于死循环,一直循环尝试获取锁。(synchronized,在偏向锁升级为轻量级锁的过程中)

下面的代码时伪代码,不可执行,只是想演示一下什么是自旋锁

public class ThreadDemo96 {
    //声明锁
    private static Object object = new Object();

    public static void main(String[] args) {
        while (true) {
            if (尝试获取锁) {
                break;
            }
        }
        for (;;) {
            if (尝试获取锁) {
                break;
            }
        }
    }
}

自旋锁的实现场景:synchronized


二、JUC(Java并发包)

各种锁策略,JUC里面的信号量、计数器、循环屏障,HashMap总结
JUC包下的所有类都是线程安全的,JUC下有:

  1. ReentrantLock 可重入锁

    a)lock()一定要放在try之前     
    b)在finally里面一定要释放锁
    
  2. Semaphore 信号量,可以用来实现限流功能

  3. CyclicBarrier 循环屏障

  4. CountDownLatch 计数器 (没有公平还是非公平之说的,等待所有的线程进入某个步骤之后在统一执行某个流程,就好像田径比赛,当所有的选手都到达重点之后,再来宣布成绩)

1.信号量代码演示

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadDemo97 {
    public static void main(String[] args) {
        //创建信号量
        Semaphore semaphore = new Semaphore(2);   //2 代表停车场里面有两个位置,即最多执行两个任务,剩下的任务只能等到前面的任务执行结束才能进行执行

        //创建线程池
        //每一个任务就是一辆车
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));

        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 到达停车场门口");
                try {
                    Thread.sleep(1000);
                    
                    //尝试获取锁
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行到这行代码代表已经获取到了停车位
                System.out.println(Thread.currentThread().getName()+" 进入停车场");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+" 离开停车场");

                //释放锁
                semaphore.release();
            }
        });

        //执行任务2
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 到达停车场门口");
                try {
                    Thread.sleep(1000);
                    //尝试获取锁
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行到这行代码代表已经获取到了停车位
                System.out.println(Thread.currentThread().getName()+" 进入停车场");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+" 离开停车场");

                //释放锁
                semaphore.release();
            }
        });

        //执行任务3
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 到达停车场门口");
                try {
                    Thread.sleep(1000);
                    //尝试获取锁
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行到这行代码代表已经获取到了停车位
                System.out.println(Thread.currentThread().getName()+" 进入停车场");
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+" 离开停车场");

                //释放锁
                semaphore.release();
            }
        });


        //执行任务4
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 到达停车场门口");
                try {
                    Thread.sleep(1000);
                    //尝试获取锁
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行到这行代码代表已经获取到了停车位
                System.out.println(Thread.currentThread().getName()+" 进入停车场");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+" 离开停车场");

                //释放锁
                semaphore.release();
            }
        });
    }
}
pool-1-thread-1 到达停车场门口
pool-1-thread-2 到达停车场门口
pool-1-thread-3 到达停车场门口
pool-1-thread-4 到达停车场门口
pool-1-thread-3 进入停车场
pool-1-thread-1 进入停车场
pool-1-thread-1 离开停车场
pool-1-thread-4 进入停车场
pool-1-thread-3 离开停车场
pool-1-thread-2 进入停车场
pool-1-thread-2 离开停车场
pool-1-thread-4 离开停车场

2. 计数器代码演示

执行原理就是内部有一个计数器,当执行了CoubtDown 之后计数器-1,直到减到0那么这个计数器就使用完毕了,就可以执行await()之后的代码了

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 计数器示例
 */
public class ThreadDemo98 {
    public static void main(String[] args) throws InterruptedException {
        //创建计数器为3
        CountDownLatch countDownLatch = new CountDownLatch(3);  //当线程数到达3个之后再去执行其他的任务

        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));


        //执行任务
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 开跑");
                int num = new Random().nextInt(5);
                num+=1;
                try {
                    Thread.sleep(num*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 也到达了终点");

                //计数器-1
                countDownLatch.countDown();

            }
        });

        //执行任务
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 开跑");
                int num = new Random().nextInt(5);
                num+=1;
                try {
                    Thread.sleep(num*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 也到达了终点");

                //计数器-1
                countDownLatch.countDown();

            }
        });

        //执行任务
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 开跑");
                int num = new Random().nextInt(5);
                num+=1;
                try {
                    Thread.sleep(num*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 也到达了终点");

                //计数器-1
                countDownLatch.countDown();

            }
        });


        //执行任务
/*
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 开跑");
                int num = new Random().nextInt(5);
                num+=1;
                try {
                    Thread.sleep(num*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 也到达了终点");

                //计数器-1
                countDownLatch.countDown();
            }
        });
*/

        //等待所有的选手都到达终点,等待计数器为0
        countDownLatch.await();

        System.out.println("比赛结束,宣布成绩");
    }
}
pool-1-thread-1 开跑
pool-1-thread-2 开跑
pool-1-thread-3 开跑
pool-1-thread-1 也到达了终点
pool-1-thread-3 也到达了终点
pool-1-thread-2 也到达了终点
比赛结束,宣布成绩

但是上面的计数器(countDownLatch )只能使用一次,当使用结束以后就不能继续使用了。所以有了循环屏障

3. 循环屏障

循环屏障可以循环使用

代码演示:

import java.util.concurrent.*;

/**
 * 循环屏障
 */
public class ThreadDemo99 {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
            @Override
            public void run() {
                System.out.println("----------到达了屏障------------");
            }
        });

        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));

        for (int i = 0; i < 4; i++) {
            final int finalI = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(finalI*1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" 进入了任务");
                    try {
                        //等待其他线程执行
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    //System.out.println(Thread.currentThread().getName()+"  执行结束");
                }
            });
        }
    }
}
pool-1-thread-1 进入了任务
pool-1-thread-2 进入了任务
----------到达了屏障------------
pool-1-thread-3 进入了任务
pool-1-thread-4 进入了任务
----------到达了屏障------------

循环屏障执行原理:内部有一个计数器,每次线程执行到await方法的时候,计数器+1,直到计数器个数等于创建时声明的格式的时候,就会突破屏障,执行之后的代码,在突破屏障之后计时器清零可以进行新一轮的执行了。

面试题:CyclicBarrier 和CountDownLauch 的区别?
答:CountDownLauch 他的计数器只能使用一次 CyclicBarrier可以反复使用


三、HashMap:非安全的容器

  1. 多线程下的问题:
    a) JDK1.7 头插法 -> 死循环
    b) JDK1.8 尾插法 -> 数据覆盖

  2. 解决上面两个问题的方法:
    1.加锁
    2.JDK提供HashMap安全容器 ConcurrentHashMap(分段锁) Hashtable(put整体加锁)

  3. HashMap的安全版本:ConcurrentHashMap

ConcurrentHashMap实现线程安全的原理是在修改操作的时候(put),会在进入方法之后加锁,并且会在操作完成之后释放锁,所以不会有线程安全的问题

ConcurrentHashMap优化:将HashMap分成多个sengment对每个 sengment 分别进行加锁,这样就可以保障多线程如果操作的不是同一个sengment就不需要进行排队处理了,从而提高了程序的执行效率。

HashMap是非线程安全的
Hashtable是线程安全的

上一篇:java-JUC--BlockingQueueDemo(阻塞队列)


下一篇:JUC编程入门