JUC的常见类
1.callable接口
callable是一个interface,相当于把线程封装了一个“返回值”,方便程序员借助多线程的方式计算结果。callable接口和Runnable接口是并列关系,但是Runnable返回值是void重写的是run方法更注重执行的过程而不是结果,而callable重写的是call方法,call是有返回值的返回值的类型是泛型参数。
1.2 callable接口的使用
callable代码实现:
import java.util.Timer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//此处callable只是定义了一个“带有返回值的”的任务
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 1; i <= 100; i++) {
result += i;
}
return result;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
//get操作就是获取到 FutureTask 的返回值,这个返回值就来自于Callable的call方法
// get可能会阻塞,如果当前线程执行完毕,get拿到返回值
// 如果当前线程没有执行完毕 get就会一直阻塞
System.out.println(futureTask.get());
}
}
在这里我们可以看到,这里的callable是要放在FutureTask里面才能被线程获取到和Runnable是不一样的。其实Thread本身不提供获取结果的方法,就需要凭借FutureTsak来拿到结果。这里不提供获取结果的方法主要是为了解偶合,这样使Thread和任务这个概念分离开来不关心任务是啥样的是否有返回值。
Runnable的实现:
public class Demo4 {
//静态方法主线程和他线程可以共用
public static int total = 0;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
//Runnable是一个接口,它定义了一个契约,即实现它的类必须提供一个run方法
@Override
public void run() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
total = sum;
}
};
Thread t1 = new Thread(runnable);
t1.start();
t1.join();
System.out.println("total:" + total);
}
}
在这里我们可以总结一下创建线程的写法:
1继承Thread(定义单独的类、使用匿名内部类)
2.实现Runnable(定义单独的类、使用匿名内部类)
3.lambda
4.实现Callable(定义单独的类、实现内部类)
5.线程池ThreadFactory
2.ReentrantLock
可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果保证线程安全。
2.1ReentrantLock的使用
用法如下:
- lock():加锁,如果获取不到锁就死等
- trylock(超时时间):加锁,如果获取不到锁·等待一定时间之后放弃加锁
- unlock:解锁
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Demo2 {
private static int count = 0;
public static void main(String[] args) {
ReentrantLock locker = new ReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
locker.lock();
try {
count++;
}finally {
locker.unlock();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
locker.lock();
try {
count++;
}finally {
locker.unlock();
}
}
}
});
t1.start();
t2.start();
try{
t1.join();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(count);
}
}
但是和synchronized相比synchronized效率更高。
2.2 synchronized和ReentrantLock之间的区别(重点)
1.synchronized是关键字(内部实现是JVM通过C++实现的),ReentrantLock标准库的类(java)
2.synchronized通过代码块控制加锁解锁不需要手动释放锁,ReentrantLock需要lock、unlock方法,需要注意unlock不被调用的问题建议搭配Finally使用
3.ReentrantLock除了提供lock、unlock之外,还提供了一个方法tryLock。这个方法不会阻塞还可以设置超时时间,加锁成功返回true,加锁失败返回false,调用者判定返回值决定接下来咋做。这个tryLock相当于追女神追不到就换一个,而普通的synchronized就是如果追不带女神就一直等,但是加了之后就不需要考虑解锁。
4.ReentrantLock提供了公平锁的实现,默认是非公平的。按照如下设置是公平锁
5.ReentrantLock搭配等待通知机制是Condition类,相比wait notify来说功能更强大一些。
3.原子类
原子类内部用的是CAS实现,所以性能要比加锁实现i++高很多,原子类有以下几个
1.AtomicBoolean
2.AtomicInteger
3.AtomicIntegerArray
4.AtomicLong(数据统计,服务器收到多少请求)
5.AtomicReference
6.AtomicStampedReference
4.信号量Semaphore
信号量用来表示可用资源的个数,本质上就是一个计数器。能够协调多个进程之间的资源分配,也能协调多个线程之间的资源分配。信号量表示可用资源的个数,申请一个资源计数器就会-1,释放一个资源计数器就会+1,计数器为0,继续申请就会阻塞等待。其中申请(acquire方法)资源(p操作),release方法表示释放资源(V操作)。
代码实现:
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(4);
semaphore.acquire();
System.out.println("进行一次P操作");
semaphore.acquire();
System.out.println("进行一次P操作");
semaphore.acquire();
System.out.println("进行一次P操作");
semaphore.acquire();
System.out.println("进行一次P操作");
}
}
如果再进行一次P操作那么代码就会阻塞,开始的时候定义为4表示4个可用资源.打印的仍然是四次。
信号量还有一个特殊的情况,那就是初始值为1的时候。取值要么是1要么是0称为二元信号量,这样的信号量等价于锁。只有第一个线程才能进行P操作第二个线程要想进行P操作就需要等待,如果是普通N的信号量,就可以限制同时有多少个线程来执行某个逻辑。
关于信号量锁的实现:
import java.util.concurrent.Semaphore;
public class Demo4 {
private static int count = 0;
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
try {
semaphore.acquire();
count++;
semaphore.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
try {
semaphore.acquire();
count++;
semaphore.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(count);
}
}
5.CountDownLatch
我们在使用多线程,经常把一个大的任务拆分成多个子任务,使用多个线程执行这些子任务从而提高程序的效率。如何衡量这些任务是否都完成了呢?这就需要使用到CountDownLatch 同时等待N个任务执行结束。
5.1CountDownLatch的使用
1.实例化时构造方法指定参数,描述拆成多少个任务传递参数就为多少就,这里假设为10
2.每个任务执完毕之后,都调用一次countDown方法。当调用次数为10说明任务全部完成
3.主线程调用await方法,阻塞等待所有任务执行完毕,await就会返回
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//现在把整个任务拆成10个部分,每个部分视为一个子任务
// 可以把这10个任务丢到线程池中,让线程池执行
// 当然也可以安排10个独立的线程执行
CountDownLatch countDownLatch = new CountDownLatch(10);
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
int id = i;
executorService.submit(()->{
System.out.println("子任务开始执行:" +id);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子任务结束执行:"+id);
countDownLatch.countDown();
});
}
//这个方法阻塞等待所有的任务结束
countDownLatch.await();
System.out.println("所有任务执行完毕");
}
}
结果: 线程池包含前台线程所以没有 return 0 ,要想return 0 在代码最后加上
executorService.shutdown();
原文地址:https://blog.csdn.net/2301_80785428/article/details/143726127
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!