代码解读复制代码// 创建一个固定大小为 5 的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 向线程池提交 10 个任务
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println("CurrentThread name:" + Thread.currentThread().getName());
});
}
// 关闭线程池
executorService.shutdown();
代码解读复制代码public class Counter {
private int counter;
public synchronized void increment() {
counter++;
}
public int read() {
return counter;
}
}
CAS(Compare And Swap)是一种利用处理器提供的特殊指令来实现对内存地址上的值进行比较和替换的操作12。CAS指令接收三个参数:内存地址(M)、期望值(A)和更新值(B)。它会将内存地址上的当前值与期望值进行比较,如果相等,则将内存地址上的值替换为更新值,并返回true;如果不相等,则不做任何修改,并返回false12。
Java中没有直接实现CAS,而是通过sun.misc.Unsafe类提供了一些底层方法来调用CAS指令34。Unsafe类是一个非常危险的类,它可以直接操作内存地址和数据,绕过Java的安全机制和访问控制。因此,它只能在受信任的代码中使用,一般用户无法直接获取Unsafe类的实例34。
Unsafe类提供了多个方法来执行CAS操作,例如compareAndSwapInt()、compareAndSwapLong()、compareAndSwapObject()等。这些方法都需要传入一个对象、一个偏移量和两个期望值或更新值作为参数,并尝试将对象中偏移量位置上的值从期望值改为更新值。如果成功,则返回true;否则返回false。
例如,在AtomicInteger类中,就使用了Unsafe类提供的compareAndSwapInt()方法来实现原子地增加或减少变量值:Java
代码解读复制代码public class AtomicInteger {
private volatile int value;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final int decrementAndGet() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return next;
}
}
}
在这个例子中,首先使用反射获取value变量在AtomicInteger对象中的偏移量,并保存在valueOffset属性中。然后在compareAndSet()方法中,调用unsafe.compareAndSwapInt(this, valueOffset, expect, update)方法来执行CAS操作。在incrementAndGet()和decrementAndGet()方法中,使用循环不断地尝试获取当前值、计算新值并调用compareAndSet()方法来更新变量值。
可重入锁是一种锁,它允许一个线程多次获取同一个锁,而不会造成死锁。每次获取锁时,锁的持有计数会加一,每次释放锁时,持有计数会减一。当持有计数为零时,锁被完全释放。可重入锁可以实现线程对共享资源的独占访问,同时也支持线程在同一个方法或者不同方法中递归地获取同一个锁。
Java中提供了ReentrantLock类来实现可重入的互斥锁24。ReentrantLock类实现了Lock接口,并提供了一些扩展功能,比如公平性、可中断性、条件变量等。使用ReentrantLock类的一般步骤如下:
创建一个ReentrantLock实例,根据需要选择不同的构造参数在访问共享资源之前,调用lock()方法获取锁,如果锁不可用,则线程会阻塞直到获取到锁在try-finally块中访问共享资源,并在finally块中调用unlock()方法释放锁根据需要使用其他ReentrantLock提供的方法和特性例如:Java
代码解读复制代码import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++; // 访问共享资源
} finally {
lock.unlock(); // 释放锁
}
}
public int read() {
return count;
}
}
这个例子中,使用了ReentrantLock作为锁对象,它是一个可重入的互斥锁。在increment()方法中,在对count变量进行加一操作之前,先调用lock.lock()方法获取锁。如果其他线程已经持有了这个锁,则当前线程会阻塞直到获得这个锁。然后在try-finally块中访问count变量,并在finally块中调用lock.unlock()方法释放锁。这样可以保证只有一个线程能够修改count的值。
代码解读复制代码// 导入相关类
import java.util.concurrent.locks.ReentrantReadWriteLock;
// 创建一个ReentrantReadWriteLock对象
ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
// 获取读锁和写锁对象
ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
// 定义一个方法用于读操作
public static void read() {
// 尝试获取读锁
readLock.lock();
try {
// 执行读操作,打印线程名和信息
System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
// 模拟耗时操作,睡眠1秒
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放读锁,并打印线程名和信息
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放读锁");
}
}
// 定义一个方法用于写操作
public static void write() {
// 尝试获取写锁
writeLock.lock();
try {
// 执行写操作,打印线程名和信息
System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
// 模拟耗时操作,睡眠1秒
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放写锁,并打印线程名和信息
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
}
}
// 在main方法中创建四个线程分别进行读和写操作,并启动它们
public static void main(String[] args) {
new Thread(() -> read(), "Thread1").start();
new Thread(() -> read(), "Thread2").start();
new Thread(() -> write(), "Thread3").start();
new Thread(() -> write(), "Thread4").start();
}
输出结果如下,可以看到线程1和线程2可以同时获取读锁,而线程3和线程4只能依次获取写锁,因为线程4必须等待线程3释放写锁后才能获取到锁: 代码解读
复制代码Thread1获取读锁,开始执行
Thread2获取读锁,开始执行
Thread1释放读锁
Thread2释放读锁
Thread3获取写锁,开始执行
Thread3释放写锁
Thread4获取写锁,开始执行
Thread4释放写锁
`