1.why?
为什么要有线程池? 举个生活中的例子,比如A某要举行一场晚会,期间有很多重要人物需要接待,但A某一个人接待不过来,于是A某打算雇好几个人来帮忙接待,假设这些人接待完一个客户后就算是完成任务了,完成后领盒饭走人.那么如果有几百个客户需要接待,那么A某就要雇几百个人来接待,这成本太高A某吃不消了,于是A某想了个万全之策:根据客户数量按比例雇了接待人员,比如有200个客户,因为客户来的时间点都不一样,所以A某雇了20个人来接待,只要有客户来A某就安排接待人员去接待,其余接待人员等待新的客户到来,如果每个人都去接待了,这时有新的客户来就在门口排队等待接待.这样A某不仅节省了成本,提高了接待人员的接待效率,还让整个接待过程稳定圆满完成.
类似地,在程序中当一个线程不能很好的满足任务效率需求时,可以通过线程池来解决尴尬.
2.创建线程池
Executors.newXXXThreadPool
线程池可以创建指定线程数量的线程池Executors.newFixedThreadPool(int num);//其中num为指定的线程数量
也可以创建缓冲池(不固定线程数量,根据业务系统自动创建线程数)Executors.newCachedThreadPool();
还可以创建定时任务线程池(在指定的时间执行任务)Executors.newScheduledThreadPool(int num);//其中num为指定的线程数量
贴个小DEMO:
//演示线程池的几种创建方式
public class ThreadPoolDemo {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newCachedThreadPool();
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for(int i=0;i<10;i++) {
final int task = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
for(int j=0;j<10;j++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"第"+task+"次循环的第"+j+"次循环");
}
}});
}
threadPool.shutdown();
}
}
3.Callable和Future
可以返回线程执行的结果,值得注意的是,当需使用Callable时,线程的启动方式不再为execute,而为commit,贴个DEMO:
//演示Callable和Future
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
Future<String> future = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "你好啊..";
}
});
System.out.println("开始执行...");
try {
System.out.println("执行结果是:"+future.get());
} catch (Exception e) {
e.printStackTrace();
}
threadPool.shutdown();
}
}
当线程执行后并不会立刻拿到返回结果,线程可能会进入阻塞状态,为了避免线程长时间的阻塞下去,我们可以人为地设置等待时间,也就是说当我等了假设2秒(可以自行设定)后,如果还没有拿到返回结果,系统就会抛异常.
System.out.println("执行结果是:"+future.get(1,TimeUnit.SECONDS));//在get中设置等待时间,意思是老子最多等你1秒,1秒后没结果给我走人...
4.ComplitionService
用于返回提交一组Callable任务,其take方法可以返回已完成的一个callable任务返回的结果future对象.
春天到了,农民有10块田,每块田里都同时种了稻子,到了秋天稻子需要收割,哪块先熟先收割哪块,那么到底哪块稻子先熟呢? 下面用代码来模拟这个场景.
//模拟割稻子
public class CompletionServiceDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CompletionService<Integer> cs = new ExecutorCompletionService(threadPool);
for(int i=0;i<10;i++) {
final int seq = i;
cs.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(new Random().nextInt(5000));
return seq;
}
});
}
try {
for(int i=0;i<10;i++) {
System.out.println("收获的是第"+cs.take().get()+"块田的稻子");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.再谈JDK1.5中的Lock锁
Lock相比于synchronized更加面向对象,而且具有读写锁.
ReentrantReadWriteLock(可重入读写锁)
读锁的重入是允许申请读操作的线程,(读数据不会被阻塞)而写锁只允许单个线程占有,该线程的写操作可以重入.(但写数据线程会阻塞).
如果一个线程占有了写锁,此时在不释放写锁的情况下,它还可以占有读锁,占有读锁后它被自动降级为读锁.
//使用读写锁写一个缓存系统(伪代码)
public class ReentrantReadWriteLockDemo {
Map<String, Object> cache = new HashMap();
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
}
public Object getVlaue(String key) {
Object obj = null;
try {
rwl.readLock();
obj = cache.get(key);
if (obj == null) {
rwl.readLock().unlock();// 加入读锁,防止在读的时候其他线程去写数据.
rwl.writeLock();
try {
if (obj == null) {
obj = "从数据库里查";// 伪代码部分
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
return obj;
}
}
6.再谈wait,condition.await/notify,condition.singnal
condition是jdk1.5出现的,在功能上略胜一筹,原来wait/notify可以实现的功能,在condition中都可以实现,而且它比原来的wait/notify更强大,下面以一个条件阻塞队列的例子来说明它的强大之处:
需求:现在需要定义一个缓冲区,可以理解为队列,也可以理解为生产者和消费者模型.在队里中有两个方法,一个负责向队列中填充数据,一个负责从队列中取走数据,但队列的大小是有限的,假设只有100条,当队列中填满了数据之后,就不能继续向其填充,只能等待被取走之后才能继续填充,同时对于负责取数据的方法而言,如果队里没有数据,那它只能等待,直到队列里被填充了新的数据它才能去取.
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;//定义存取位置的指针
public void put(Object x) throws InterruptedException {
lock.lock(); try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally { lock.unlock(); }
}
public Object take() throws InterruptedException {
lock.lock(); try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally { lock.unlock(); }
}
}
因为需要区分需要被唤醒的部分,所以如果换成是notify就无法完成. condition可以指定我要唤醒哪一个.
7.ReentrantReadWriteLock(可重入读写锁)
读锁的重入是允许多个申请读操作的线程的,而写锁同时只允许单个线程占有,该线程的写操作可以重入。
如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。
对于同时占有读锁和写锁的线程,如果完全释放了写锁,那么它就完全转换成了读锁,以后的写操作无法重入,在写锁未完全释放时写操作是可以重入的。
公平模式下无论读锁还是写锁的申请都必须按照AQS锁等待队列先进先出的顺序。非公平模式下读操作插队的条件是锁等待队列head节点后的下一个节点是SHARED型节点,写锁则无条件插队。
读锁不允许newConditon获取Condition接口,而写锁的newCondition接口实现方法同ReentrantLock。
下面贴一个使用可重入读写锁写的一个缓存系统:
//使用读写锁写一个缓存系统(伪代码)
public class ReentrantReadWriteLockDemo {
Map<String, Object> cache = new HashMap();
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
}
public Object getVlaue(String key) {
Object obj = null;
try {
rwl.readLock();
obj = cache.get(key);
if (obj == null) {
rwl.readLock().unlock();// 加入读锁,防止在读的时候其他线程去写数据.
rwl.writeLock();
try {
if (obj == null) {
obj = "从数据库里查";// 伪代码部分
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
return obj;
}
}
8.Semaphore(信号灯)
有一个停车场,总共有5个车位,然后有10辆车子同时开过来了,但保安大叔只允许5辆车子驶入,其余车子在外面等候,直到有车子驶离车库后方可驶入新的车子,反正不管怎样停车场内最多只允许同时停放5辆车子.下面用代码来模拟该场景的实现:
//模拟有5个停车位,但有10辆车来停车.
class Car implements Runnable{
private int carNum;//汽车编号
private Semaphore semaphore;
public Car(int carNum, Semaphore semaphore) {
this.carNum = carNum;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("编号为"+carNum+"的车占用了一个车位");
Thread.sleep(1000);
System.out.println("编号为"+carNum+"的车驶离了该车位");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ParkingLotDemo {
public static void main(String[] args) {
int carNum = 10;//10辆车
int carSpace = 5;//5个车位
Semaphore semaphore = new Semaphore(carSpace);
for(int i=0;i<carNum;i++) {
new Thread(new Car(i,semaphore)).start();
}
}
}
Semaphore可以解决类似多个线程同时访问多个资源(资源数量小于等于线程数量),资源不够用需要排队等待的场景.
9.CyclicBarrier(循环屏障)
公司要组织一次团建,准备去3个地方浪一圈,员工需要自行安排出行方式,但每到达一个地方都需要大家先集合清点人数后方可前往下一个地方,先到达的人需要等待其他人都到齐了才能一同出发.下面用代码来模拟这个场景:
//循环屏障
public class CyclicBarrierDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3);
for(int i=0;i<3;i++) {
Runnable ruunable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10000));
System.out.println("线程:"+Thread.currentThread().getName()+":已经到达集合地点1,当前已有"+cb.getNumberWaiting()+"个线程已到达,"+(cb.getNumberWaiting()==2?"都到齐了,继续前进...":"正在等候..."));
cb.await();
Thread.sleep(new Random().nextInt(10000));
System.out.println("线程:"+Thread.currentThread().getName()+":已经到达集合地点2,当前已有"+cb.getNumberWaiting()+"个线程已到达,"+(cb.getNumberWaiting()==2?"都到齐了,继续前进...":"正在等候..."));
cb.await();
Thread.sleep(new Random().nextInt(10000));
System.out.println("线程:"+Thread.currentThread().getName()+":已经到达集合地点3,当前已有"+cb.getNumberWaiting()+"个线程已到达,"+(cb.getNumberWaiting()==2?"都到齐了,结束行程...":"正在等候..."));
cb.await();
}catch(Exception e){
e.printStackTrace();
}
}
};
service.execute(ruunable);
}
service.shutdown();
}
}
10.CountDownLatch(倒计时锁存器)
公司举办了百米赛跑,共有3名运动员和1名裁判参加,运动员需要等待裁判发出号令号方可起跑,到达终点后需要告诉裁判:"我到了",当三个人都到达并通知裁判后,裁判示意比赛结束.
//演示倒计时锁存器
public class CountDownLatchDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("运动员:" + Thread.currentThread().getName() + "已准备就绪,等待接收命令");
cdOrder.await();
System.out.println("运动员:"+Thread.currentThread().getName()+"已收到命令,开始狂奔...");
Thread.sleep(new Random().nextInt(10000));
System.out.println("运动员:"+Thread.currentThread().getName()+"已到达终点并通知裁判");
cdAnswer.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep(5000);
System.out.println("裁判发出命令,开始跑!");
cdOrder.countDown();//裁判发出命令
cdAnswer.await();
System.out.println("比赛结束,谢谢大家");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
service.shutdown();
}
}
}
11.Exchanger(交换)
A有一头牛,B有一头驴,A想用他的牛换B的驴(估计脑子被驴踢了...),B很乐意,于是他两达成协议互相交换,下面用代码来模拟该场景
public class ExchangeDemo {
private final static String cow = "牛";
private final static String donkey = "驴";
public static void main(String[] args) {
Exchanger e = new Exchanger();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("A:"+Thread.currentThread().getName()+"拿着他的"+cow+"正在和B交换中...");
Thread.sleep(new Random().nextInt(5000));
Object result = e.exchange(cow);
System.out.println("交易成功,A:"+Thread.currentThread().getName()+"换到了B的:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("B:"+Thread.currentThread().getName()+"拿着他的"+donkey+"正在和A交换中...");
Thread.sleep(new Random().nextInt(5000));
Object result = e.exchange(donkey);
System.out.println("交易成功,B:"+Thread.currentThread().getName()+"换到了A的:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.shutdown();
}
}
12ArrayBlockingQueue(阻塞队列)
有点类似JMS的消息队列:生产者和消费者. 假设有2个生产者,1个消费者,队列大小为3,下面用代码模拟该场景:
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue abq = new ArrayBlockingQueue(3);
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + ":正在放入数据");
abq.put("你好");
System.out.println("队列中共有:" + abq.size() + "条数据被放入");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
service.execute(new Runnable() {
@Override
public void run() {
try {
while(true) {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+":正在取数据");
Object x = abq.take();
System.out.println(Thread.currentThread().getName()+":已取走数据:"+x+",队列中还剩:"+abq.size()+"条数据");
if(abq.size()<=0) {
break;
}
}
}catch(Exception e) {
e.printStackTrace();
}
}
});
service.shutdown();
}
}
13.同步集合类
ConcurrentHashMap
线程安全的HashMap,在jdk1.5之前,为了解决HashMap线程不安全问题,使用的是Collections.synchroizedMap().
ConcurrentSkipListMap<K,V>,有序(可以规定排序规则)且线程安全的Map.
ConcurrentSkipListSet,可以规定排序且线程安全的Set.顺便说一下,HashSet其实底层就是HashMap,只不过它只用了HashMap的Key,没用Value.
CopyOnWriteArrayList,CopyOnWriteArraySet.用于在多线程访问,且正在迭代时要对集合里内容做修改(如新增/删除),因为传统的Collection在迭代时是不允许修改集合中的内容的,否则会报错.