写点什么

当场折服,这份阿里 P8 大牛给我的 JUC 知识总结真的超详细

  • 2021 年 11 月 12 日
  • 本文字数:6882 字

    阅读完需:约 23 分钟

}


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


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


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;

评论

发布
暂无评论
当场折服,这份阿里P8大牛给我的JUC知识总结真的超详细