类似于Runnable用来描述一个任务,Runnable描述的任务没有返回值,但是Callable描述的任务是有返回值的.
如果我们需要使用一个线程单独的计算出某个结果,此时使用Callable是比较合适的.
java.util.concurrent这个包里放了并发编程(多线程)相关的组件.多线程是实现并发编程的一种具体方式.(同时也是Java中提供的默认的方式)除了这种方式外,还有其他的并发编程模型.
创建线程计算 1 + 2 + 3 + … + 1000
public class ThreadDemo29 {
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 < 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
Integer result = futureTask.get();
System.out.println(result);
}
}
可重入锁,这里ReentrantLock是标准库给我们提供的另一种锁,顾名思义也是"可重入的".synchronized是基于代码块的方式来进行加锁解锁的.ReentrantLock更传统,它使用lock和unlock方法进行加锁解锁.
public class ThreadDemo30 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
try{
//...
}finally {
reentrantLock.unlock();
}
}
}
ReentrantLock 和 synchronized 的区别:
public class ThreadDemo30 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock(true);
boolean result = reentrantLock.tryLock();
try{
if(result){
//做需要考虑线程安全的操作
}else{
// 啥都不做
}
}finally {
if(result){
reentrantLock.unlock();
}
}
}
}
原子类:
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference
AtomicInteger 举例,常见方法:
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;
基于CAS确实更高效的解决了线程安全问题,但是CAS不能代替锁.CAS使用范围是有限的,不像锁适用范围广.
信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.此处的信号量是Java把操作系统原生的信号量封装了一下.
可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源.当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.
Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.
public class ThreadDemo31 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(4);
semaphore.acquire();
System.out.println("执行一次 P 操作");
semaphore.acquire();
System.out.println("执行一次 P 操作");
semaphore.acquire();
System.out.println("执行一次 P 操作");
semaphore.acquire();
System.out.println("执行一次 P 操作");
semaphore.release();
}
}
同时等待 N 个任务执行结束. 类似于跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。
为了等待这个比赛的结束,就引入了CountDownLatch.
主要是两个方法:
例如:指定四个选手比赛,初始情况下,调用await就会阻塞,每个选手都冲过终点,都会调用countDown方法.前三次调用countDown,await没有任何影响,但是第四次调用countDown,await就会被唤醒,返回(解除阻塞).此时就可以认为是整个比赛都结束了.
在实际开发中CountDownlatch有很多使用场景,例如下载器:迅雷 steam idm fdm qBittorrent 这些下载器可以给我们提供多线程下载.我们可以使用CountDownLatch来进行区分是不是整体下载完了.
Java标准库里大部分集合类都是"线程不安全"的.Vector.StackHashTable这几个类关键方法带有synchronized,是少数的线程安全的集合类.
多线程环境使用HashMap线程是不安全的.另一个选择是HashTable是线程安全的.给关键方法加了synchronized,更推荐的是使用ConcurrentHashMap.这是一个更优化的线程安全哈希表.
ConcurrentHashMap进行了哪些优化?和HashTable之间区别?