Java_day25-29
Java_简答
- day 25
- 1 HashMap 为什么是线程不安全的? 如何实现线程安全
- 2concurrentHashMap 如何保证线程安全
- 3HashMap和ConcurrentHashMap的区别
- day 26
- 1 HashSet 和 HashMap 的区别
- 2HashMap 和 HashTable 的区别
- day 27
- 1Java 创建线程有哪几种方式?
- 2线程start和run的区别
- day 28
- 1你知道Java中有哪些锁吗
- 2说一说你对 synchronized 的理解
- day 29
- 1synchronized和lock的区别是什么
- 2synchronized和ReentrantLock的区别是什么
day 25
1 HashMap 为什么是线程不安全的? 如何实现线程安全
(1) 为什么是线程不安全的
主要原因是它的操作不是原子的,即在多个线程同时进行读写操作时,可能会导致数据不一致性或抛出异常.
**并发修改:**当一个线程进行写操作(插入、删除等)时,另一个线程进行读操作,可能会导致读取到不一致的数据,甚至抛出 ConcurrentModificationException 异常。
非原子性操作:HashMap 的一些操作不是原子的,例如,检查是否存在某个键、获取某个键对应的值等,这样在多线程环境中可能发生竞态条件。
(2)如何实现线程安全
为了实现线程安全的 HashMap,有以下几种方式:
使用Collections.synchronizedMap()方法:可以通过 Collections.synchronizedMap() 方法创建一个线程安全的 HashMap,该方法返回一个同步的 Map 包装器,使得所有对 Map 的操作都是同步的。
使用ConcurrentHashMap:ConcurrentHashMap 是专门设计用于多线程环境的哈希表实现。它使用分段锁机制,允许多个线程同时进行读操作,提高并发性能。
使用锁机制:可以在自定义的 HashMap 操作中使用显式的锁(例如 ReentrantLock)来保证线程安全。
2concurrentHashMap 如何保证线程安全
ConcurrentHashMap 在JDK 1.7中使用的数组 加 链表的结构,其中数组分为两类,大树组 Segment 和 小数组 HashEntry,ConcurrentHashMap 的线程安全是建立在 Segment 加 ReentrantLock 重入锁来保证
ConcurrentHashMap 在JDK1.8中使用的是数组 加 链表 加 红黑树的方式实现,它是通过 CAS 或者 synchronized 来保证线程安全的,并且缩小了锁的粒度,查询性能也更高。
3HashMap和ConcurrentHashMap的区别
线程安全性:
HashMap 不是线程安全的。在多线程环境中,如果同时进行读写操作,可能会导致数据不一致或抛出异常。
ConcurrentHashMap 是线程安全的,它使用了分段锁(Segment Locking)的机制,将整个数据结构分成多个段(Segment),每个段都有自己的锁。这样,不同的线程可以同时访问不同的段,提高并发性能。
同步机制:
HashMap 在实现上没有明确的同步机制,需要在外部进行同步,例如通过使用 Collections.synchronizedMap() 方法。
ConcurrentHashMap 内部使用了一种更细粒度的锁机制,因此在多线程环境中具有更好的性能。
迭代时是否需要加锁:
在 HashMap 中,如果在迭代过程中有其他线程对其进行修改,可能抛出 ConcurrentModificationException 异常。
ConcurrentHashMap 允许在迭代时进行并发的插入和删除操作,而不会抛出异常。但是,它并不保证迭代器的顺序,因为不同的段可能会以不同的顺序完成操作。
初始化容量和负载因子:
HashMap 可以通过构造方法设置初始容量和负载因子。
ConcurrentHashMap 在Java 8及之后版本中引入了ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)构造方法,允许设置初始容量、负载因子和并发级别。
性能:
在低并发情况下,HashMap 的性能可能会比 ConcurrentHashMap 稍好,因为 ConcurrentHashMap 需要维护额外的并发控制。
在高并发情况下,ConcurrentHashMap 的性能通常更好,因为它能够更有效地支持并发访问。
总的来说,如果需要在多线程环境中使用哈希表,而且需要高性能的并发访问,通常会选择使用 ConcurrentHashMap。如果在单线程环境中使用,或者能够手动进行外部同步管理,那么 HashMap 可能是更简单的选择。
day 26
1 HashSet 和 HashMap 的区别
HashMap 适用于需要存储键值对的情况,而 HashSet 适用于只关心元素唯一性的情况。在某些情况下,可以使用 HashMap 来模拟 HashSet 的行为,只使用键而将值设为固定的常量。
使用
HashMap 用于存储键值对,其中每个键都唯一,每个键关联一个值。
HashSet 用于存储唯一的元素,不允许重复。
内部实现:
HashMap 使用键值对的方式存储数据,通过哈希表实现。
HashSet 实际上是基于 HashMap 实现的,它只使用了 HashMap 的键部分,将值部分设置为一个固定的常量。
元素类型:
HashMap 存储键值对,可以通过键获取对应的值。
HashSet 存储单一元素,只能通过元素本身进行操作。
允许 null:
HashMap 允许键和值都为 null。
HashSet 允许存储一个 null 元素。
迭代方式:
HashMap 的迭代是通过迭代器或增强型 for 循环遍历键值对。
HashSet 的迭代是通过迭代器或增强型 for 循环遍历元素。
关联关系:
HashMap 中的键与值是一一对应的关系。
HashSet 中的元素没有关联的值,只有元素本身。
性能影响:
HashMap 的性能受到键的哈希分布和哈希冲突的影响。
HashSet 的性能也受到元素的哈希分布和哈希冲突的影响,但由于它只存储键,通常比 HashMap 的性能稍好。
2HashMap 和 HashTable 的区别
同步
Hashtable 是同步的,即它的方法是线程安全的。这是通过在每个方法上添加同步关键字来实现的,但这也可能导致性能下降。
HashMap 不是同步的,因此它不保证在多线程环境中的线程安全性。如果需要同步,可以使用 Collections.synchronizedMap() 方法来创建一个同步的 HashMap。
性能
由于 Hashtable 是同步的,它在多线程环境中的性能可能较差。
HashMap 在单线程环境中可能比 Hashtable 更快,因为它没有同步开销。
空值
Hashtable 不允许键或值为 null。
HashMap 允许键和值都为 null。
继承关系
Hashtable 是 Dictionary 类的子类,而 HashMap 是 AbstractMap 类的子类,实现了 Map 接口。
迭代器
Hashtable 的迭代器是通过 Enumerator 实现的。
HashMap 的迭代器是通过 Iterator 实现的。
初始容量和加载因子
Hashtable 的初始容量和加载因子是固定的。
HashMap 允许通过构造方法设置初始容量和加载因子,以便更好地调整性能。
day 27
1Java 创建线程有哪几种方式?
在 Java 中,创建线程有四种方式,分别是 继承Thread类,实现Runnable接口, 使用Callable和Future, 使用线程池.
继承Thread类: 通过创建Thread类的子类,并重写其run方法来定义线程执行的任务。
实现Runnable接口: 创建一个实现了Runnable接口的类,并实现其run方法。然后创建该类的实例,并将其作为参数传递给Thread 对象。
使用Callable和Future接口:创建一个实现了Callable接口的类,并实现其call方法,该方法可以返回结果并抛出异常。使用ExecutorService来管理线程池,并提交Callable任务获取Future对象,以便在未来某个时刻获取Callable任务的计算结果。
使用线程池:通过使用Executors类创建线程池,并通过线程池来管理线程的创建和复用。
2线程start和run的区别
在Java多线程中,run 方法和 start 方法的区别在于:
run 方法是线程的执行体,包含线程要执行的代码,当直接调用 run 方法时,它会在当前线程的上下文中执行,而不会创建新的线程。
start 方法用于启动一个新的线程,并在新线程中执行 run 方法的代码。调用 start 方法会为线程分配系统资源,并将线程置于就绪状态,当调度器选择该线程时,会执行 run 方法中的代码。
因此,虽然可以直接调用 run 方法,但这并不会创建一个新的线程,而是在当前线程中执行 run 方法的代码。如果需要实现多线程执行,则应该调用 start 方法来启动新线程。
day 28
1你知道Java中有哪些锁吗
公平锁/非公平锁:公平锁指多个线程按照申请锁的顺序来获取锁,非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优 先获取锁。有可能,会造成优先级反转或者饥饿现象。 对于 Java ReentrantLock 而言,默认是非公平锁,对于 Synchronized 而言,也是一种非公平锁。
可重入锁(递归锁):在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。对于 Java ReentrantLock 而言,是可重入锁,对于 Synchronized 而言,也是一个可重入锁。
独享锁/共享锁:独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。对于 Java ReentrantLock 而言,其是独享锁。但是对于Lock的另一个实现类 ReadWriteLock,其读锁 是共享锁,其写锁是独享锁。 对于 Synchronized 而言,当然是独享锁。
互斥锁/读写锁:上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。 互斥锁在Java中的具体实现就是 ReentrantLock。读写锁在Java中的具体实现就是 ReadWriteLock
乐观锁/悲观锁:乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加 锁会带来大量的性能提升。
分段锁:分段锁其实是一种锁的设计,并不是具体的一种锁,对于 ConcurrentHashMap 而言,其并发的实现就 是通过分段锁的形式来实现高效的并发操作。
偏向锁/轻量级锁/重量级锁:这三种锁是指锁的状态,并且是针对 Synchronized 。在Java 5通过引入锁升级的机制来实现高效 Synchronized 。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
自选锁:在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好 处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
2说一说你对 synchronized 的理解
synchronized是Java中的一个关键字,用于实现同步和线程安全。
当一个方法或代码块被 synchronized 修饰时,它将成为一个临界区,同一时刻只能由一个线程访问。其他线程必须等待当前线程退出临界区才能进入。确保多个线程在访问共享资源时不会产生冲突
synchronized 可以应用于方法或代码块。当它应用于方法时,整个方法被锁定;当它应用于代码块时,只有该代码块被锁定。这样做的好处是,可以选择性地锁定对象的一部分,而不是整个方法。
synchronized 实现的机理依赖于软件层面上的JVM,因此其性能会随着Java版本的不断升级而提高。 到了 Java1.6,synchronized 进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的 Java1.7 与 1.8 中,均对该关键字的实现机理做了优化。 需要说明的是,当线程通过 synchronized 等待锁时是不能被 Thread.interrupt() 中断的,因此程序设计时必须检查确保合理,否则可能会造成线程死锁的尴尬境地。
最后,尽管 Java 实现的锁机制有很多种,并且有些锁机制性能也比 synchronized 高,但还是强烈推荐在 多线程应用程序中使用该关键字,因为实现方便,后续工作由 JVM 来完成,可靠性高。只有在确定锁机 制是当前多线程程序的性能瓶颈时,才考虑使用其他机制,如 ReentrantLock 等。
day 29
1synchronized和lock的区别是什么
(1) synchronized和lock的区别是什么
synchronized和Lock都是Java中用于实现线程同步的手段,synchronized是Java的关键字,基于JVM的内置锁实现,可以用于修饰方法或代码块,使用起来比较简单直接。而Lock是一个接口,是Java提供的显式锁机制,需要手动获取和释放锁,通过实现类(如ReentrantLock)来创建锁对象,然后主动调用锁的获取和释放方法。
特性
synchronized:灵活性相对较低,只能用于方法或代码块。而且synchronized方法一旦开始执行,即使线程被阻塞,也不能中断。没有超时机制,一旦获取不到锁就会一直等待,也没有公平性的概念,线程调度由JVM控制。
lock:提供了更多的灵活性,例如可以尝试获取锁,如果锁已被其他线程持有,可以选择等待或者中断等待。提供了超时获取锁的能力,可以在指定时间内尝试获取锁,也可以设置为公平锁,按照请求锁的顺序来获取锁。
等待与通知:
synchronized:与 wait() 和 notify()/notifyAll() 方法一起使用,用于线程的等待和通知。
lock:可以与 Condition 接口结合,实现更细粒度的线程等待和通知机制。
使用场景:
总结来说,synchronized使用简单,适合锁的粒度较小、竞争不激烈、实现简单的场景。而Lock提供了更多的灵活性和控制能力,适用于需要更复杂同步控制的场景。
2synchronized和ReentrantLock的区别是什么
synchronized和ReentrantLock都是Java中用于实现线程同步的手段,synchronized是Java的关键字,基于JVM的内置锁实现,可以用于修饰方法或代码块,使用起来比较简单直接。而ReentrantLock是java.util.concurrent.locks包中的一个锁实现,需要显式创建,并通过调用lock()和unlock()方法来管理锁的获取和释放。
特性
synchronized:灵活性相对较低,只能用于方法或代码块。而且synchronized方法一旦开始执行,即使线程被阻塞,也不能中断。没有超时机制,一旦获取不到锁就会一直等待,也没有公平性的概念,线程调度由JVM控制。
ReentrantLock:支持中断操作,可以在等待锁的过程中响应中断, 提供了尝试获取锁的超时机制,可以通过tryLock()方法设置超时时间。可以设置为公平锁,按照请求的顺序来获取锁,提供了isLocked()、isFair()等方法,可以检查锁的状态。
条件变量:
synchronized可以通过wait()、notify()、notifyAll()与对象的监视器方法配合使用来实现条件变量。
ReentrantLock可以通过Condition新API实现更灵活的条件变量控制。
锁绑定多个条件:
synchronized与单个条件关联,需要使用多个方法调用来实现复杂的条件判断。
ReentrantLock可以与多个Condition对象关联,每个对象可以有不同的等待和唤醒逻辑。
使用场景:
总结来说,synchronized适合简单的同步需求,而ReentrantLock提供了更丰富的控制能力和灵活性,适用于需要复杂同步控制的场景。