A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 逆风TO 黑马粉丝团   /  2020-4-8 14:10  /  7241 人查看  /  174 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

文章目录
ConcurrentHashMap
CountDownLatch
CyclicBarrier
Semaphore
Exchanger
在JDK的并发包里提供了几个非常有用的并发容器和并发工具类,供我们在多线程开发中进行使用。

并发包的来历:
在实际开发中如果不需要考虑线程安全问题,大家不需要做线程安全,因为如果做了反而性能不好!
但是开发中有很多业务是需要考虑线程安全问题的,此时就必须考虑了。否则业务出现问题。
Java为很多业务场景提供了性能优异,且线程安全的并发包,程序员可以选择使用!
ConcurrentHashMap
为什么要使用ConcurrentHashMap:
HashMap线程不安全,会导致数据错乱
使用线程安全的Hashtable效率低下
基于以上两个原因,便有了ConcurrentHashMap的闪亮登场机会。
HashMap线程不安全演示
[Java] 纯文本查看 复制代码
public class ConcurrentHashMapDemo {
    // 定义一个静态的HashMap集合,只有一个容器。
     public static Map<String,String> map = new HashMap<>();
      //public static Map<String,String> map = new Hashtable<>();
    //public static Map<String,String> map = new ConcurrentHashMap<>();

    public static void main(String[] args) throws Exception {
        // HashMap线程不安全演示。
        // 需求:多个线程同时往HashMap容器中存入数据会出现安全问题。
        // 具体需求:提供2个线程分别给map集合加入50万个数据!
        Runnable target = new AddMapDataThread();
        Thread t1 = new Thread(target,"线程1");
        Thread t2 = new Thread(target,"线程2");
        t1.start();//让t1跑完,主线程不能抢t1的cpu,但是t2抢t1的cpu
        t2.start();//让t2跑完,主线程不能抢t1的cpu,但是t1抢t2的cpu

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

        //休息10秒,确保两个线程执行完毕
        //Thread.sleep(1000 * 4);
        //打印集合大小
        System.out.println("Map大小:" + map.size());
    }
}

class AddMapDataThread implements Runnable{
    @Override
    public void run() {
        for(int i = 1 ; i <= 500000 ; i++ ){
            ConcurrentHashMapDemo.map.put(Thread.currentThread().getName()+"键:"+i ,"值"+i);
        }
    }
}

说明:两个线程分别向同一个map中写入50000个键值对,最后map的size应为:100000,但多运行几次会发现有以下几种错误:
错误结果
1.png
假死
2.png
异常报错
3.png
Hashtable保证的线程安全,但是效率低。
HashTable效率低下原因:
public synchronized V put(K key, V value)
public synchronized V get(Object key)

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。
4.png
ConcurrentHashMap仍能保证结果正确,而且提高了效率。
ConcurrentHashMap高效的原因:CAS + 局部(synchronized)锁定分段式锁
5.png
小结:
HashMap是线程不安全的。
Hashtable线程安全基于synchronized,综合性能差,被淘汰了。
ConcurrentHashMap:线程安全的,分段式锁,综合性能最好,线程安全开发中推荐使用
CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作,再执行自己。

例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行。
CountDownLatch构造方法:
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象

CountDownLatch重要方法:
public void await() throws InterruptedException// 让当前线程等待
public void countDown()        // 计数器进行减1

代码示例
[Java] 纯文本查看 复制代码
public class CountDownLatchDemo {
    public static void main(String[] args) {
        //创建1个计数器:用来控制 A , B线程的执行流程的。
        CountDownLatch down = new CountDownLatch(1);

        new ThreadA(down).start();
        new ThreadB(down).start();
    }
}

class ThreadA extends Thread{
    private CountDownLatch down;
    public ThreadA(CountDownLatch down){
        this.down = down;
    }
    @Override
    public void run() {
        System.out.println("A");
        try {
            down.await(); // A线程你进入等待,让B线程执行自己!
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}

class ThreadB extends Thread{
    private CountDownLatch down;
    public ThreadB(CountDownLatch down){
        this.down = down;
    }
    @Override
    public void run() {
        System.out.println("B");
        down.countDown(); // 这里相当于是-1,代表自己执行完毕了。A线程被唤醒!!
    }
}

结果:会保证按:A B C的顺序打印。
6.png
说明:
CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。
await()方法的线程阻塞状态解除,继续执行。
CyclicBarrier
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
CyclicBarrier构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)
// 用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景

CyclicBarrier重要方法:
public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞

使用场景

使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。
Semaphore
Semaphore(发信号)的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore构造方法:
public Semaphore(int permits)                                                permits 表示许可线程的数量
public Semaphore(int permits, boolean fair)                        fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程


Semaphore重要方法:
public void acquire() throws InterruptedException        表示获取许可
public void release()                                                                release() 表示释放许可


Exchanger
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger构造方法:
public Exchanger()
1
Exchanger重要方法:
public V exchange(V x)

使用场景:可以做数据校对工作
需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,并对两个文件数据进行校对,看看是否录入一致.
你知道的越多,你不知道的越多。
有道无术,术尚可求,有术无道,止于术。
如有其它问题,欢迎大家留言,我们一起讨论,一起学习,一起进步



174 个回复

倒序浏览
感谢分享哦~
回复 使用道具 举报

感谢分享哦~
回复 使用道具 举报

感谢分享哦~
回复 使用道具 举报

感谢分享哦~
回复 使用道具 举报
6666666666666666666666
回复 使用道具 举报
66666666666666666666
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
感谢分享哦~
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
感谢分享
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
66666666666666666
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
作者棒棒哒
回复 使用道具 举报
棒棒哒 ..................... ..................
回复 使用道具 举报
很不错哦!!!
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马