JUC,原子类,信号量Semaphore,多线程使用哈希表

JUC(java.util.concurrent)

包含了各种集合类,

Callable接口 

类似于Runnable一样,Runnable用来描述一个任务,描述的任务没有返回值,Callable也是用来描述一个任务,描述的任务是有返回值的,如果一个线程单独需要计算出一个结果Callable是比较合适的。Callable也是创建线程的一种方式

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

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用Callable来计算1+2+3+。。。。+1000
        //Callable中的类型就是最后返回值的类型
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            //call方法相当于Runnable的run方法 call方法的返回值是泛型参数
            public Integer call() throws Exception {
               int sum = 0;
               for(int i =0;i <= 100;i++){
                   sum+=i;
               }
               return sum;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);//不能直接传入Callable
        t.start();
        
        Integer result = futureTask.get();
        //get方法是获取结果 get会发生阻塞 直到callable执行完毕 get才阻塞结束
        System.out.println(result);
    }
}

不能直接把Callable传入Thread构造方法里,需要套上一层其他区的辅助类。

ReentrantLock

可重入的,ReentrantLock是标准库给我们提供的另一种锁,也是“可重入的”。Synchronized是直接基于代码块的方式加锁解锁的,RenntrantLock,是使用lock和unlock方法加锁解锁的,建议用法:把unlock方法到finally中,但是对比Synchronized来看加锁解锁是非常并不方便的

1.ReentrantLock提供了公平锁的实现,synchronized是非公平锁

 ReentrantLock reentrantLock = new ReentrantLock(true);

参数写成true则是公平锁,写成flase或者则是非公平锁

2.ReentrantLock提供了tryLock();,无参数版本,能加锁就加锁,加不上锁就放弃。有参数版本,指定了超时时间,加不上锁就等待,而synchronized来说,提供的加锁操作就是“死等”,只要获取不到锁就一直死等下去。

 reentrantLock.tryLock();

3.RenntrantLock提供了一个更强大,更方便的等待通知机制,synchronized搭配的是wait notify,notify的时候是随机唤醒一个wait的线程,而RenntranLock搭配一个Condition类,进行唤醒的时候可以唤醒指定的线程.

原子类

基于CAS实现,保证线程安全的,但是CAS不能代替锁,CAS使用范围没有锁那么广

信号量Semaphore

就好比停车场会在入口这里,显示一个牌子,当前空闲车位有XX个,每次有车,从入口进去,计数器就-1,每次有车,从出口出来,计数器就+1,如果当前停车场里面的车满了,计数器就是0了,这个时候,如果还有车想停,1)在这里等,2)放弃这里,去找别的停车场。

信号量本质上就是一个计数器,描述了“可用资源的个数”,p操作:申请一个可用资源,计数器就要-1,v操作:释放一个可用资源,计数器就要+1,p操作如果要是计数器为0了,继续p操作,就会出现阻塞等待的情况。

一个初始值为1的信号量,针对这个信号量的值,就只有1和0两种取值,执行一次p操作,1->0,执行一次V操作,0->1,如果已经进行一次p操作了,继续进行p操作,就会阻塞等待,锁可以视为计数器为1的二元信号量,锁是信号量里的一种特殊情况,

CountDownLatch

就好比,举行运动会有跑步比赛,选手的起始时间是相同的,但是结束时间不一定相同,而这个比赛结束的时间,是由最后一名来决定的,为了等待跑步比赛结束就引入了CountDownLatch

主要有两个方法:1.await(wait是等待,a = all)主线程来调用这个方法。2.countDown表示选手冲过了终点线,CountDownLatch在构造的时候,指定一个计数(选手的个数),例如,指定四个选手进行比赛,初始情况下,,调用await就会阻塞,每个选手都冲过终点,都会调用countDown方法,前三次调用countDown,await没有任何影响,第四次调用countDown,await就会被唤醒,返回,解除阻塞,此时可以认为是整个比赛都结束了

多线程使用哈希表

 HashMap是线程安全的,HashTable是线程安全的,里边给关键方法,加了synchronized,相比于这两个更推荐使用ConcurrentHashMap更优化的线程安全哈希表

ConcurrentHashMap进行了哪些优化?比HashTable好在哪里?和HashTable之间的区别是什么?

1.最大的优化之处:ConcurrentHashMap相比于HashTable大大缩小了锁冲突的概率,把一把大锁,转换成多把小锁了,HashTable是直接在方法上就加锁,等于是给this加锁,只要操作哈希表上的任意元素,都会产生加锁,也就都有可能发生锁冲突,但是实际上,基于哈希表的结构特点,有些元素在进行并发操作的时候,是不会产生线程安全问题的,也就不需要使用锁控制

如果元素1和2在同一个链表上,如果线程A修改元素1,同时线程B修改元素2,那么就会由线程安全问题,如果这两个元素相邻,此时进行并发的插入或者修改,就需要修改这两节点相邻的节点的next的指向,但是如果线程A修改元素1,线程B修改元素3,那么就没有线程安全问题,这种情况就相当于多个线程修改不同的变量,ConcurrentHashMap的做法是给每个链表都有自己的锁,使用每个链表的头节点作为锁对象,此时锁的粒度变小了,针对1和2这种情况,是针对同意把锁,会有锁竞争,会保证线程安全。针对不同的锁进行加锁,不会由锁竞争了,没有阻塞等待,程序就会更快。

2.ConcurrentHashMap做了一个激进的操作,针对读操作,不加锁,只针对写操作加锁,读和写之间没有冲突,写和写之间有冲突,读和读之间也没有冲突,但是很多场景下,读写之间不加锁控制,可能会读到一个写了一半的结果,如果写操作不是原子的,此时读就可能会读到写了一半的数据,读早了,但是ConcurrentHashMap使用了volatile加一些原子的写操作,让写操作称为了原子的。

3.ConcurrentHashMap内部充分的使用了CAS,通过这个也进一步的削减加锁操作的数目。

4.针对扩容,采取了“化整为零”的方式。HashMap/hashTable扩容,创建一个更大的数组空间,把旧的数组上的链表上的每个元素搬运到新的数组上,这个扩容操作会在某次put的时候进行触发,当负载因子达到一定值时候,ConcurrentHashMap中,扩容采取的是每次搬运一部分元素的方式,创建新的数组,旧的数组也保留,每次put操作,都往新数组上添加,同时进行一部分搬运,每次get的时候,则旧数组和新数组都查询,每次remove的时候,只是元素删了就行了,经过多次搬运之后,所有的旧数组都搬运好了,最终再释放旧数组

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值