【JavaEE】多线程中安全的集合类、JUC的常见类的基本用法。

目录

一、Callable类

二、Reentrantlock类

        (1)ReentrantLock 和 synchronized 的区别: 

        (2)如何选择使⽤哪个锁?

三、Semaphore类

四、CountDownLatch类

五、多线程安全的集合类

        (1)多线程环境使⽤ArrayList

        (2)多线程环境使⽤队列

        (3)多线程环境使⽤哈希表


一、Callable类

        Callable是⼀个 interface。相当于把线程封装了⼀个 "返回值"。而 Callable 的返回值需要FutureTask类来接收,可以把 FutureTask 理解为Callable类的号码牌,通过futuretask.get()来获取最终结果值。

        Callable 和 Runnable相对,都是描述⼀个 "任务"。Callable 描述的是带有返回值的任务,  Runnable 描述的是不带返回值的任务。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo27 {
    public static int sum;
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                for (int i = 0; i <100 ; i++) {
                    sum += i;
                }
                return sum;
            }
        };
        FutureTask<Integer> future = new FutureTask<>(callable);
        Thread t = new Thread(future);
        t.start();
        int result = future.get();
        System.out.println(result);
    }
}

二、Reentrantlock类

        可重⼊互斥锁。和 synchronized 定位类似,都是⽤来实现互斥效果,保证线程安全。

        ReentrantLock 的⽤法:

                • lock():加锁,如果获取不到锁就死等。

                • trylock(超时时间):加锁,如果获取不到锁,等待⼀定的时间之后就放弃加锁。

                • unlock():解锁

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock();
    reentrantLock.lock();
    try {
        //working
    } finally {
        reentrantLock.unlock();
    }
}
        (1)ReentrantLock 和 synchronized 的区别: 

                • synchronized 是⼀个关键字,是 JVM 内部实现的。 ReentrantLock 是标准 库的⼀个类,在 JVM 外实现的。

                • synchronized 使⽤时不需要⼿动释放锁。ReentrantLock  使⽤时需要⼿动释放。使⽤起来更灵活,但是也容易遗漏 unlock。

                • synchronized 在申请锁失败时,会死等。ReentrantLock 可以通过 trylock 的⽅式等待⼀段时间就放弃。 

                • synchronized 是⾮公平锁,ReentrantLock 默认是⾮公平锁。可以通过构造⽅法传⼊⼀个 true 开启 公平锁模式.。

                 • 更强⼤的唤醒机制。synchronized 是通过 Object 的 wait / notify 实现等待-唤醒。每次唤醒的是⼀个 随机等待的线程。ReentrantLock 搭配 Condition类实现等待-唤醒,可以更精确控制唤醒某个指定的线程。

        (2)如何选择使⽤哪个锁?

                • 锁竞争不激烈的时候,使⽤ synchronized,效率更⾼,⾃动释放更⽅便。

                • 锁竞争激烈的时候,使⽤ ReentrantLock,搭配 trylock 更灵活控制加锁的⾏为。

                 • 如果需要使⽤公平锁,使⽤ ReentrantLock。

三、Semaphore类

        信号量,⽤来表⽰ "可⽤资源的个数",本质上就是⼀个计数器。Semaphore 的 PV 操作中的加减计数器操作都是原⼦的,可以在多线程环境下直接使⽤。而当 Semaphore 资源只有一个的时候,就相当于 “锁” 的作用,也被叫做 “二元信号量”。

import java.util.concurrent.Semaphore;
public class Demo30 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);
        Thread t1 = new Thread(()->{
            try {
                System.out.println("t1尝试获取资源");
                semaphore.acquire();
                System.out.println("t1获取成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(()->{
            try {
                System.out.println("t2尝试获取资源");
                semaphore.acquire();
                System.out.println("t2获取成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
    }
}

总结:synchronized,ReentrantLock,Semaphore 等都可以⽤于线程同步。

四、CountDownLatch类

        可以同时等待 N 个任务执⾏结束,没有确定线程的时候,比如用到线程池的时候,可以用countdownlatch来确定线程池任务执行结束。

                • 构造 CountDownLatch 实例,初始化 10 表⽰有 10 个任务需要完成。

                • 每个任务执⾏完,都调⽤ latch.countDown()。CountDownLatch内部计数器同时自减。

                • 主线程中使⽤ latch.await(); 阻塞等待所有任务执⾏完毕。

    public static void main(String[] args) throws InterruptedException {
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(4);
        CountDownLatch latch = new CountDownLatch(10);
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                int id = i;
                pool.submit(() -> {
                    System.out.println("线程名字" + Thread.currentThread() + "," + "执行任务" + id);
                });
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }
        });
        t.start();
        latch.await();
    }

五、多线程安全的集合类

        (1)多线程环境使⽤ArrayList

                •⾃⼰使⽤同步机制(synchronized 或者 ReentrantLock)。

                •Collections.synchronizedList(new ArrayList)。

synchronizedList 是标准库提供的⼀个基于 synchronized 进⾏线程同步的 List. synchronizedList 的关键操作上都带有synchronized

                •使⽤ CopyOnWriteArrayList

CopyOnWrite 容器即写时复制的容器,当我们往⼀个容器添加元素的时候,不直接往当前容器添加,⽽是先将当前容器进⾏Copy,复制 出⼀个新的容器,然后新的容器⾥添加元素,添加完元素之后,再将原容器的引⽤指向新的容器。这样做的好处是我们可以对 CopyOnWrite容器进⾏并发的读,⽽不需要加锁,因为当前容器不会添 加任何元素。 所以 CopyOnWrite容器也是⼀种读写分离的思想,读和写不同的容器。优点:在读多写少的场景下,性能很⾼,不需要加锁竞争。 缺点:1. 占⽤内存较多。 2. 新写的数据不能被第⼀时间读取到。

        (2)多线程环境使⽤队列

                •ArrayBlockingQueue基于数组实现的阻塞队列

                •LinkedBlockingQueue 基于链表实现的阻塞队列

                • PriorityBlockingQueue 基于实现的带优先级的阻塞队列

                • TransferQueue 最多只包含⼀个元素的阻塞队列

        (3)多线程环境使⽤哈希表

                •HashMap 本⾝不是线程安全的

                •在多线程环境下使⽤哈希表可以使⽤

                        • Hashtable

Hashtable  只是简单的把关键⽅法加上了 synchronized 关键字。 这相当于直接针对 Hashtable 对象本⾝加锁。如果多线程访问同⼀个 Hashtable 就会直接造成锁冲突。size 属性也是通过 synchronized 来控制同步,也是⽐较慢的。⼀旦触发扩容,就由该线程完成整个扩容过程。这个过程会涉及到⼤量的元素拷⻉,效率会⾮常低。

                        • ConcurrentHashMap

ConcurrentHashMap 相⽐于Hashtable 做出了⼀系列的改进和优化。以 Java1.8 为例

        • 读操作没有加锁(但是使⽤了 volatile 保证从内存读取结果),只对写操作进⾏加锁。加锁的⽅式仍然 是是⽤  synchronized,但是不是锁整个对象,⽽是 "锁桶" (⽤每个链表的头结点作为锁对象),⼤⼤降 低了锁冲突的概率。

        • 充分利⽤ CAS 特性。⽐如 size 属性通过 CAS 来更新,避免出现重量级锁的情况。

        • 优化了扩容⽅式::化整为零, 发现需要扩容的线程,只需要创建⼀个新的数组,同时只搬⼏个元素过去。

                 ◦ 扩容期间,新⽼数组同时存在。

                 ◦ 后续每个来操作 ConcurrentHashMap 的线程,都会参与搬运的过程,每个操作负责搬运⼀⼩部分元素。

=========================================================================

最后如果感觉对你有帮助的话,不如给博主来个三连,博主会继续加油的ヾ(◍°∇°◍)ノ゙。

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值