【多线程】JUC的常见类 | Callable接口 | ReentranLock | 线程安全的集合类


一、JUC的常见类

JUC :Java.util.concurrent

​ 并发(这个包里的内容,主要就是一些多线程相关的组件)

1.Callable接口

  • 也是一种创建线程的方式,配合FutureTask使用,FutureTask保存任务执行的结果
  • 适用于想让某个线程执行一个逻辑,并且返回结果的时候

相比之下,Runnable不关注结果。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义任务
        Callable<Integer> callable = new Callable<Integer>() {
            //泛型的参数类型就是希望返回值的参数类型
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 1001; i++) {
                    sum += i;
                }
                return sum;
            }
        };
        //把任务放进线程中去执行
        //callable不能直接传进线程当中,需要FutureTask类进行包装
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t1 = new Thread(futureTask);
        t1.start();
        //get方法来获取到callable的返回结果
        //由于线程是并发执行的,执行到get的时候,t线程可能还没执行完,get就会进行阻塞。
        System.out.println(futureTask.get());
    }
  • callable不能直接传进线程当中,需要FutureTask类进行包装
  • t线程还没执行完时,get就会进行阻塞

​ 就类似于食堂点餐,点餐完成之后会给你一个取餐牌,后厨就相当于一个线程,开始执行,这个过程需要进行等待。直到饭做好了,就可以凭餐牌取餐。futureTask就是小票,拿着小票来换取线程执行的结果。

创建线程的方式:

1.直接继承Thread,重写run(创建一个类/匿名内部类)

2.实现Runnable,重现run

3.实现Callable,重写call

4.Lambda表达式

5.ThreadFactory线程工厂

6.线程池


2.ReentranrLock

  • 可重入锁,效果和synchronized类似。
	ReentrantLock lock = new ReentrantLock(); 
-----------------------------------------
lock.lock();   
try {    
 // working    
} finally {    
 lock.unlock()    
}

要记得调用unlock(),进行解锁

1.ReentranLock的优势
1.两种加锁方法

​ lock() :加锁后,如果遇到锁冲突,就会一直阻塞等待。

​ tryLock() : 尝试去加锁,如果没加上锁,就放弃了。

2.提供了公平锁的实现

默认情况下是非公平锁,可以通过构造方法传入一个 true 开启公平锁模式

公平锁(队列-实现加锁顺序)

3.提供了更强大的等待通知机制。

synchronized 是通过 Object 的 wait / notify 实现等待-唤醒,每次唤醒的线程是随机的

ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程


二、线程安全的集合类

数据结构中的集合类大部分都是线程不安全的

Vector.Stack,HashTable的线程安全的。(都带有Synchronized)

Stack继承自Vector。Vector和HashTable都是历史遗留,是早期Java引入的集合类。在方法上都加上了synchronized.

针对其他线程不安全的集合类,如果在多线程环境中使用,就需要考虑线程安全问题。

可以自行进行加锁,同时Java标准库也提供了一些搭配的组件,来保证线程安全。

1.多线程环境使用ArraList
1.synchronizedList
Collections.synchronizedList(new ArrayList)
  • 会返回一个新的对象,相当于给ArrayList套了层壳。在方法上直接使用synchronized。

就相当于ArrayList和加锁的外壳是可拆分的,吸取了Vector的教训,把本体和外壳分离开。在单线程环境下用本体,在多线程环境下用套外壳的本体。

2.CopyOnWriteArrayList
写时拷贝。

比如:多个线程同时使用一个ArrayList。可能会读,也可能会修改。

  • 如果两个线程都是读操作,不会涉及到线程安全问题。

  • 如果某个线程要进行修改,就把ArrayList复制出一份副本。修改线程就去修改这个副本,与此同时,另一个线程仍然可以读取数据(从原来的数据上进行读取),并不会相互干扰。

一旦修改完毕,就会使用修改好的数据,替换掉原来的数据(往往是引用赋值)

这样的修改过程,就不需要进行加锁。

局限性:

1.当前操作的ArrayList不能太大(拷贝成本不能太高)

2.更适合一个线程来修改,而不能是多个线程同时修改。(多个线程读,一个线程修改的场景)

​ 针对特定场景:适用于服务器的配置跟新。可以通过配置文件来描述配置的详细内容(文件本身不是很大),配置的内容就会被读到内存中,再由其他的线程,来读取这里的内容。而修改这个配置内容,往往只有一个线程来修改。使用某个命令让服务器重新加载配置,就可以使用写时拷贝的方式。

2.多线程环境使用队列

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

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

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

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

3.多线程环境使用哈希表

​ Hashtable保证线程安全的方式,主要就是给关键方法加上synchromized。只要两个线程,操作同一个Hashtable,就会出现锁冲突。

​ 实际上,如果不考虑触发扩容的前提下。对于链地址法的哈希表来说,操作不同链表上的内容,此时是线性安全的。只有在操作同一个链表上的内容时,才会发生线程安全问题。但是整个Hashtable只有一把锁。

1.ConcurrentHashMap
  • 1.调整了锁的粒度

​ ConcurrentHashMap最核心的改进,就是把一个全局的大锁,改进成每个链表独立的小锁。从而大幅度的降低锁冲突的概率。

​ 把每个链表的头结点,作为锁对象

​ 分段锁:Java8之前,concurrentHashMap就是基于分段锁的方式实现的(多个链表共用一把锁)。从Java8之后,就成了直接在链表头结点加锁。

  • 2.充分利用到了CAS的特性,把一些不必要加锁的环节省略了。

​ 比如使用一个变量来记录哈希表的中的元素个数,此时就没必要对变量来进行加锁,直接通过原子操作。用CAS来维护元素个数。

  • 3.针对读操作没有加锁。(激进)

读和读之间,读和写之间,都没有锁竞争。写和写还需要加锁。

底层修改的时候,避免了使用++这种非原子操作。而是使用 赋值“=”进行修改,保证写操作本身就是原子的。读的时候要么是写之前的旧值,要么是写之后的新值,不会出现读一半的情况。

  • 4.针对扩容操作,做了单独的优化。

​ 本身Hashtable或者HashMap在扩容的时候,都需要把所有元素都拷贝一遍。如果元素很多,拷贝就比较耗时。在极端情况下,用户访问了1000次,999次很流畅。而第1000次触发扩容,就会造成严重的卡顿问题。

  • 用化整为零的方法来解决:一旦需要进行扩容,确实需要搬运。但是不是一次搬运完,而是分为了多次进行搬运。每次只搬运一部分数据来避免单次操作过于卡顿的问题。

​ 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去. 扩容期间, 新老数组同时存在. 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运一小部分元素. 搬完最后一个元素再把老数组删掉. 这个期间, 插入只往新数组加. 这个期间, 查找需要同时查新数组和老数组。


点击移步博客主页,欢迎光临~

偷cyk的图

  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值