JUC的常见类
文章目录
JUC的常见类引子1. Callable接口2. ReentrantLock3. 多线程环境下使用哈希表3.1 HashMap3.2 Hashtable3.3 ConcurrentHashMap
引子
JUC(java.util.current),这个包中存放了一些进行多线程开发时有用的工具类,这里会列举一些进行讲解(在之前的文章中其实也有展开讲解过其中的一些如线程池等,对于这些大家有兴趣可以去翻我前面的文章):
1. Callable接口
在通过Thread类创建线程的时候,之前是基于Runnable或lambda表达式来创建线程的,这里在加一种方式即基于Callable来创建线程。
Callable与Runnable的区别在于:
Runnable关注的是任务的执行过程,不关注执行结果,其提供的run方法返回类型为voidCallable需要关注任务的执行结果,其提供的call方法,返回值就是执行任务得到的结果
当我们执行的任务需要有返回结果时,就可以使用Callable来更快捷的获取返回值!
举个例子,计算1到1000加起来的总和,通过Callable来实现
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 计算 1 ~ 1000 的值的和
Callable
@Override
public Integer call() throws Exception {
int ret = 0;
for (int i = 1;i <= 1000;i++) {
ret += i;
}
return ret;
}
};
FutureTask
Thread t = new Thread(futureTask);
t.start();
int ret = futureTask.get();
System.out.println("1 ~ 1000 的总和 = " + ret);
}
}
注:
Callable<>中的泛型填写的就是任务执行完后需要返回的结果的参数类型;使用Callable需要搭配FutureTask这个类,通过FutureTask将callable实例包装一下,之后新线程会调用callable中的call方法,并将执行结果保存到futuretask对象中,通过FutureTask的get方法获取到数据,同时get方法能使当前线程进入阻塞直到与futuretask绑定的任务执行完并返回结果时才释放
2. ReentrantLock
ReentrantLock:可重入互斥锁,与synchronized一样都是通过互斥效果来维护线程安全的,但ReentrantLock并没有synchronized一样那么“智能”,它需要手动的加锁和解锁,存在以下方法:
lock():加锁,如果获取不到锁就会死等;unlock():解锁,容易遗忘,建议放在try–catch-finally的finally中;trylock(time):加锁,但如果获取不到锁,在等待一定时间后就会放弃加锁
那它与synchronized有哪些区别呢?区别如下:
ReentrantLock 它提供了trylock(),能够在线程获取不到锁时只阻塞等待一定时间就放弃获取锁,而synchronized会死等下去;ReentrantLock实现了公平锁,能够让先尝试获取锁的线程先拿到锁,而synchronized是非公平锁;ReentrantLock需要手动释放锁,而synchronized可以自动释放锁;ReentrantLock通过Condition类来实现等待和唤醒机制,能够更精准的唤醒某个指定的线程,而synchronized通过wait-notify来控制等待和唤醒,同时通知唤醒机制是由系统随机唤醒一个线程
大部分情况下使用synchronized就可以解决问题了,但在特殊情况如需要使用公平锁时就可以使用ReentrantLock来完成操作!
注:结合之前的文章总结下来,目前能够用来维护线程安全的方式有:
synchronizedReentrantLockCASSemaphore(信号量,执行PV操作,与学习操作系统时需要理解的信号量一样,相当于一种更广义的锁,这里不展开讲解)
3. 多线程环境下使用哈希表
3.1 HashMap
HashMap本身就是线程不安全的,一般不推荐在多线程环境下使用
3.2 Hashtable
Hashtable则可以在多线程环境下使用,因为它给关键的方法都加上了锁,但这样它的加锁位置可以理解为直接给Hashtable对象本身加上了锁:
如上图所示,只要有两个线程访问其中任意一个数据就都会出现锁冲突,然后其中一个线程就会进入阻塞,即使两个线程访问的是不同链表上的数据,这样显然会因为阻塞而降低我们程序的执行效率
3.3 ConcurrentHashMap
ConcurrentHashMap是标准库提供的一个更好的解决方案,它的加锁对象不再局限于给整个哈希表加锁,而是给哈希表中每个链表的头节点都分配一把锁,降低了锁的粒度,也解决了上面Hashtable没有解决的阻塞影响问题:
这样子多线程环境下只有两个线程访问的恰好是同一个哈希桶(链表)上的数据才会出现锁冲突!!同时,ConcurrentHashMap涉及到的读操作是不会加锁的(搭配了volatile关键字,能保证读取刚修改的数据),只对写操作加锁,大大降低了锁冲突的概率!
注:
在java1.7前ConcurrentHashMap采用的是锁分段技术,即将若干链表分为一个段,对每个段分别加锁,而1.8后取消了锁分段,改成对每个链表上的头节点加锁ConcurrentHashMap中也充分利用到了CAS技术,通过添加原子性操作减少加锁操作,进而减少线程阻塞ConcurrentHashMap优化了扩容,在哈希表需要进行扩容时,ConcurrentHashMap能够将一次总扩容分批次操作,在执行某种操作如插入操作时完成一部分扩容,累加起来后相当于完成一次总扩容,使得扩容时消耗的时间不会太长,这样对于用户来说就没有明显的直接影响
以上便是对JUC常见类的介绍与使用了,如果上述内容对大家有帮助的话请给一个三连支持吧💕( •̀ ω •́ )✧( •̀ ω •́ )✧✨