Callable和Runnable是一个类似的接口,但是Callable中的call方法可以返回任意一个类型结果,而Runable中的run方法并不能返回结果。所以引入Callable接口可以方便在多线程中的计算结果。
利用Callable接口计算1加到100:
import java.util.concurrent.*;
public class Test2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 计算1加到100
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
};
//Callable常要搭配FutureTask来使用,接受执行结果
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
//调用get()会进行堵塞,直至获得结果
int result = futureTask.get();
System.out.println(result);
}
}
我们并不能直接获得call方法返回的结果,所以Callable常搭配FutureTask来使用,FutureTask相当于一个执行任务的容器,使用get()可以获得执行任务的结果,并且调用get()会进行堵塞,直至获得结果。
Callable也可以搭配线程池使用,充分提供多线程性能优势:
import java.util.concurrent.*;
public class Test2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 计算1加到100
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// Thread t = new Thread(futureTask);
// t.start();
executorService.submit(futureTask);
int result = futureTask.get();
System.out.println(result);
executorService.shutdown();
}
}
- 两者都是执行一个任务;
- Callable可以返回一个任何类型的值,而Runnable不可以;
- Callable需要搭配FutureTask来使用,而Runnable不需要;
RenntrantLock(可重入锁)是Java中的一个并发锁类它和synchronized关键字有相似的功能,但是相比更加的灵活,具有更多的功能,但操作起来应该更加的谨慎,防止死锁。
例如:
- 都具有可重入性:一个线程一次可以获得多个锁,不会发生死锁。
- 具有公平性:ReentrantLock默认是非公平锁,但是可以构造锁是传入true,开启公平锁模式,确定等待时间最长的锁优先获得锁。
// ReentrantLock 的构造方法 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
- 条件变量支持:它支持条件变量,可以在某些条件下等待或唤醒线程。
- 可中断性:线程可以在等待过程中被中断,可用于处理取消操作。
- 超时获得锁:线程在一定时间内尝试获得锁,如果超时仍然没有获得,可以进行其他操作。
它属于Java的java.util.concurrent.locks包。
- lock():加锁,如果获不得锁就死等。
- trylock(超时时间):加锁,在超时时间内等待获得锁,超过则放弃加锁。
- unlock():解锁。
格式:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// working
} finally {
//可以防止锁没被释放
lock.unlock()
}
举例,两个线程并发执行,分别对数字count加1000:
public class Test1 {
//全局变量count
static int count = 0;
public static void main(String[] args) throws InterruptedException {
//锁
ReentrantLock lock = new ReentrantLock();
//线程t1
Thread t1 = new Thread(() -> {
lock.lock();
try{
for (int i = 0; i < 1000; i++) {
count++;
}
}finally {
lock.unlock();
}
});
//线程t2
Thread t2 = new Thread(() -> {
lock.lock();
try{
for (int i = 0; i < 1000; i++) {
count++;
}
}finally{
lock.unlock();
}
});
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(count);
}
}
- synchronized是一个关键字,基于JVM内部实现,ReentrantLock是一个类,基于JVM外部实现(Java实现)。
- 两者都是可重入锁,一个线程可以多次加锁。
- synchronized一开始是非公平锁,在锁竞争激烈的情况下转换为公平锁,ReentrantLock默认也是一个非公平锁,但是也可以构造出公平锁。
- synchronized需要不手动释放锁,ReentrantLock需要手动获得锁。
- synchronized申请锁失败后,会死等,而ReentrantLock可以使用trylock(超时时间),一定时间放弃锁。
- synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程。
- 锁竞争不激烈,使用synchronized更加的方便,自动释放锁,加锁。
- 锁竞争激烈,使用ReentrantLock,搭配trylock(超时时间)更加方便,且避免死等。
- 需要公平锁 ,使用ReentrantLock。
原子类是 Java中用于多线程编程的一组类,它们提供了一种线程安全的方式来执行原子操作,这意味着这些操作不会被其他线程中断,也不会导致数据不一致或竞态条件。
一些常见的 Java 原子类包括:
- AtomicInteger: 用于原子操作整数类型,例如自增、自减、获取当前值等。
- AtomicLong: 用于原子操作长整数类型,同样支持自增、自减、获取当前值等操作。
- AtomicBoolean: 用于原子操作布尔类型,通常用于控制标志位的状态。
- AtomicReference: 用于原子操作引用类型,允许你原子性地设置和获取引用对象。
- AtomicReferenceArray: 用于原子操作引用类型的数组。
- AtomicIntegerFieldUpdater: 用于原子性地更新某个类的整数字段。
这些原子类的主要优势在于它们提供了一种无需显式使用锁(如synchronized关键字)就可以执行线程安全操作的方式。这可以提高多线程程序的性能,因为锁可能导致线程的阻塞和竞争。
方法 | 作用 |
---|---|
incrementAndGet() | 前置自增1 |
decrementAndGet() | 前置自减1 |
getAndDecrement() | 后置自减1 |
getAndIncrement() | 后置自增1 |
getAndAdd(int n) | 自增n |
public class Test2 {
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicInteger = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
atomicInteger.incrementAndGet();//++i
// atomicInteger.decrementAndGet();//--i
// atomicInteger.getAndDecrement();//i--
// atomicInteger.getAndIncrement();//i++
// atomicInteger.getAndAdd(n);//自增n
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
atomicInteger.incrementAndGet();
}
});
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(atomicInteger.get());//get方法获得数
}
}
信号量(Semaphore)用于表示可用资源数量,本质上就是一个计数器。当执行P操作这个信号量就减一个,当执行V操作这个信号量就加一个。
如果信号量减为0,P操作就无法执行,等待V操作增加信号量,如果信号量为最大值了,V操作就无法执行,等待P操作减少信号量。
Semaphore中PV操作时原子性的,则可以多线程环境中直接使用。
方法 | 作用 |
---|---|
acquire() | P操作 |
release() | V操作 |
public class Test3 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("申请到一个资源");
Thread.sleep(2000);
semaphore.release();
System.out.println("释放一个资源");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
}
}
- 资源控制:信号量可以对共享资源进行保护,限制同时访问资源的线程数量。例如,数据库连接池可以使用信号量来限制同时连接到数据库的线程数量,以避免资源过度竞争。
- 线程的通信:信号量也可以用于线程之间的通信,例如一个线程可以等待另一个线程发出的信号来执行某些操作。这在多线程协作和同步的场景中非常有用。
- 控制执行顺序:通过适当设置信号量的初始值,可以控制线程的执行顺序。例如,可以设置一个信号量为0,然后一个线程在完成某个任务后释放信号量,另一个线程等待信号量被释放才能执行。
- 限流:信号量可以用于限制对某些资源或服务的请求速率,以防止资源过度消耗或过载。
- 解决死锁:在一些情况下,信号量可以用来解决死锁问题,通过适当的信号量设置,可以破坏死锁的条件,使得系统更加健壮。
总之,信号量是一种重要的同步工具,虽然在某些情况下可能看起来多此一举,但在复杂的多线程应用中,它们可以提供精确的控制和协调,帮助确保线程安全和程序的可靠性
同时等待N个任务执行结束。
方法 | 作用 |
---|---|
countDown() | 计数器减一 |
await() | 堵塞等待所有任务执行结束 |
public class Test4 {
static int count = 10;
public static void main(String[] args) throws InterruptedException {
//初始化10,表示10个任务需要完成
CountDownLatch countDownLatch = new CountDownLatch(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
try{
System.out.println(count--);
//每次调用countDown(),CountDownLatch内计数器减1
countDownLatch.countDown();
}catch (Exception e){
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
//堵塞等待所有任务执行结束
countDownLatch.await();
System.out.println("执行结束");
}
}