线程池
在我们需要使用线程来执行任务的时候,通常会采用继承Thread类或者实现Runnable接口来创建线程,此时我们存在问题:
- 单个线程约占用1M空间,过多分配线程可能会造成内存溢出
- 频繁的创建及销毁线程会增加虚拟机的回收频率,资源开销,造成程序性能下降
(JVM回收垃圾线程时停止一切用户线程)
所以我们一般可以通过线程池来解决上述问题,以下为线程池的基本概念:
- 存储线程的容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,同时里面的线程对象可以重用
- 有效避免了频繁的线程创建与销毁
其原理如下图:
原理总结就是:将任务提交到线程池中,由线程池分配线程来执行,并在任务完成后可以复用执行别的任务
创建与使用线程池:
常见的线程池接口和类为下述,所在包 java.util.concurrent (简称JUC):
- Executor:线程池的顶级接口
- ExecutorService:线程池接口,Executor的子接口,可以提交任务到线程池中
submit(Callabl<T> task) //提交一个Callable任务,返回Future对象
submit(Runnable task) //提交一个Runnable任务
shutdown() //关闭线程池,不接受提交的任务
isTerminated() //线程中所有任务是否执行完毕?true:false- Executors:线程池的工具类,提供了线程池的创建方法
newFixedThreadPool(int nThreads) //创建指定大小的线程池
newCachedThreadPool() //获得动态大小的线程池
两种方法皆会返回ExecutorService对象
线程池的具体应用:
通过Runnable接口创建任务提交给线程池:
实现过程如下图:
public class Demo02 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(new MyTask());
}
}
class MyTask implements Runnable{
public void run(){
System.out.println("线程任务...");
}
}
使用Runnable接口来提交任务,我们可能会遇到两个问题:
- 我们的run()方法里可能会产生异常,而Runnable接口里的run()方法本身没有声明异常,所以我们没办法再重写的方法中声明异常,而且
- 我们无法通过run()方法来返回一个值,也就是我们不能通过多线程并发来共同解决一个问题
所以这时JDK5为我们提供了Callable接口来创建任务,从而可以规范任务的返回值和声明异常
通过Callable接口创建任务提交给线程池:
实现过程如下图:
public class Demo02 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(new MyTask());
}
}
class MyTask implements Callable<Object> {
public Object call() throws Exception{
System.out.println("线程任务...");
return null;
}
}
在Callable接口中定义的call()方法具体用法同run()方法,但其声明了异常同时可以通过泛型来规范返回值的类型
那么我们如何获得call()方法的返回值呢,JDK5同时提供了一个Future接口来获取call()方法的返回值
Future接口及其使用:
基本概念:
异步接收ExecutorService.submit()的返回结果,其中包括call()的结果
V get()可以以阻塞的形式等待Future中的异步处理结果(call()的返回值)
实现过程如下图:
public class Demo02 {
public static void main(String[] args) {
System.out.println("---main start---");
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future future = executorService.submit(new MyTask());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("---main end---");
}
}
class MyTask implements Callable<Integer> {
public Integer call() throws Exception{
int sum = 0;
for (int i =1;i<=100;i++){
sum += i;
}
Thread.sleep(1000);
return sum;
}
}
可以看出效果,在执行完毕call()之前,get()方法会处于一个无限期等待状态,直到call()执行完获得返回值,get()才会执行完毕
Lock接口
- JDK5之后加入,其比synchronized更加灵活
- 提供更多的使用方法,功能强大,性能更优越
- 常用方法:
void lock() //获取锁
boolea tryLock() //尝试获取锁一次?ture:false
void unlock() //释放锁
其有两种实现类可分为重入锁和读写重入锁
重入锁(ReentrantLock):
ReentrantLock和synchronized的区别
- synchronized 是JVM层面的锁,是Java关键字而ReentrantLock是一个Lock接口的实现类,在底层是实现上不同
- synchronized为非公平锁,ReentrantLock则即可以选公平锁也可以选非公平锁
- ReentrantLock可实现手动中断线程,即线程可以不用一直等待锁
- ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒
- synchronzied锁的是对象,ReentrantLock锁的是线程
其与synchronized一样支持重入操作
实现如下图:
public class Demo02 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Callable<Object> callable = new MyTask();
executorService.submit(callable);
executorService.submit(callable);
executorService.submit(callable);
}
}
class MyTask implements Callable<Object> {
ReentrantLock rl = new ReentrantLock();
public Object call() throws Exception{
rl.lock();
try {
System.out.println("线程任务...");
Thread.sleep(1000);
return null;
} finally {
rl.unlock();
}
}
}
读写锁(ReentrantReadWriteLock):
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁和写锁
- 支持多次分配读锁,使得多个读操作可以并发执行
互斥规则:
- 读—读:不互斥
- 写—写:互斥,会堵塞
- 读—写:互斥,读堵塞写,写堵塞读
具体实现如下图:
public class Demo02 {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(20);
Bank bank =new Bank();
Callable<Integer> callable01 = ()->{
return bank.getUserID();
};
Callable<Integer> callable02 = ()->{
bank.setUserID(100);
return null;
};
long millis = System.currentTimeMillis();
for (int i=0;i<15;i++) {
es.submit(callable01);
}
for (int i=0;i<5;i++) {
es.submit(callable02);
}
es.shutdown();
while (true){
System.out.println("---运行中");
if (es.isTerminated()){
break;
}
}
System.out.println("---执行时间:"+ (System.currentTimeMillis()-millis));
}
}
class Bank{
private int userID;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock rl = rwl.readLock();
private ReentrantReadWriteLock.WriteLock wl = rwl.writeLock();
public int getUserID() {
rl.lock();
try {
try { Thread.sleep(1000); } catch (Exception e) { }
return this.userID;
} finally {
rl.unlock();
}
}
public void setUserID(int userID) {
wl.lock();
try {
try { Thread.sleep(1000); } catch (Exception e) { }
this.userID = userID;
} finally {
wl.unlock();
}
}
}
本文章仅供个人参考学习,欢迎各位大佬交流与改正