当场折服,这份阿里 P8 大牛给我的 JUC 知识总结真的超详细
}
public int getSerialNumber() {
return serialNumber.getAndIncrement();
}
}
ConcurrentHashMap 锁分段机制
=======================
Hashtable 效率非常低 复合操作可能线程不安全?一次只能一个线程进行操作
复合操作包括迭代(反复获取元素,直到容器中的最后一个元素)、导航(根据一定顺序查找下一元素)、条件运算(“若不存在则添加”,“若存在则删除”)
Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器 的性能
ConcurrentHashMap 同步容器类是 Java 5 增加的一个线程安全的哈希表。对 与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段” 机制替代?Hashtable 的独占锁。进而提高性能
此包还提供了设计用于多线程上下文中的 Collection 实现: ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、 CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给 定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap, ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远 大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList
ConcurrentHashMap 的 concurrentLevel 是 16
每个段都是独立的锁,当多个线程并发访问时,在不同的段上进行操作,则可做到并行
copyOnWriteArrayList 例子
public class CopyOnWriteArrayListTest {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 2; i++) {
new Thread(ht).start();
}
}
}
/**
CopyOnWriteArrayList 写入并复制,添加操作多时,效率低,因为每次添加时都会进行复制,开销很大
并发迭代操作多时可以选择
*/
class HelloThread implements Runnable {
// 这种会出现并发修改异常
// private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("AA");
}
}
}
CountDownLatch 闭锁
=================
Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器 的性能。
CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作 之前,它允许一个或多个线程一直等待。
闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活 动直到其他活动都完成才继续执行:
确保某个计算在其需要的所有资源都被初始化之后才继续执行; 确保某个服务在其依赖的所有其他服务都已经启动之后才启动; 等待直到某个操作所有参与者都准备就绪再继续执行
闭锁:在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才能继续执行
public class CountDownLatchTest {
public static void main(String[] args) {
// 5 表示其他线程的数量
CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
// 此处要一直等到 latch 的值为 0 ,就能往下执行了
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("消耗时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
}
Condition 控制线程通信
================
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用 法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的 功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关 联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版 本中的不同
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是?await、signal 和 signalAll
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 三个方法的使用方法和 wait,notify 和 notifyAll 一样
线程按序交替
======
编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要 求输出的结果必须按顺序显示。 如:ABCABCABC…… 依次递归
public class ABCAlternateTest {
public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
ad.loopA(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopB(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopC(i);
}
}
},"C").start();
}
}
class AlternateDemo {
// 记录当前正在执行的线程的 ID
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int totalLoop) {
try {
lock.lock();
if(number != 1) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.print(Thread.currentThread().getName());
}
number = 2;
condition2.signal();
} finally {
lock.unlock();
}
}
public void loopB(int totalLoop) {
try {
lock.lock();
if(number != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.print(Thread.currentThread().getName());
}
number = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void loopC(int totalLoop) {
try {
lock.lock();
if(number != 3) {
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.print(Thread.currentThread().getName());
}
number = 1;
condition1.signal();
System.out.print(" ");
} finally {
lock.unlock();
}
}
}
ReadWriteLock 读写锁
=================
ReadWriteLock 是一个接口
ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作。只要没有 writer,读取锁可以由?多个 reader?线程同时保持。写入锁是独占的
ReadWriteLock 读取操作通常不会改变共享资源,但执行 写入操作时,必须独占方式来获取锁。?对于读取操作占 多数的数据结构。ReadWriteLock 能提供比独占锁更高 的并发性。而对于只读的数据结构,其中包含的不变性 可以完全不需要考虑加锁操作
写写 / 读写 都需要“互斥”
读读 不需要互斥
public class ReadWriteLockTest {
public static void main(String[] args) {
ReadWriteLockDemo rw = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
rw.set((int)(Math.random() * 101));
}
},"write").start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.get();
}
}).start();
}
}
}
class ReadWriteLockDemo {
private int number = 0;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 读
public void get() {
try {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + ":" + number);
} finally {
readWriteLock.readLock().unlock();
}
}
// 写
public void set(int number) {
try {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName());
this.number = number;
} finally {
readWriteLock.writeLock().unlock();
}
}
}
线程八锁
====
静态同步方法与非静态同步方法之间是不会有静态条件的,非静态同步方法,看他们的锁是不是同一个 number,是同一个 number 则,一个拿到,另一个要等待;否则,另一个不用等待
线程八锁的关键: 1.非静态方法的锁 this, 静态方法的锁 对应的 Class 实例
1.两个同步方法,两个线程,打印 one two
2.新增 Thread.sleep()给 getOne 打印 one two
3.新增普通方法 getThree,打印 three one two
4.注释 getThree,number2.getTwo,打印 two one
5.修改 getOne 为静态同步方法,改为 number.getTwo,打印 two one
6.两个方法都为静态同步方法,一个 number 对象,打印 one two
7.getOne 为静态同步方法,getTwo 为同步方法,改为 number2.getTwo,打印 two one
8.两个静态同步方法,两个 number 对象,打印 one two
*/
public class Thread8MonitorTest {
public static void main(String[] args) {
Number number = new Number();
// 4
// Number number2 = new Number();
// 7
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 1,2,3
// number.getTwo();
// 4
// number2.getTwo();
// 5
// number.getTwo();
// 7
number2.getTwo();
}
}).start();
// 3
/*new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();*/
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("three");
}
}
线程池
===
第四种获取线程的方法:线程池,一个 ExecutorService,它使用可能的几个池线程之 一执行每个提交的任务,通常使用 Executors 工厂方法配置。
线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在 执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行 任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数 据,如完成的任务数。
为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但 是,强烈建议程序员使用较为方便的 Executors 工厂方法 :
Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
Executors.newFixedThreadPool(int)(固定大小线程池)
Executors.newSingleThreadExecutor()(单个后台线程) 它们均为大多数使用场景预定义了设置。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处: 1. 提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理?corePoolSize:核心池的大小 ,/maximumPoolSize:最大线程数,keepAliveTime:线程没有任务时最多保持多长时间后会终止 线程池:提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建于销毁的额外开销,提高了响应的速度
java.util.concurrent.Executor :负责线程的使用与调度的根接口 –ExecutorService 子接口:线程池的主要接口,继承 Executor –ThreadPoolExecutor 线程池的实现类 –ScheduledExecutorService 子接口:负责线程的调度,继承 ExecutorService –ScheduledThreadPoolExecutor 继承 ThreadPoolExecutor 实现 ScheduledExecutorService
public class ThreadPoolTest {
public static void main(String[] args) {
// 1.创建线程池 5 个线程
ExecutorService pool = Executors.newFixedThreadPool(5);
ThreadPoolDemo tpd = new ThreadPoolDemo();
/*// 2.为线程池中的线程分配任务
for (int i = 0; i < 5; i++) {
pool.submit(tpd);
}
// 3.关闭线程池,保证所有线程的任务完成才会关闭
// shutdownNow() 立即关闭,不管任务做完没有
pool.shutdown();*/
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
// Future 得到 Callable 的返回值
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int j = 0; j <= 100; j++) {
sum += j;
}
return sum;
}
});
list.add(future);
}
pool.shutdown();
for(Future<Integer> future : list) {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
线程调度
====
ScheduledExecutorService newScheduledThreadPool() 创建固定大小的线程,可以延迟或定时的执行任务
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
Sche
duledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
Future<Integer> result = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);
System.out.println(Thread.currentThread().getName() + ":" + num);
return num;
}
}, 3, TimeUnit.SECONDS);// 延迟 3s 执行
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
}
TimeUnit 的用法
ForkJoinPool 分支/合并框架 工作窃取
=========================
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成 若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进 行 join 汇总
采用 “工作窃取”模式(work-stealing): 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加 到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队 列中
相对于一般的线程池实现,fork/join 框架的优势体现在对其中包含的任务 的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些 原因无法继续运行,那么该线程会处于等待状态。而在 fork/join 框架实现中, 如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理 该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了 线程的等待时间,提高了性能
Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换
public class ForkJoinPoolTest {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
// RecursiveTask<V> extends ForkJoinTask<V>
ForkJoinTask<Long> task = new ForkJoinTest(0L, 50000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
@Test
public void test2() { // 18256
Instant start = Instant.now();
long sum = 0;
for (long i = 0; i <= 50000000000L; i++) {
sum += i;
}
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
@Test
public void test3() { // 14444
// 对 ForkJoin 的改进
Instant start = Instant.now();
// rangeClosed 生成连续的数
long sum1 = LongStream.rangeClosed(0, 50000000000L)
.parallel()
.reduce(0, Long::sum); // 第二个参数 是函数式接口 LongBinaryOperator
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
}
class ForkJoinTest extends RecursiveTask<Long> {// Recursive 递归
// RecursiveAction 没有返回值
// RecursiveTask 有返回值
private long start;
private long end;
public ForkJoinTest(long start, long end) {
this.start = start;
this.end = end;
}
private static final long THRESHOLD = 10000;
评论