线程池
线程容器,可设定线程分配的数量上限。
将预先创建线程对象存入池中,并重用线程池中的线程对象。
避免频繁的的创建和销毁。
常用的线程池接口和类
Executor :线程池的顶级接口。
ExecutorService :线程池接口,可通过submit(Runnable task)提交任务代码。
Executors工厂类:通过此类可获得一个线程池。
通过newFixedThreadPool(int nThreads)获取固定数量的线程池。
通过newCachedThreadPool()获得动态数量的线程池,如果不够 则创建新的,没有上限。
public class TestThreadPool {
public static void main(String[] args) {
//线程池接口(引用) ---> Executors工具类(工厂类)
ExecutorService es = Executors.newFixedThreadPool(4);
Runnable task = new MyTask();
es.submit(task); //submit()提交任务
es.submit(task);
es.submit(task);
es.submit(task);
}
}
//创建接口实现类
class MyTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " - " +i);
}
}
}
Callable接口
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
有泛型返回值,可以声明异常。
Future接口
概念:
异步接收ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值。
方法:
V get()以阻塞形式等待Future的异步处理结果(call()的返回值)
使用
线程池执行Callable接口,Callable返回值由Future接收,Future当中的get() 方法就可以得到异步返回值。
可以解决并发下的一些统计工作。
public class TestCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("程序开始");
//创建线程池
ExecutorService ex = Executors.newFixedThreadPool(3);
//接口引用指向实现接口
Callable<Integer> task1 = new MyTask1();
Callable<Integer> task2 = new MyTask2();
Future<Integer> f1 = ex.submit(task1); //得到Callable线程接口的返回值
Future<Integer> f2 = ex.submit(task2);
Integer result1 = f1.get(); //以阻塞形式等待Future中的异步处理结果(call的返回值)
Integer result2 = f2.get(); //在没有返回值以前,get无限期等待
System.out.println(result1 + result2); //输出5050
}
}
//Mytask1类遵从Callable接口实现call()方法
class MyTask1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(1000); //休眠1秒,观察Future接口get方法
Integer sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum; //返回计算的sum值
}
}
//Mytask2类遵从Callable接口实现call()方法
class MyTask2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(1000);
Integer sum = 0;
for (int i = 51; i <= 100; i++) {
sum += i;
}
return sum; //返回计算的sum值
}
}
同步和异步的区别:
同步:
形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
异步:
形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。
Lock接口
提供更多实用性的方法,功能更强大,性能更优越。
常用方法:
void lock() - 获取锁,如锁被占用,则等待。
boolean tryLock() - 尝试获取锁(成功返回true,失败返回false,不阻塞)
void unlock() - 释放锁
重入锁
ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。
Lock locker = new ReentrantLock(); //创建重入锁对象
locker.lock(); //开启锁
try{
锁住代码块
}finally{
//考虑可能会出现异常,释放锁必须放入finally代码块中,避免无法释放
locker.unlock(); //释放锁
}
读写锁
ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
写 - 写:互斥,阻塞。
读 - 写:互斥,读阻塞写,写阻塞读。
读 - 读:不互斥,不阻塞。
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
//读写锁
class Student {
//创建读写锁对象
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//创建读锁
ReentrantReadWriteLock.ReadLock readLock =rwl.readLock();
//创建写锁
ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();
private int value; //属性
//读方法
public int getValue() throws InterruptedException {
readLock.lock(); //开启读锁
try {
Thread.sleep(1000); //休眠1秒,观察读的时间
return value;
} finally {
readLock.unlock(); //关闭读锁
}
}
//写方法
public void setValue(int value) throws InterruptedException {
writeLock.lock(); //开启写锁
try {
Thread.sleep(1000);
this.value = value;
} finally {
writeLock.unlock(); //关闭写锁
}
}
}