6. 什么是坎卡伦齐?
并发是 Java 中的一个类库,其中包含针对跨多个线程工作而优化的特殊类。这些类被收集在一个包中java.util.concurrent
。它们可以根据功能进行如下示意性划分: 并发集合- 一组在多线程环境中比java.util
包中的标准通用集合更有效地工作的集合。不是Collections.synchronizedList
使用阻塞访问整个集合的基本包装器,而是使用数据段上的锁,或者使用无等待算法对数据的并行读取进行优化。 队列- 具有多线程支持的非阻塞和阻塞队列。非阻塞队列旨在提高速度并在不阻塞线程的情况下进行操作。当某些条件不满足时,您需要“减慢”“生产者”或“消费者”线程的速度,例如队列为空或溢出,或者没有空闲的“消费者”,则使用阻塞队列。 同步器是用于同步线程的辅助实用程序。它们是“并行”计算中的强大武器。 Executors - 包含用于创建线程池、调度异步任务和获取结果的优秀框架。 锁- 代表与基本同步机制 synchronized
相比的替代且更灵活的线程同步机制。原子- 支持对基元和引用进行原子操作的类。 来源:wait
notify
notifyAll
7. 你知道 Kankarensi 的哪些课程?
这个问题的答案在这篇文章中得到了完美的阐述。我不认为在这里重印所有内容有什么意义,因此我将只描述那些我有幸简要熟悉的类。 ConcurrentHashMap<K, V> -与 和上的Hashtable
块不同,数据以段的形式呈现,分为键的哈希值。因此,数据是按段访问的,而不是按单个对象访问的。另外,迭代器代表特定时间段的数据,不会抛出异常。 AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerArray、AtomicLongArray - 如果在类中您需要同步访问一个简单类型变量怎么办?您可以将构造与, 一起使用,并且在使用原子操作,时使用。但是您可以通过使用新类做得更好。由于使用了 CAS,这些类的操作比通过. 另外,还有按给定数量进行原子加法以及递增/递减的方法。 synhronized
HashMap
ConcurrentModificationException
int
synchronized
set/get
volatile
Atomic*
synchronized/volatile
8. ConcurrentHashMap 类如何工作?
在其引入时,ConcurrentHashMap
Java 开发人员需要以下哈希映射实现:
- 线程安全
- 访问整个表时没有锁
- 执行读操作时最好没有表锁
ConcurrentHashMap
如下:
-
地图元素
与元素不同
HashMap
,Entry
inConcurrentHashMap
被声明为volatile
。这是一个重要的特性,也是由于JMM的变化所致。static final class HashEntry<K, V> { final K key; final int hash; volatile V value; final HashEntry<K, V> next; HashEntry(K key, int hash, HashEntry<K, V> next, V value) { this .key = key; this .hash = hash; this .next = next; this .value = value; } @SuppressWarnings("unchecked") static final <K, V> HashEntry<K, V>[] newArray(int i) { return new HashEntry[i]; } }
-
哈希函数
ConcurrentHashMap
还使用了改进的散列函数。HashMap
让我提醒一下JDK 1.2中的情况:static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
ConcurrentHashMap JDK 1.5 版本:
private static int hash(int h) { h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
为什么需要使哈希函数变得更复杂?哈希映射中的表的长度由 2 的幂决定。对于二进制表示在低位和高位上没有区别的哈希码,我们将会发生冲突。增加哈希函数的复杂度正好解决了这个问题,减少了映射中发生冲突的可能性。
-
段
该映射被分为N个不同的段(默认为16个,最大值可以是16位并且是2的幂)。每个段都是一个线程安全的映射元素表。增加段的数量将鼓励修改操作跨越多个段,从而降低运行时阻塞的可能性。
-
并发级别
该参数影响存储卡的使用情况和卡中的段数。
段的数量将被选择为大于 concurrencyLevel 的最接近的 2 的幂。降低 concurrencyLevel 会使线程在写入时更有可能阻塞映射段。高估该指标会导致内存使用效率低下。如果只有一个线程会修改map,其余线程都会读取,建议使用值1。
-
全部的
那么,主要优点和实现特点
ConcurrentHashMap
:- 地图有一个类似的
hashmap
交互界面 - 读操作不需要锁并且并行执行
- 写操作通常也可以并行执行而不会阻塞
- 创建时注明需要的
concurrencyLevel
,通过读写统计来确定 - 地图元素的值
value
声明为volatile
- 地图有一个类似的
9. 什么是Lock类?
为了控制对共享资源的访问,我们可以使用锁作为同步运算符的替代方案。锁定功能封装在java.util.concurrent.locks
. 首先,线程尝试访问共享资源。如果它是空闲的,则在该线程上放置一个锁。一旦工作完成,共享资源上的锁就会被释放。如果资源未释放并且已在其上放置了锁,则线程将等待直到该锁被释放。锁类实现一个Lock
定义以下方法的接口:
void lock():
等待直到获取锁boolean tryLock():
尝试获取锁;如果获取锁,则返回true。如果未获取锁,则返回false。与该方法不同的是,lock()
如果锁不可用,它不会等待获取锁void unlock():
移除锁Condition newCondition():
Condition
返回与当前锁关联的对象
lock()
,在完成共享资源的操作后,调用该方法unlock()
,从而释放锁。该对象Condition
允许您管理阻塞。通常,要使用锁,需要使用ReentrantLock
包中的一个类。java.util.concurrent.locks.
该类实现了 接口Lock
。让我们以一个小程序为例来看看如何使用 Java Lock API: 那么,假设我们有一个类,它Resource
有几个线程安全的方法和不需要线程安全的方法。
public class Resource {
public void doSomething(){
// пусть здесь происходит работа с базой данных
}
public void doLogging(){
// потокобезопасность для логгирования нам не требуется
}
}
现在让我们看一个实现接口Runnable
并使用类方法的类Resource
。
public class SynchronizedLockExample implements Runnable{
// экземпляр класса Resource для работы с методами
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
现在让我们使用 Lock API 而不是synchronized
.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
// лочим на 10 секунд
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//убираем лок
lock.unlock();
}
// Для логгирования не требуется потокобезопасность
resource.doLogging();
}
}
从程序中可以看到,我们使用该方法tryLock()
来确保线程只等待一定的时间。如果它没有获得对象的锁,它只会记录并退出。另一个重要的一点。必须使用块try-finally
来确保即使方法doSomething()
抛出异常也能释放锁。 资料来源:
11.什么是互斥体?
互斥体是用于同步线程/进程的特殊对象。它可以有两种状态——忙碌和空闲。简单来说,互斥体是一个布尔变量,它有两个值:busy (true) 和 free (false)。当一个线程想要一个对象的独占所有权时,它将其互斥体标记为忙碌,当它完成对对象的处理时,它将其互斥体标记为空闲。Java 中的每个对象都附加了一个互斥体。只有 Java 机器可以直接访问互斥体。它对程序员是隐藏的。12.什么是显示器?
监视器是一种特殊的机制(一段代码) - 互斥锁上的附加组件,可确保其正确操作。毕竟,仅仅标记对象忙碌是不够的;我们还必须确保其他线程不会尝试使用忙碌对象。在Java中,监视器是使用关键字来实现的synchronized
。当我们编写同步块时,Java编译器将其替换为三段代码:
- 在块的开头
synchronized
,添加了将互斥体标记为忙的代码。 - 在块的末尾,
synchronized
添加了一个代码,将互斥锁标记为空闲。 - 在该块之前,
synchronized
添加代码来检查互斥锁是否繁忙,然后线程必须等待它被释放。
GO TO FULL VERSION