Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便程序猿借助多线程的方式计算结果.通过下面两个代码实例,可以清晰的看到Callable的优势:
现在要求创建线程计算 1 + 2 + 3 + … + 1000的结果。
在没有Callable的情况下,我们想要在直接通过一个线程来获取到计算结果是不可能的,因为这种情况下,线程的执行不返回任何结果。Runnable接口的run方法定义了线程的执行逻辑,但该方法没有返回值。此时想要实现题目要求,就必须再线程中对一个全局变量进行修改,并且配合线程通信方法来实现:
public class Demo1 {
static class Result {
public int sum = 0;
public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread t = new Thread() {
@Override
public void run() {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
synchronized (result.lock) {
result.sum = sum;
result.lock.notify();
}
}
};
t.start();
synchronized (result.lock) {
while (result.sum == 0) {
result.lock.wait();
}
System.out.println(result.sum);
}
}
}
可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂, 容易出错.
但是当有了Callable,就可以通过以下步骤直接在线程内计算然后返回一个值的方式来实现题目要求:
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 1; i <= 1000; i++) {
result += i;
}
return result;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);
}
理解Callable:
1.Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务。
2.Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
3.FutureTask 就可以负责这个等待结果出来的工作。future部分源码:
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
private Object outcome; // non-volatile, protected by state reads/writes
/** The underlying callable; nulled out after running */
private Callable<V> callable;
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
ReentrantLock可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.
ReentrantLock 的用法:
ReentrantLock lock = new ReentrantLock();
-----------------------------------------
lock.lock();
try {
// working
} finally {
lock.unlock()
}
ReentrantLock 和 synchronized 的区别:
ReentrantLock reentrantLock = new ReentrantLock(true);
// ReentrantLock 的构造方法
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
class SharedResource {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int value;
public void produce(int newValue) {
lock.lock();
try {
value = newValue;
System.out.println("Producing: " + value);
condition.signal(); // 唤醒等待的线程
} finally {
lock.unlock();
}
}
public int consume() {
lock.lock();
try {
while (value == 0) {
try {
condition.await(); // 等待,直到有数据被生产
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
int consumedValue = value;
value = 0;
System.out.println("Consuming: " + consumedValue);
return consumedValue;
} finally {
lock.unlock();
}
}
}
public class Demo2 {
public static void main(String[] args) {
SharedResource sharedResource = new SharedResource();
// 生产者线程
Thread producerThread = new Thread(() -> {
sharedResource.produce(42);
});
// 消费者线程
Thread consumerThread = new Thread(() -> {
int value = sharedResource.consume();
});
producerThread.start();
consumerThread.start();
}
}
原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference
这些原子类是非常有用的,因为它们可以确保在多线程环境中进行原子操作,避免了竞态条件和数据竞争。这些操作是线程安全的,不需要显式的同步措施。例如,你可以使用AtomicInteger来实现计数器,多个线程可以同时递增计数器的值而不会发生竞态条件。常用方法有:
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i–;
incrementAndGet(); ++i;
getAndIncrement(); i++;
以下是一个示例,演示了如何使用AtomicInteger:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger(0);
// 多个线程同时递增计数器的值
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet();//++i
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
//同时启动两个线程对原子类的实力进行自增
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Counter: " + counter.get()); // 应该输出 2000
}
}
线程池在前面的博文有详细的讲解:线程池博文
java.util.concurrent.Semaphore 是 Java 并发编程中的一个重要工具,用于控制并发访问资源的数量。它提供了一种计数信号机制,允许你控制同时访问某个共享资源的线程数。
Semaphore 维护了一个计数器,该计数器表示允许同时访问资源的线程数量。当线程要访问资源时,它必须先获取信号量,如果信号量计数大于零,则线程可以访问资源,同时信号量计数减一;如果计数为零则线程必须等待,直到有其他线程释放资源,增加信号量计数。
Semaphore 的核心方法包括:
acquire(): 当线程想要获得一个信号量时,可以调用这个方法。如果信号量计数大于零,线程将成功获得信号量,计数减一;否则,线程将阻塞,直到有信号
量可用。((这个称为信号量的 P 操作))
release(): 当线程使用完资源后,应该调用 release() 方法来释放信号量,这将使信号量计数加一,表示资源可供其他线程使用。((这个称为信号量的
V操作))
CountDownLatch 的主要思想是,一个线程(通常是主线程)等待其他一组线程执行完特定任务,然后再继续执行。它通过一个计数器来实现,该计数器初始化为一个正整数,每个线程在完成任务时会将计数器减一,当计数器的值达到零时,等待的线程将被唤醒继续执行。
举个例子:
同时等待 N 个任务执行结束.—>好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。
理解:
public class Demo3 {
public static void main(String[] args) {
int taskCount = 10;
CountDownLatch latch = new CountDownLatch(taskCount);
Runnable task = () -> {
// 模拟任务的执行
System.out.println("任务正在执行。");
// 任务完成后减少计数器
latch.countDown();
};
// 启动 10 个线程执行任务
for (int i = 0; i < taskCount; i++) {
Thread thread = new Thread(task);
thread.start();
}
try {
// 等待所有任务完成
latch.await();
System.out.println("所有任务已完成。主线程可以继续执行。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}