1、生产者-消费者问题
实现一个生产者-消费者模型,其中生产者线程生成数据并将其放入缓冲区,而消费者线程从缓冲区中取出数据进行处理。你可以使用wait()和notify()方法或者使用BlockingQueue来实现该模型。
例:
public class ProducerConsumer {
private LinkedList<Integer> buffer = new LinkedList<>();
private int capacity = 5;
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (this) {
while (buffer.size() == capacity) {
wait();
}
System.out.println("Producer produced: " + value);
buffer.add(value++);
notify();
Thread.sleep(1000);
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (buffer.isEmpty()) {
wait();
}
int value = buffer.removeFirst();
System.out.println("Consumer consumed: " + value);
notify();
Thread.sleep(1000);
}
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producerThread = new Thread(() -> {
try {
pc.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumerThread = new Thread(() -> {
try {
pc.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producerThread.start();
consumerThread.start();
}
}
在上面的示例中,ProducerConsumer类是生产者-消费者模型的实现。buffer是一个用于存储数据的缓冲区,capacity表示缓冲区的最大容量。
produce()方法是生产者线程的执行逻辑。如果缓冲区已满,生产者线程会等待,直到有消费者取走数据。然后,它生成一个新的数据项,并将其添加到缓冲区中,然后通知等待的消费者线程。
consume()方法是消费者线程的执行逻辑。如果缓冲区为空,消费者线程会等待,直到有生产者放入数据。然后,它从缓冲区中移除第一个数据项,并进行消费,然后通知等待的生产者线程。
在main中,我们创建一个ProducerConsumer对象,并使用两个线程分别执行生产者的produce()方法和消费者的consume()方法。
2、读者-写者问题
实现一个读者-写者模型,其中多个读者线程可以同时读取共享数据,而写者线程在写入数据时必须独占访问。
public class ReaderWriter {
//正在读取的读者数量
private int readers = 0;
//正在写入的写者数量
private int writers = 0;
//正在请求写入的写者数量
private int writerRequests = 0;
public void startRead() throws InterruptedException {
synchronized (this) {
while (writers > 0 || writerRequests > 0) {
wait();
}
readers++;
System.out.println("Reader started, total readers: " + readers);
}
}
public void endRead() throws InterruptedException {
synchronized (this) {
readers--;
System.out.println("Reader ended, total readers: " + readers);
if (readers == 0) {
notifyAll();
}
}
}
public void startWrite() throws InterruptedException {
synchronized (this) {
writerRequests++;
while (readers > 0 || writers > 0) {
wait();
}
writerRequests--;
writers++;
System.out.println("Writer started, total writers: " + writers);
}
}
public void endWrite() throws InterruptedException {
synchronized (this) {
writers--;
System.out.println("Writer ended, total writers: " + writers);
notifyAll();
}
}
public static void main(String[] args) {
ReaderWriter readerWriter = new ReaderWriter();
Thread readerThread1 = new Thread(new Reader(readerWriter));
Thread readerThread2 = new Thread(new Reader(readerWriter));
Thread writerThread1 = new Thread(new Writer(readerWriter));
Thread readerThread3 = new Thread(new Reader(readerWriter));
readerThread1.start();
readerThread2.start();
writerThread1.start();
readerThread3.start();
}
}
class Reader implements Runnable {
private ReaderWriter readerWriter;
public Reader(ReaderWriter readerWriter) {
this.readerWriter = readerWriter;
}
@Override
public void run() {
try {
readerWriter.startRead();
// Perform read operation
Thread.sleep(1000);
readerWriter.endRead();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Writer implements Runnable {
private ReaderWriter readerWriter;
public Writer(ReaderWriter readerWriter) {
this.readerWriter = readerWriter;
}
@Override
public void run() {
try {
readerWriter.startWrite();
// Perform write operation
Thread.sleep(1000);
readerWriter.endWrite();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、哲学家就餐问题
实现哲学家就餐问题,其中有一组哲学家坐在圆桌周围,每个哲学家需要同时拿起左右两边的餐叉才能进餐。你可以使用ReentrantLock和条件变量来实现避免死锁的算法。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Philosopher implements Runnable {
private Lock leftFork;
private Lock rightFork;
public Philosopher(Lock leftFork, Lock rightFork) {
this.leftFork = leftFork;
this.rightFork = rightFork;
}
private void doAction(String action) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " " + action);
Thread.sleep((int) (Math.random() * 100));
}
@Override
public void run() {
try {
while (true) {
doAction("Thinking");
leftFork.lock();
try {
if (rightFork.tryLock()) {
try {
doAction("Eating");
} finally {
rightFork.unlock();
}
}
} finally {
leftFork.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class Main {
public static void main(String[] args) {
int numberOfPhilosophers = 5;
Philosopher[] philosophers = new Philosopher[numberOfPhilosophers];
Lock[] forks = new ReentrantLock[numberOfPhilosophers];
for (int i = 0; i < numberOfPhilosophers; i++) {
forks[i] = new ReentrantLock();
}
for (int i = 0; i < numberOfPhilosophers; i++) {
Lock leftFork = forks[i];
Lock rightFork = forks[(i + 1) % numberOfPhilosophers];
philosophers[i] = new Philosopher(leftFork, rightFork);
Thread thread = new Thread(philosophers[i], "Philosopher " + (i + 1));
thread.start();
}
}
}
在上面的示例中,每个哲学家都是一个线程,并且使用两个锁(左手边的叉子和右手边的叉子)来模拟哲学家就餐的过程。
Philosopher类实现了Runnable接口,并在run()方法中定义了哲学家的行为。每个哲学家在一个无限循环中,交替地进行思考和就餐。当一个哲学家要就餐时,它首先尝试获取左手边的叉子,然后尝试获取右手边的叉子。如果成功获取了两个叉子,它就进行就餐,然后释放两个叉子。
Main类中创建了一定数量的哲学家和叉子,并通过线程启动它们。在这个示例中,有5个哲学家和5个叉子(锁),哲学家们按照顺时针的顺序获取叉子。
请注意,这个示例中的实现使用了ReentrantLock来表示叉子(锁)。这里使用了tryLock()方法来避免死锁。如果一个哲学家不能同时获得两个叉子,它将释放已经获取的叉子,继续思考一会儿,然后再次尝试就餐。
4、线程池
实现一个简单的线程池,可以动态地管理一组线程来执行任务。你可以使用ExecutorService框架来创建和管理线程池。
public class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Task " + name + " started");
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + name + " completed");
}
public static void main(String[] args) {
int numThreads = 3; // 线程池中的线程数量
ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
for (int i = 1; i <= 5; i++) {
String taskName = "Task " + i;
Task task = new Task(taskName);
executorService.execute(task);
}
executorService.shutdown();
}
}
在上面的示例中,我们首先创建一个ExecutorService对象,使用Executors.newFixedThreadPool()方法创建一个固定大小的线程池,其中线程数量为3。
然后,我们创建5个Task对象,每个对象代表一个任务。通过调用executorService.execute(task)将任务提交给线程池执行。
最后,我们调用executorService.shutdown()方法来关闭线程池。
每个任务都是Runnable接口的实现类,通过实现run()方法来定义任务的逻辑。在示例中,每个任务只是简单地输出一些信息,并模拟了2秒的任务执行时间。
使用线程池的好处是,它可以重用线程,避免了频繁地创建和销毁线程的开销。它还提供了任务调度、线程池大小控制等功能。
5、并发集合
在Java中,有几种并发集合类可用于在多线程环境中安全地进行数据访问。这些并发集合提供了线程安全的数据结构,可以支持并发读取和写入操作。以下是几个常用的并发集合类:
-
ConcurrentHashMap: 这是一个线程安全的哈希表实现,可以在多个线程之间共享和修改数据。它提供了高效的并发读取和写入操作,并且不需要对整个集合进行锁定。
-
ConcurrentLinkedQueue: 这是一个线程安全的队列实现,适用于并发的生产者和消费者场景。它支持高效的无锁并发操作,并且具有良好的性能。
-
CopyOnWriteArrayList: 这是一个线程安全的动态数组实现,适用于读多写少的场景。每次修改集合时,它都会创建一个新的副本,因此读取操作不会被阻塞。它适合于遍历操作频繁的场景。
-
ConcurrentSkipListSet: 这是一个基于跳表的线程安全的有序集合实现。它提供了对有序元素的高效并发访问,并且支持高效的范围查询操作。
这只是一些常见的并发集合类的示例,Java中还有其他许多并发集合类可供选择,具体取决于你的需求和使用场景。
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 添加数据
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 获取数据
System.out.println("Value of A: " + map.get("A"));
System.out.println("Value of B: " + map.get("B"));
System.out.println("Value of C: " + map.get("C"));
// 修改数据
map.put("A", 10);
System.out.println("Modified value of A: " + map.get("A"));
// 删除数据
map.remove("B");
System.out.println("Value of B after removal: " + map.get("B"));
// 遍历数据
System.out.println("All entries:");
for (String key : map.keySet()) {
int value = map.get(key);
System.out.println(key + ": " + value);
}
}
}
在上面的示例中,我们创建了一个ConcurrentHashMap对象,并使用put()方法添加键值对。然后,通过get()方法获取指定键的值,并使用put()方法修改某个键的值。我们还使用remove()方法删除了一个键值对。
最后,通过遍历ConcurrentHashMap的键集合,我们可以打印出所有的键值对。