Callable 接口
在Java中,Callable
接口是一个函数式接口,主要用于表示可以返回结果的任务。它和Runnable
接口类似,但Runnable
的run
方法没有返回值,而Callable
的call
方法可以返回一个结果,且可以抛出异常。
主要特点:
-
泛型 :
Callable
接口是一个泛型接口,需要指定返回值的类型。例如,Callable<Integer>
表示一个可以返回Integer
类型结果的任务。 -
call 方法 :
Callable
接口中定义了的方法是call()
,该方法执行任务并返回一个结果。它的签名如下:javaV call() throws Exception;
-
异常处理 :与
Runnable
接口不同,call()
方法可以抛出异常,这使得在执行任务时能够捕获和处理可能出现的错误。 -
与Executor框架结合使用 :
Callable
通常与ExecutorService
结合使用,可以通过Future
类获取任务的结果。使用submit()
方法提交一个Callable
任务后,返回一个Future
对象,允许你在之后的某个时间点获取结果或检查任务是否已完成。
示例代码:
以下是一个使用Callable
接口的简单示例:
java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 创建一个Callable任务
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 模拟一些计算
return 123;
}
};
// 提交任务并获取Future对象
Future<Integer> future = executorService.submit(task);
try {
// 获取任务的结果
Integer result = future.get();
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
在这个示例中,我们创建了一个实现了Callable
接口的任务,并将其提交到一个线程池中执行。最后,通过Future
对象获取计算结果。
总之,Callable
接口是Java中实现并发编程的重要组成部分,广泛用于需要返回结果或处理异常的任务。
ReentrantLock 可重入互斥锁
ReentrantLock
是Java中的一个重要的同步工具类,提供了比内置的同步机制(如synchronized
关键字)更灵活的锁机制。它位于java.util.concurrent.locks
包中,主要用于在多线程环境中控制对共享资源的访问。
主要特点:
-
可重入性:锁是可重入的,这意味着同一个线程可以多次获得同一个锁而不会发生死锁。每次锁的获取都会增加锁的计数,释放时相应地减少。
-
公平性 :
ReentrantLock
可以选择是否使用公平策略。在公平模式下,线程会按照请求锁的顺序来获得锁;而在非公平模式下,线程可以插队获取锁。公平性在构造ReentrantLock
时指定:javaReentrantLock lock = new ReentrantLock(true); // 公平锁
-
灵活的锁定和释放 :与
synchronized
不同,ReentrantLock
允许在代码的任何位置手动锁定和释放,这为控制锁的范围提供了更大的灵活性。 -
条件变量 :
ReentrantLock
提供了条件变量(Condition
对象),允许线程在某些条件下等待并被其他线程唤醒。这使得在复杂的线程协调中更加强大。 -
中断支持 :
ReentrantLock
的锁获取可以响应中断,可以通过调用lockInterruptibly()
方法来实现,使得线程可以在尝试获取锁时响应中断。
示例代码:
以下是一个使用ReentrantLock
的简单示例:
java
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static ReentrantLock lock = new ReentrantLock();
private static int sharedResource = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(new Task());
Thread thread2 = new Thread(new Task());
thread1.start();
thread2.start();
}
static class Task implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
lock.lock(); // 获取锁
try {
// 访问共享资源
sharedResource++;
System.out.println(Thread.currentThread().getName() + " : " + sharedResource);
} finally {
lock.unlock(); // 释放锁
}
}
}
}
}
在这个示例中,我们创建了一个ReentrantLock
实例来保护对共享资源sharedResource
的访问。两个线程同时尝试增加该资源的值。lock.lock()
和lock.unlock()
负责获取和释放锁,确保线程安全。
总结:
ReentrantLock
是Java并发编程中一个强大且灵活的工具,提供了比内置锁更丰富的特性。它允许开发者根据具体需求选择锁的行为,并更好地控制多线程之间的资源竞争和协调。
信号量 Semaphore
Semaphore
类是Java中的一个同步工具,用于控制对某个资源的访问。它位于java.util.concurrent
包中,能够限制同时访问某个特定资源的线程数量,从而实现更精细的线程控制。
主要特点:
-
许可证(Permit) :
Semaphore
维护一组许可证(permits),每个许可证表示可以同时被一个线程占用的资源。线程在获取许可证之前,需要检查是否有可用的许可证。 -
公平性 :
Semaphore
可以选择是否使用公平策略。在公平模式下,线程会按照请求许可证的顺序来获得许可证;而在非公平模式下,线程可以插队抢占许可证。公平性在Semaphore
构造时指定:javaSemaphore semaphore = new Semaphore(3, true); // 公平信号量
-
可重入 :
Semaphore
允许同一个线程多次获得许可证,而在释放时则需要相应地多次释放。 -
限制资源访问 :使用
Semaphore
可以控制对共享资源的访问,例如限制同时访问数据库连接、文件、网络连接等的线程数。 -
阻塞和非阻塞 :
Semaphore
提供了阻塞和非阻塞的获取许可证的方法。acquire()
方法是阻塞式的,线程会在没有可用许可证时等待,而tryAcquire()
方法则是非阻塞式的,立即返回,允许线程根据许可证的可用性做进一步处理。
示例代码:
以下是一个使用Semaphore
的简单示例:
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int MAX_CONCURRENT_THREADS = 3;
private static Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(new Task(i));
}
executorService.shutdown();
}
static class Task implements Runnable {
private final int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
try {
// 请求许可证
semaphore.acquire();
System.out.println("Task " + taskId + " is executing...");
// 模拟任务执行
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放许可证
semaphore.release();
System.out.println("Task " + taskId + " has finished.");
}
}
}
}
在这个示例中,我们创建一个Semaphore
实例,允许最多3个线程同时执行Task
。我们使用semaphore.acquire()
请求许可证,如果许可证不可用,线程会等待。模拟任务执行时,每个任务会输出其ID,表示正在执行。任务结束后,使用semaphore.release()
释放许可证,以便其他线程能够获取。
总结:
Semaphore
是用于控制对共享资源访问的强大工具,尤其适合于限制同时访问资源的线程数量。通过合理利用Semaphore
,可以有效地防止资源过载,提高系统性能与稳定性。
CountDownLatch
CountDownLatch
是Java中的一个同步辅助类,位于java.util.concurrent
包中。它允许一个或多个线程等待直到一组操作完成。其核心功能是通过倒计时来控制线程的执行,常用于多线程协调。
主要特点:
-
计数器 :
CountDownLatch
维护一个计数器,在线程之间协调工作。当计数器的值减为0时,表示所有线程都已完成它们的任务。 -
构造方法 :
CountDownLatch
的构造方法需要一个整数参数,表示初始计数值。这个值通常是要等待的事件数量。 -
await()方法 :调用该方法的线程会被阻塞,直到计数器的值达到0。无论是主线程还是工作线程都可以调用
await()
。 -
countDown()方法 :当一个线程完成了某个任务时,可以调用这个方法使得计数器减1。每次调用
countDown()
都会减少计数器的值。 -
一次性使用 :一旦计数器减到0,
CountDownLatch
不能重用。如果需要重用,必须创建一个新的实例。
使用场景:
CountDownLatch
非常适合于以下场景:
- 等待多个线程完成它们的任务,例如在多线程环境中主线程需要等待所有工作线程完成后再继续执行。
- 在某个初始条件满足之前,不允许某些线程开始执行。
示例代码:
以下是一个使用CountDownLatch
的简单示例:
java
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private static final int THREAD_COUNT = 3;
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
// 创建并启动多个线程
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(new Worker(latch, i)).start();
}
try {
// 等待计数器减到0
latch.await();
System.out.println("All threads have finished, main thread is continuing...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class Worker implements Runnable {
private final CountDownLatch latch;
private final int threadId;
public Worker(CountDownLatch latch, int threadId) {
this.latch = latch;
this.threadId = threadId;
}
@Override
public void run() {
try {
// 模拟任务执行
System.out.println("Thread " + threadId + " is working...");
Thread.sleep((long) (Math.random() * 1000)); // 随机睡眠时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 完成任务后调用countDown()
latch.countDown();
System.out.println("Thread " + threadId + " has finished.");
}
}
}
}
在这个示例中,我们创建了一个CountDownLatch
实例,其初始计数值为线程的数量。每个工作线程在执行完成后调用countDown()
,主线程在调用latch.await()
时会被阻塞,直到所有工作线程完成任务计数器减到0,主线程之后再继续执行。
总结:
CountDownLatch
是一个非常有用的工具,尤其是在需要等待多个线程完成某项任务时。通过合理地使用CountDownLatch
,可以简化多线程程序的控制逻辑,从而提高程序的可读性和维护性。
线程安全集合类
Java提供了一系列线程安全的集合类,主要用于在多线程环境中安全地处理数据。这些集合类通常是在java.util.concurrent
包中实现的,也有一些是在java.util
包中的类具有同步(synchronized)功能。以下是一些常用的线程安全集合类:
ConcurrentHashMap
- 描述:一个高效的哈希表实现,支持高并发的访问。
- 特点:支持分段锁定,能在多个线程同时读取和写入时保持高性能。
CopyOnWriteArrayList
- 描述:线程安全的ArrayList实现,主要用于读多写少的场景。
- 特点:每次修改(添加、删除)时都会复制整个底层数组,因此在读取时不会出现并发修改的问题。
CopyOnWriteArraySet
- 描述 :线程安全的Set实现,底层使用
CopyOnWriteArrayList
。 - 特点 :提供与
CopyOnWriteArrayList
类似的特性,但去除重复元素。
BlockingQueue
接口及其实现
- 描述 :
BlockingQueue
是一个支持阻塞操作的队列接口,有多个实现。 - 典型实现 :
ArrayBlockingQueue
:基于数组实现的有界阻塞队列。LinkedBlockingQueue
:基于链表实现的可选界限队列。PriorityBlockingQueue
:支持优先级排序的无界阻塞队列。DelayQueue
:一个基于时间的阻塞队列,元素会在指定时间后可用。
ConcurrentSkipListMap
- 描述:一个高效的并发跳表实现,具有给定顺序的Map结构。
- 特点:可以使用并发地进行插入、删除、查询等操作,且具有排序特性。
ConcurrentSkipListSet
- 描述 :一个基于
ConcurrentSkipListMap
的线程安全Set实现。 - 特点:具备Set的所有特性,并支持高并发。
Collections.synchronizedList()
- 描述:返回一个线程安全的List包装。
- 使用方法 :例如,使用
Collections.synchronizedList(new ArrayList<>())
可以将ArrayList包装成线程安全的List。
Collections.synchronizedMap()
- 描述:返回一个线程安全的Map包装。
- 使用方法 :例如,使用
Collections.synchronizedMap(new HashMap<>())
可以将HashMap包装成线程安全的Map。
Collections.synchronizedSet()
- 描述:返回一个线程安全的Set包装。
- 使用方法 :例如,使用
Collections.synchronizedSet(new HashSet<>())
可以将HashSet包装成线程安全的Set。
总结
这些线程安全的集合类允许开发者在多线程环境中安全地管理数据,避免了并发操作导致的数据不一致和其他问题。在选择使用哪种集合类时,可以根据具体的使用场景和性能要求进行权衡。