1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

Merge pull request #286 from dongzl/master

第二部分《Java》部分错误修正
This commit is contained in:
SnailClimb 2019-04-23 20:01:29 +08:00 committed by GitHub
commit 1a815963fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 41 deletions

View File

@ -614,9 +614,9 @@ TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
**HashMap 和 Hashtable 的区别** **HashMap 和 Hashtable 的区别**
1. **线程是否安全:** HashMap 是非线程安全的HashTable 是线程安全的HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 1. **线程是否安全:** HashMap 是非线程安全的Hashtable 是线程安全的Hashtable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
2. **效率:** 因为线程安全的问题HashMap 要比 HashTable 效率高一点。另外HashTable 基本被淘汰,不要在代码中使用它; 2. **效率:** 因为线程安全的问题HashMap 要比 Hashtable 效率高一点。另外Hashtable 基本被淘汰,不要在代码中使用它;
3. **对Null key 和Null value的支持** HashMap 中null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null直接抛出 NullPointerException。 3. **对Null key 和Null value的支持** HashMap 中null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 Hashtable 中 put 进的键值只要有一个 null直接抛出 NullPointerException。
4. **初始容量大小和每次扩充容量大小的不同 ** ①创建时如果不指定容量初始值Hashtable 默认的初始大小为11之后每次扩充容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充容量变为原来的2倍。②创建时如果给定了容量初始值那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 4. **初始容量大小和每次扩充容量大小的不同 ** ①创建时如果不指定容量初始值Hashtable 默认的初始大小为11之后每次扩充容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充容量变为原来的2倍。②创建时如果给定了容量初始值那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。 5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。
@ -749,13 +749,13 @@ public class test1 {
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; - **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- **实现线程安全的方式(重要):****在JDK1.7的时候ConcurrentHashMap分段锁** 对整个桶数组进行了分割分段(Segment)每一把锁只锁容器其中一部分数据多线程访问容器里不同数据段的数据就不会存在锁竞争提高并发访问率。默认分配16个Segment比Hashtable效率提高16倍。 **到了 JDK1.8 的时候已经摒弃了Segment的概念而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。JDK1.6以后 对 synchronized锁做了很多优化** 整个看起来就像是优化过且线程安全的 HashMap虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。 - **实现线程安全的方式(重要):****在JDK1.7的时候ConcurrentHashMap分段锁** 对整个桶数组进行了分割分段(Segment)每一把锁只锁容器其中一部分数据多线程访问容器里不同数据段的数据就不会存在锁竞争提高并发访问率。默认分配16个Segment比Hashtable效率提高16倍。 **到了 JDK1.8 的时候已经摒弃了Segment的概念而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。JDK1.6以后 对 synchronized锁做了很多优化** 整个看起来就像是优化过且线程安全的 HashMap虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** : s使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。
**两者的对比图:** **两者的对比图:**
图片来源http://www.cnblogs.com/chengxiao/p/6842045.html 图片来源http://www.cnblogs.com/chengxiao/p/6842045.html
HashTable: Hashtable
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg) ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg)
JDK1.7的ConcurrentHashMap JDK1.7的ConcurrentHashMap
@ -772,7 +772,7 @@ Node: 链表节点):
**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 **ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁扮演锁的角色。HashEntry 用于存储键值对数据。 Segment 实现了 ReentrantLock所以 Segment 是一种可重入锁扮演锁的角色。HashEntry 用于存储键值对数据。
```java ```java
static class Segment<K,V> extends ReentrantLock implements Serializable { static class Segment<K,V> extends ReentrantLock implements Serializable {

View File

@ -249,7 +249,7 @@ Java 程序在执行子类的构造方法之前,如果没有用 super() 来调
4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。
5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象。从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。 5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象。从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
备注:在JDK8中接口也可以定义静态方法可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口接口中定义了一样的默认方法则必须重写不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)) 备注在JDK8中接口也可以定义静态方法可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口接口中定义了一样的默认方法则必须重写不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146))
## 18. 成员变量与局部变量的区别有那些? ## 18. 成员变量与局部变量的区别有那些?

View File

@ -18,9 +18,9 @@ synchronized关键字解决的是多个线程之间访问资源的同步性sy
- **修饰静态方法:** :也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源不管new了多少个对象只有一份。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 - **修饰静态方法:** :也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源不管new了多少个对象只有一份。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
- **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 - **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
**总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到静态方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中字符串常量池具有缓功能! **总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到静态方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中字符串常量池具有缓功能!
下面我一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 下面我一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” 面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
@ -84,7 +84,7 @@ public class SynchronizedDemo {
从上面我们可以看出: 从上面我们可以看出:
**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中synchronized 锁便是通过这种方式获取锁的也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后将锁计数器设为0表明锁被释放。如果获取对象锁失败那当前线程就要阻塞等待直到锁被另外一个线程释放为止。 **synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中synchronized 锁便是通过这种方式获取锁的也是为什么Java中任意对象可以作为锁的原因) 的持有权当计数器为0则可以成功获取获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后将锁计数器设为0表明锁被释放。如果获取对象锁失败那当前线程就要阻塞等待直到锁被另外一个线程释放为止。
**② synchronized 修饰方法的的情况** **② synchronized 修饰方法的的情况**
@ -110,26 +110,26 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、
关于这几种优化的详细信息可以查看:[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd) 关于这几种优化的详细信息可以查看:[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd)
### 1.5 谈谈 synchronized和ReenTrantLock 的区别 ### 1.5 谈谈 synchronized和ReentrantLock 的区别
**① 两者都是可重入锁** **① 两者都是可重入锁**
两者都是可重入锁。“可重入锁”概念是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁此时这个对象锁还没有释放当其再次想要获取这个对象的锁的时候还是可以获取的如果不可锁重入的话就会造成死锁。同一个线程每次获取锁锁的计数器都自增1所以要等到锁的计数器下降为0时才能释放锁。 两者都是可重入锁。“可重入锁”概念是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁此时这个对象锁还没有释放当其再次想要获取这个对象的锁的时候还是可以获取的如果不可锁重入的话就会造成死锁。同一个线程每次获取锁锁的计数器都自增1所以要等到锁的计数器下降为0时才能释放锁。
**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** **② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化但是这些优化都是在虚拟机层面实现的并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化但是这些优化都是在虚拟机层面实现的并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
**③ ReenTrantLock 比 synchronized 增加了一些高级功能** **③ ReentrantLock 比 synchronized 增加了一些高级功能**
相比synchronizedReenTrantLock增加了一些高级功能。主要来说主要有三点**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** 相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - **ReentrantLock提供了一种能够中断等待锁的线程的机制**通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 - **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类当然也可以实现但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的它具有很好的灵活性比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例即对象监视器**线程对象可以注册在指定的Condition中从而可以有选择性的进行线程通知在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的用ReentrantLock类结合Condition实例可以实现“选择性通知”** 这个功能非常重要而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 - synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类当然也可以实现但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的它具有很好的灵活性比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例即对象监视器**线程对象可以注册在指定的Condition中从而可以有选择性的进行线程通知在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的用ReentrantLock类结合Condition实例可以实现“选择性通知”** 这个功能非常重要而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
如果你想使用上述功能那么选择ReenTrantLock是一个不错的选择。 如果你想使用上述功能那么选择ReentrantLock是一个不错的选择。
**④ 性能已不是选择标准** **④ 性能已不是选择标准**
@ -183,7 +183,7 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象通过这个future对象可以判断任务是否执行成功**并且可以通过future的get()方法来获取返回值get()方法会阻塞当前线程直到任务完成,而使用 `getlong timeoutTimeUnit unit`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 2)**`submit()` 方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象通过这个Future对象可以判断任务是否执行成功**并且可以通过future的get()方法来获取返回值get()方法会阻塞当前线程直到任务完成,而使用 `getlong timeoutTimeUnit unit`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
### 3.4 如何创建线程池 ### 3.4 如何创建线程池
@ -192,7 +192,7 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
> Executors 返回线程池对象的弊端如下: > Executors 返回线程池对象的弊端如下:
> >
> - **FixedThreadPool 和 SingleThreadExecutor** 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致OOM。 > - **FixedThreadPool 和 SingleThreadExecutor** 允许请求的队列长度为 Integer.MAX_VALUE 可能堆积大量的请求从而导致OOM。
> - **CachedThreadPool 和 ScheduledThreadPool** 允许创建的线程数量为 Integer.MAX_VALUE 可能会创建大量线程从而导致OOM。 > - **CachedThreadPool 和 ScheduledThreadPool** 允许创建的线程数量为 Integer.MAX_VALUE 可能会创建大量线程从而导致OOM。
**方式一:通过构造方法实现** **方式一:通过构造方法实现**
@ -248,7 +248,7 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
**对象的属性修改类型** **对象的属性修改类型**
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 - AtomicIntegerFieldUpdater原子更新整形字段的更新器
- AtomicLongFieldUpdater原子更新长整形字段的更新器 - AtomicLongFieldUpdater原子更新长整形字段的更新器
- AtomicStampedReference原子更新带有版本号的引用类型。该类将整数值与引用关联起来可用于解决原子的更新数据和数据的版本号可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 - AtomicStampedReference原子更新带有版本号的引用类型。该类将整数值与引用关联起来可用于解决原子的更新数据和数据的版本号可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
@ -349,7 +349,7 @@ AQS使用一个int成员变量来表示同步状态通过内置的FIFO队列
private volatile int state;//共享变量使用volatile修饰保证线程可见性 private volatile int state;//共享变量使用volatile修饰保证线程可见性
``` ```
状态信息通过procted类型的getStatesetStatecompareAndSetState进行操作 状态信息通过protected类型的getStatesetStatecompareAndSetState进行操作
```java ```java
@ -374,7 +374,7 @@ protected final boolean compareAndSetState(int expect, int update) {
- **Exclusive**独占只有一个线程能执行如ReentrantLock。又可分为公平锁和非公平锁 - **Exclusive**独占只有一个线程能执行如ReentrantLock。又可分为公平锁和非公平锁
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
- **Share**共享多个线程可同时执行如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 - **Share**共享多个线程可同时执行如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
ReentrantReadWriteLock 可以看成是组合式因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 ReentrantReadWriteLock 可以看成是组合式因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
@ -417,9 +417,9 @@ tryReleaseShared(int)//共享方式。尝试释放资源成功则返回true
- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源。 - **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源。
- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类用来协调多个线程之间的同步。这个工具通常用来控制线程等待它可以让某一个线程等待直到倒计时结束再开始执行。 - **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类用来协调多个线程之间的同步。这个工具通常用来控制线程等待它可以让某一个线程等待直到倒计时结束再开始执行。
- **CyclicBarrier(循环栅栏)** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - **CyclicBarrier(循环栅栏)** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) 关于AQS这部分的更多内容可以查看我的这篇文章[并发编程面试必备AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)
# Reference # Reference

View File

@ -256,10 +256,10 @@ synchronized只锁定当前链表或红黑二叉树的首节点这样只要ha
- **TreeSet有序唯一** 红黑树(自平衡的排序二叉树。) - **TreeSet有序唯一** 红黑树(自平衡的排序二叉树。)
### Map ### Map
- **HashMap** JDK1.8之前HashMap由数组+链表组成的数组是HashMap的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突.JDK1.8以后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间 - **HashMap** JDK1.8之前HashMap由数组+链表组成的数组是HashMap的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突JDK1.8以后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间
- **LinkedHashMap:** LinkedHashMap 继承自 HashMap所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析JDK1.8)》](https://www.imooc.com/article/22931) - **LinkedHashMap** LinkedHashMap 继承自 HashMap所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析JDK1.8)》](https://www.imooc.com/article/22931)
- **HashTable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 - **Hashtable** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- **TreeMap:** 红黑树(自平衡的排序二叉树) - **TreeMap** 红黑树(自平衡的排序二叉树)