多线程之七:复习回顾

目录

一、多线程基础概念&三大特性

1. wait()方法的使用

2. sleep方法与wait方法的区别

3. volatile关键字

4. CAS面试

5. ThreadLocal

6. 并发编程三大特性如何保证

5. 其他知识点回忆

二、锁

1. 锁的分类有哪些

2. synchronized

2.1 synchronized的使用

2.2 synchronized的锁优化

2.3 synchronized实现原理

3. AQS面试

4. Atomic原子类

5. ReentrantLock和ReentrantReadWriteLock

5.1 Lock接口基本的方法

5.2 Lock 接口方法详解

(1)lock()

(2)tryLock()

(3)tryLock(long time,TimeUnit unit)

(4)LockInterruptibly

5.3 Lock和synchronized的区别

5.4 自旋锁&阻塞锁

5.5 好文:

5.6 ConditionObject

5.7 不熟悉的几道面试题

5.8 死锁★★★

三、阻塞队列

3.1 概述

3.2 各实现类对比

四、线程池

1. ThreadPoolExecutor的UML类图

2. 线程池的参数有哪些?如何设计?

3. ThreadPoolExecutor是如何运行的呢?

4. 线程池的阻塞队列有哪些常用的实现?

5. JDK提供的拒绝策略有哪些?

6. 其他

五、并发集合

六、并发工具&异步编程


一、多线程基础概念&三大特性

1. wait()方法的使用

  • wait()方法是锁调用的,不是线程调用的,即应该是 锁对象.wait();  而不是 线程对象.wait();
  • 无参的wait()必须手动唤醒,有参时到了时间被唤醒。
/**
 * 线程状态
 */
public class ThreadStateDemo {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o) {
                    try {
                        o.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }, "t1");
        System.out.println(t1.getState());//NEW

        t1.start();
        Thread.sleep(1000);
        System.out.println(t1.getState());//WAITING

        synchronized (o) {
            o.notify();
        }
        System.out.println(t1.getState());//BLOCKED

        Thread.sleep(1000);
        System.out.println(t1.getState());//TERMINATED
    }
}

2. sleep方法与wait方法的区别

3. volatile关键字

        volatile可以解决并发编程中的可见性和有序性,并不能解决原子性。

        使用场景:①标记位;②解决DCL懒汉式(单例设计模式)中指令重排的问题。

        说明:双重检测锁模式,也叫DCL懒汉式(DCL:Double Check Lock)

        设计模式之---单例模式(以及DCL懒汉式并发问题解决)_单例singletonholder-CSDN博客

        Java并发:volatile关键字详解-CSDN博客

4. CAS面试

(1)CAS是什么?

(2)CAS的缺点有哪些?

        CAS 是什么?_什么是 cas-CSDN博客

        https://www.cnblogs.com/javazhizhe/p/17478838.html 

https://joonwhee.blog.csdn.net/article/details/79561458?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_paycolumn_v3&utm_relevant_index=2icon-default.png?t=N7T8https://joonwhee.blog.csdn.net/article/details/79561458?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1.pc_relevant_paycolumn_v3&utm_relevant_index=2

5. ThreadLocal

        ThreadLocal浅析-CSDN博客

6. 并发编程三大特性如何保证

(1)如何保证并发编程的原子性

  • 加锁;——Lock锁,synchronized锁;
  • CAS方式;——如基于CAS实现的AtomicInteger等原子类,AtomicStampedReference解决了ABA问题
  • ThreadLocal;——ThreadLocal保证原子性的方式,是不让多线程去操作临界资源,让每个线程去操作属于自己的数据。

(2)如何保证并发编程的可见性

  • 加锁;——Lock锁,synchronized锁;
  • volatile;—— 使用该关键字之后,CPU不会使用三级缓存,从而确保每次读取数据都要从主内存读取;
  • final关键字。

(3)如何保证并发编程的有序性

  • volatile —— 在属性上加volatile后,通过内存屏障来禁止指令重排,从而解决了乱序问题。

5. 其他知识点回忆

  • 线程状态有哪些?
  • 线程的常用API有哪些?
  • 创建线程、结束线程的方式分别有哪些?
  • 并发编程中的三大特性指什么?

二、锁

1. 锁的分类有哪些

  • synchronized是悲观锁,可重入锁,非公平锁,互斥锁。
  • ReentrantLock是悲观锁,可重入锁,互斥锁,默认是非公平锁(也可实现公平锁)。
  • ReentrantReadWriteLock是悲观锁,可重入锁,既可共享锁也可互斥锁(读读共享),既可公平锁也可以非公平锁。
  • 乐观锁:Atomic原子性类中,就是基于CAS乐观锁实现的。

悲观锁:获取不到资源时会挂起(进入BLOCKED、WAITING),线程挂起会涉及到用户态和内核态的切换,而这种切换是比较消耗资源的。

乐观锁:获取不到锁资源,可以再次让CPU调度,重新尝试获取锁资源。

互斥锁:同一把锁在同一时刻只能有一个线程持有。

2. synchronized

2.1 synchronized的使用

(a)同步代码块时,锁对象就是小括号()中配置的对象; 

(b)同步方法时,有两种情况:如果是静态方法,则锁是当前类的Class对象;如果是普通方法,则锁对象为当前实例对象

2.2 synchronized的锁优化

JDK1.6进行了优化,优化之一就是锁升级。

Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级-CSDN博客

synchronized锁升级详细过程-CSDN博客

第十一章详解synchronized锁升级-CSDN博客

2.3 synchronized实现原理

首先需要了解Java中对象在堆内存的存储。

Mark Word详情:

MarkWord中标记着四种锁的信息:无锁、偏向锁、轻量级锁、重量级锁。 重量级锁底层是ObjectMonitor。

3. AQS面试

AQS的全称是 AbstractQueuedSynchronizer,主要是用来构造同步器和锁,Java的 Juc 包中很多锁如 ReentrangLock、Semaphore,它们都有一个共同的基类,就是AQS,因为 AQS 能十分便利的搭建锁或者同步器,所以在 Java 并发编程中得以大量使用,同样的,我们可以基于 AQS 来搭建自己的锁或者同步器。

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

参考:AQS 详细介绍 - 知乎 

https://www.cnblogs.com/kxxiaomutou/p/15980675.html

4. Atomic原子类

在 Java 1.5后引入了原子类操作类,这些操作的加入,让我们不用锁即可以实现原子操作,更加高效,简洁。

Atomic包下所有的原子类都只适用于单个元素,即只能保证一个基本数据类型、对象、或者数组的原子性。根据使用范围,可以将这些类分为四种类型,分别为原子更新基本类型原子更新数组原子更新引用原子更新属性

参考:https://www.cnblogs.com/kxxiaomutou/p/15980936.html

https://www.cnblogs.com/javazhizhe/p/17478841.html

Java16个原子类介绍_java 原子类-CSDN博客

Java原子类Atomic详解_java atomic-CSDN博客

Atomic原子类常用方法总结(包含四大类型)_原子类里的方法-CSDN博客

问:

  • 什么是原子类?与锁有什么区别?
  • 原子类底层是如何实现的?
  • 原子更新基本类型有哪些?常用API有哪些?

5. ReentrantLock和ReentrantReadWriteLock

        相比于 ReentrantLock 适用于一般场合,ReentrantReadWriteLock 适用于读多写少的情况,合理使用可以进一步提高并发效率。 

5.1 Lock接口基本的方法

5.2 Lock 接口方法详解

(1)lock()
  • 最普通的获取锁,如果所被其他线程获得了,进行等待
  • Lock不会像synchronized一样在异常时自动释放锁
  • 使用时,一定要在finally中释放锁
  • lock()方法 不能被中断,一旦死锁,lock() 就会永久等待
(2)tryLock()
  • tryLock()用来尝试获取锁,如果当前线程没有被其他线程占用,则获取成功,则返回true,否则返回false,代表获取锁失败
  • 相比上面的lock(),他可以返回一个值,让我们知道是否成功获取到锁;进而决定后续程序的行为
  • 它会立刻返回,即便在拿不到锁时,不会一直等待
(3)tryLock(long time,TimeUnit unit)

        可以设定超时时间的尝试获取锁,一段时间内等待锁,超时就放弃。使用 tryLock(long time,TimeUnit unit) 来避免死锁的代码也略,详见下面链接文章中。

(4)LockInterruptibly

        这个是lockInterruptibly和tryLock(time,unit)唯一的区别是:lockInterruptibly拿不到锁资源就死等,等到锁资源释放后被唤醒,或者是被中断唤醒。

5.3 Lock和synchronized的区别

        略。

5.4 自旋锁&阻塞锁

5.5 好文:

https://www.cnblogs.com/javazhizhe/p/17478837.html ★★★★★

AQS原理分析-CSDN博客

AQS源码分析之ConditionObject_conditionobject源码分析-CSDN博客 

百度安全验证icon-default.png?t=N7T8https://baijiahao.baidu.com/s?id=1710170107392167577&wfr=spider&for=pcCondition的await-signal流程详解_await signal-CSDN博客

5.6 ConditionObject

ConditionObject是AQS中定义的内部类,实现了Condition接口,在其内部通过链表来维护等待队列(条件队列)。Contidion必须在lock的同步控制块中使用,调用Condition的signal方法并不代表线程可以马上执行,signal方法的作用是将线程所在的节点从等待队列中移除,然后加入到同步队列中,线程的执行始终都需要根据同步状态(即线程是否占有锁)。

每个条件变量都会有两个方法,唤醒和等待。当条件满足时,我们就会通过唤醒方法将条件容器内的线程放入同步队列中;如果不满足条件,我们就会通过等待方法将线程阻塞然后放入条件队列中。

深入详解Condition条件队列、signal和await-腾讯云开发者社区-腾讯云

【多线程系列】JUC 中的另一重要大杀器 AQS 抽象队列同步器-腾讯云开发者社区-腾讯云

5.7 不熟悉的几道面试题

(1)为什么需要锁降级?

        答:提高效率。某个线程执行过程中不同时间段的操作不同,一开始执行写操作,之后都进行读;一直使用写锁的话,后面的读操作不能和其他线程进行共享,就会浪费资源;如果将写锁释放掉然后去抢占读锁,不一定能抢到。所有就有了写锁降级,然后让其他线程也能获取到读锁。遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级为读锁。

(2)为什么不支持读锁的升级?

        答:因为读锁升级需要等所有的读锁都释放了才能升级,容易造成死锁,比如两个线程都在等待升级的话,就会互相等待对方释放读锁,就成了死锁。

(3)什么是锁的可见性保证?

  • lock符合happens-before规则,具有可见性
  • 当线程解锁,下一个线程加锁后可以看到所有前一个线程解锁前发生的所有操作。

(4)锁饥饿是什么?

        答:等待时间过长以至于给进程推进和响应带来明显影响,“饿而不死”。

5.8 死锁★★★

        高频面试点。

什么是死锁?

死锁产生的条件是什么?

如何预防?

如何检测?

学习文章:

心得:感觉锁这一块是并发编程核心中的核心,原理、源码、流程等等,内容很多。

太耗时了,以下暂且只罗列大纲吧。

三、阻塞队列

3.1 概述

BlockingQueue是java.util.concurrent包下的接口,用于在多线程环境下实现线程安全的数据传输。

并发阻塞队列BlockingQueue解读-腾讯云开发者社区-腾讯云   ★★★★★ 

并发编程-BlockingQueue阻塞队列 - 知乎 

BlockingQueue(阻塞队列)详解-CSDN博客

【面试题精讲】什么是 BlockingQueue?-腾讯云开发者社区-腾讯云

JUC之阻塞队列解读(BlockingQueue)-腾讯云开发者社区-腾讯云

Java 阻塞队列 BlockingQueue 介绍: put,add 和 offer 三个方法-腾讯云开发者社区-腾讯云

3.2 各实现类对比

ArrayBlockingQueue : 底层基于数组的,不支持读写同时操作,因为底层用的同一把锁。

LinkedBlockingQueue:底层基于链表。支持读写同时操作,因为底层两把锁。并发情况下,效率高。

ArrayBlockingQueue 和 LinkedBlockingQueue 是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题时,使用这两个足以。

PriorityBlockingQueue :基于优先级排序的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定)。PriorityBlockingQueue是无界队列(默认初始长度11,而后动态扩容),基于数组,数据结构为二叉堆,数组第一个也是树的根节点总是最小值。PriorityBlockingQueue并不会阻塞生产者,而只会在没有可消费数据时,阻塞数据的消费者。因此,使用时须特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。

DelayQueue : 使用优先级队列实现的延迟无界阻塞队列。它里面的元素只有当其指定的延迟时间到了,才能从队列中取到该元素。DelayQueue无界,所以插入数据的操作(生产者)永远不会阻塞,只有获取数据的操作才会阻塞。

SynchronousQueue : 不存储元素的阻塞队列。

四、线程池

Java线程池实现原理及其在美团业务中的实践 - 美团技术团队 (meituan.com)

1. ThreadPoolExecutor的UML类图

2. 线程池的参数有哪些?如何设计?

动态化线程池的核心设计包括以下三个方面:

  1. 简化线程池配置:线程池构造参数有8个,但是最核心的是3个:corePoolSize、maximumPoolSize,workQueue,它们最大程度地决定了线程池的任务分配和线程分配策略。考虑到在实际应用中我们获取并发性的场景主要是两种:(1)并行执行子任务,提高响应速度。这种情况下,应该使用同步队列,没有什么任务应该被缓存下来,而是应该立即执行。(2)并行执行大批次任务,提升吞吐量。这种情况下,应该使用有界队列,使用队列去缓冲大批量的任务,队列容量必须声明,防止任务无限制堆积。所以线程池只需要提供这三个关键参数的配置,并且提供两种队列的选择,就可以满足绝大多数的业务需求,Less is More。
  2. 参数可动态修改:为了解决参数不好配,修改参数成本高等问题。在Java线程池留有高扩展性的基础上,封装线程池,允许线程池监听同步外部的消息,根据消息进行修改配置。将线程池的配置放置在平台侧,允许开发同学简单的查看、修改线程池配置。
  3. 增加线程池监控:对某事物缺乏状态的观测,就对其改进无从下手。在线程池执行任务的生命周期添加监控能力,帮助开发同学了解线程池状态。

3. ThreadPoolExecutor是如何运行的呢?

其运行机制如下图所示:

        线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:

(1)直接申请线程执行该任务;

(2)缓冲到队列中等待线程执行;

(3)拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。 

4. 线程池的阻塞队列有哪些常用的实现?

5. JDK提供的拒绝策略有哪些?

6. 其他

线程池的钩子函数有哪些?

submit方法与execute方法的区别?

ScheduledThreadPoolExecutor是干什么的?

线程池的生命周期:

五、并发集合

1. 首先把我的那篇并发集合的笔记看完;

2. 下面这篇博文耐心读完,尤其关注以下几点:

https://www.cnblogs.com/javazhizhe/p/17480249.html

(1)ConcurrentHashMap 的 put() 操作发生了什么?get操作呢?(面试)

(2)CopyOnWriteArrayList 相关部分

  • 绝大多数并发情况下,ConcurrentHashMap 和 CopyOnWriteArrayList的性能都很好;
  • CopyOnWriteArrayList 更适合读多写少的场景,如果经常有写操作, Collections.sychronizedList 比 CopyOnWriteArrayList 性能好;
  • 无论是读还是写操作,ConcurrentHashMap 都比 Hashtable、Collections.synchronizedMap() 方法的性能好。

六、并发工具&异步编程

看看我写的那篇文章即可。写不动了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值