import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockExample {
private static AtomicInteger version = new AtomicInteger(0);
private static int sharedData = 0;
public static void main(String[] args) {
// 创建两个线程并启动
Thread thread1 = new Thread(() -> {
int currentVersion = version.get(); // 读取当前版本号
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 检查版本号是否仍为之前读取的版本号
if (version.compareAndSet(currentVersion, currentVersion + 1)) {
sharedData = newValue; // 提交修改
System.out.println("Thread 1: Shared data updated to " + sharedData);
} else {
System.out.println("Thread 1: Failed to update shared data due to concurrent modification");
}
});
Thread thread2 = new Thread(() -> {
int currentVersion = version.get(); // 读取当前版本号
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 检查版本号是否仍为之前读取的版本号
if (version.compareAndSet(currentVersion, currentVersion + 1)) {
sharedData = newValue; // 提交修改
System.out.println("Thread 2: Shared data updated to " + sharedData);
} else {
System.out.println("Thread 2: Failed to update shared data due to concurrent modification");
}
});
thread1.start();
thread2.start();
}
}
这个例子中,我们使用了 AtomicInteger 类的 compareAndSet() 方法来实现乐观锁。首先,我们定义了一个版本号 version
用于追踪共享数据的变化。然后,我们创建了两个线程,每个线程都读取当前版本号并对共享数据执行修改操作。在提交修改之前,线程会再次检查当前版本号是否仍为之前读取的版本号,如果是,则提交修改成功;否则,说明数据已被其他线程修改,需要进行相应处理。
import java.util.concurrent.locks.ReentrantLock;
public class PessimisticLockExample {
private static int sharedData = 0;
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// 创建两个线程并启动
Thread thread1 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 1: Shared data updated to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
});
Thread thread2 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 2: Shared data updated to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,我们使用了 ReentrantLock
类来实现悲观锁。首先,我们创建了一个名为 lock
的 ReentrantLock
对象来保护共享数据。然后,我们创建了两个线程,在涉及到共享数据的代码块中分别调用 lock()
方法获取锁,并在修改共享数据后调用 unlock()
方法释放锁。
在这个示例中,使用悲观锁的方式是通过显式地获取和释放锁来实现的。当一个线程获取到锁时,其他线程会被阻塞,直到锁被释放。这样可以确保同一时间只有一个线程能够访问共享资源,保证了数据的一致性和线程安全性。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static int sharedData = 0;
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// 创建两个线程并启动
Thread thread1 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 1: Shared data updated to " + sharedData);
updateSharedData(); // 调用可重入方法
} finally {
lock.unlock(); // 释放锁
}
});
Thread thread2 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 2: Shared data updated to " + sharedData);
updateSharedData(); // 调用可重入方法
} finally {
lock.unlock(); // 释放锁
}
});
thread1.start();
thread2.start();
}
private static void updateSharedData() {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Shared data updated inside the reentrant method to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
}
}
在这个示例中,我们使用了 ReentrantLock
类来实现可重入锁。首先,我们创建了一个名为 lock
的 ReentrantLock
对象来保护共享数据。然后,我们创建了两个线程,在涉及到共享数据的代码块中分别调用 lock()
方法获取锁,并在修改共享数据后调用 unlock()
方法释放锁。
值得注意的是,可重入锁允许同一个线程多次获取锁。在示例中,当线程1获取到锁后,在修改共享数据期间又调用了 updateSharedData()
方法,该方法中也需要获取锁。由于可重入锁的特性,线程1可以再次获取锁,而不会造成死锁。
可重入锁在Java中有多种实现方式,其中最常见的是 ReentrantLock
类。可重入锁提供了一种灵活且强大的机制,用于管理并保护共享资源的访问。
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private static int sharedData = 0;
private static ReentrantLock lock = new ReentrantLock(true); // 创建公平锁
public static void main(String[] args) {
// 创建两个线程并启动
Thread thread1 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 1: Shared data updated to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
});
Thread thread2 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 2: Shared data updated to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,我们使用了 ReentrantLock
类来实现公平锁。通过在创建 ReentrantLock
对象时传递参数 true
,我们创建了一个公平锁,即等待时间最长的线程会最先获取到锁。
在公平锁中,当多个线程竞争同一个锁时,锁会按照线程等待的顺序分配给它们。这可以确保较早等待的线程优先获得锁,避免了饥饿情况的发生,即某些线程一直无法获得锁。
需要注意的是,公平锁可能会牺牲一定的性能,因为它需要维护一个队列来管理等待的线程。因此,当性能要求较高且没有特殊需求时,可以使用非公平锁。
在实际开发中,公平锁的选择应根据具体的业务需求和性能要求综合考虑。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexLockExample {
private static int sharedData = 0;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 创建两个线程并启动
Thread thread1 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 1: Shared data updated to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
});
Thread thread2 = new Thread(() -> {
lock.lock(); // 获取锁
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 2: Shared data updated to " + sharedData);
} finally {
lock.unlock(); // 释放锁
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,我们使用了 ReentrantLock
类来实现互斥锁。通过创建一个 ReentrantLock
对象,我们获得了一个可重入锁。这意味着同一个线程可以多次获取同一个锁而不会发生死锁。
在示例中,当一个线程获取到锁后,其他线程将被阻塞直到锁被释放。这样确保了同时只有一个线程能够修改共享数据,从而避免了数据竞争和并发问题。
需要注意的是,在使用互斥锁时,务必在合适的地方调用 unlock()
方法来释放锁,以避免死锁和资源泄漏。
互斥锁是一种常见且有效的保护共享资源的机制,在并发编程中被广泛使用。使用互斥锁可以确保共享数据的一致性和线程安全。
import java.util.concurrent.atomic.AtomicBoolean;
public class SpinLockExample {
private static int sharedData = 0;
private static AtomicBoolean lock = new AtomicBoolean(false);
public static void main(String[] args) {
// 创建两个线程并启动
Thread thread1 = new Thread(() -> {
while (!lock.compareAndSet(false, true)) {
// 自旋等待锁释放
}
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 1: Shared data updated to " + sharedData);
} finally {
lock.set(false); // 释放锁
}
});
Thread thread2 = new Thread(() -> {
while (!lock.compareAndSet(false, true)) {
// 自旋等待锁释放
}
try {
int newValue = sharedData + 1; // 对共享数据进行修改
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sharedData = newValue; // 提交修改
System.out.println("Thread 2: Shared data updated to " + sharedData);
} finally {
lock.set(false); // 释放锁
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,我们使用了 AtomicBoolean
类来实现自旋锁。自旋锁是一种在获取锁时反复检查锁状态的锁机制。
在示例中,每个线程使用 compareAndSet
方法来尝试获取锁。如果锁当前的状态是未锁定(false
),则将其设置为锁定(true
),从而成功获取到锁。如果锁已经被其他线程获取,则会一直进行自旋等待,直到锁被释放。
需要注意的是,在使用自旋锁时,要避免死锁和活锁的情况发生。因此,合理选择自旋次数和自旋等待时间非常重要。过长的自旋时间可能导致性能下降,而过短的自旋时间可能导致过多的线程切换开销。
自旋锁适用于对共享数据的访问时间较短,且竞争不是很激烈的场景。在高并发情况下,自旋锁可能会导致CPU资源的浪费,因此需要根据具体业务场景和性能要求综合考虑是否使用自旋锁。
import java.util.concurrent.CountDownLatch;
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3; // 工作线程数目
CountDownLatch latch = new CountDownLatch(workerCount);
// 创建工作线程并启动
for (int i = 0; i < workerCount; i++) {
Thread thread = new Thread(() -> {
// 模拟工作
System.out.println("Worker thread start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Worker thread finish");
latch.countDown(); // 工作完成,计数减一
});
thread.start();
}
System.out.println("Main thread waiting for workers to finish");
latch.await(); // 主线程等待所有工作线程完成
System.out.println("All workers have finished");
// 继续主线程的后续操作
}
}
在这个示例中,我们使用了 CountDownLatch
类来实现闭锁。闭锁是一种同步工具,它可以使一个或多个线程等待其他线程完成某些操作后再继续执行。
在示例中,主线程首先创建了一个 CountDownLatch
对象,并指定需要等待的工作线程数目为 workerCount
。然后,主线程创建了多个工作线程,并在每个工作线程开始和结束时调用 countDown()
方法,表示工作完成。
主线程在调用 await()
方法后会被阻塞,直到计数器减至零,即所有工作线程都完成了工作。然后,主线程可以继续执行接下来的操作。
闭锁适用于一组线程需要等待某个条件满足后再同时继续执行的场景。通过闭锁,可以更好地控制线程的并发执行。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
int workerCount = 5; // 工作线程数目
Semaphore semaphore = new Semaphore(2); // 信号量,初始许可证数量为2
// 创建工作线程并启动
for (int i = 0; i < workerCount; i++) {
Thread thread = new Thread(() -> {
try {
semaphore.acquire(); // 获取许可证,如果没有可用的许可证,则阻塞等待
System.out.println("Worker thread start");
// 模拟工作
Thread.sleep(1000);
System.out.println("Worker thread finish");
semaphore.release(); // 释放许可证
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
}
}
}
在这个示例中,我们使用了 Semaphore
类来实现信号量。信号量是一种同步工具,它可以控制对某个资源的访问数量。
在示例中,创建了一个初始许可证数量为2的信号量(semaphore
)。然后,创建了多个工作线程并启动。
每个工作线程在开始工作之前调用 acquire()
方法来获取许可证。如果当前有可用的许可证,则线程获取到许可证并继续执行工作。如果当前没有可用的许可证,则线程会阻塞等待,直到有其他线程释放许可证。
工作线程完成工作后调用 release()
方法来释放许可证,使得其他等待的线程可以获取许可证继续执行工作。
通过信号量,我们可以控制同时访问某个资源的线程数量,实现对并发访问的控制和限制。