多线程笔记(三)
来源:互联网 发布:jquery.tooltips.js 编辑:程序博客网 时间:2024/06/06 20:50
7.1Executor框架
为了更好的控制多线程,JDK提供了一台线程框架Execator,帮助开发人员有效的进行线程控制。是JDK并发包的核心。我们平时用的最多的便是Executors工厂类,这个工厂类提供了能产生多个不同功能线程池的方法。
newFixedThreadPool()方法:具有固定数量的线程池,线程数量始终不变。当有一个新任务提交时,线程中若有空闲进程变会执行它。若没有,则新的任务会被暂停在一个任务队列中。
newCachedThreadPool()方法:线程数量不固定,当线程不足时便会产生新的线程。
newSingleThreadExecutor()方法:只有一个线程的线程池,按先进先出的顺序执行队列中的任务。
newSingleThreadScheduledExecutor()方法:只有一个线程的线程池,但是可以指定执行时间或执行周期 newScheduleThreadPool()方法:同上一个方法,只不过可以指定线程数量。
7.2 自定义线程池
newFixedThreadPool()、newSingleThreadExecutor()和newCachedThreadPool()方法其内部都使用了 ThreadPoolExecutor线程池。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
我们可以看到,以上线程池都只是 ThreadPoolExecutor 类的封装。这是一个比较复杂的类,我们通过构造函数来慢慢了解它。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池线程数量
maxMumPoolSize:最大线程数量
keepAliveTime:超过corePoolSize数量的线程的存活时间
unit: keepAliveTime的单位
workQueue:任务队列,保存了被提交但是未被执行的任务
threadFactory:用于创建线程的线程工厂
workQueue用来盛放被提交但未执行的任务队列,它是一个BlockingQueue 接口的对象,仅用于存放Runnable对象。可以使用以下一种阻塞队列直接提交队列:
由SynchronousQueue对象提供。即每一次提交任务时,如果没有空闲的线程,就会提交失败或者执行拒绝策略。因此,此时要设置很大的maximumPoolSize值。
有界的任务队列:可以使用 ArrayBlockingQueue。这时,如果有新任务需要执行,且实际线程数小于corePoolSize 时,会优先创建线程。若大于corePoolSize,则会将任务加入等待队列。若队列已满,则会创建新线程且保证线程数不大于 maximumPoolSize。若大于 maximumPoolSize,则执行拒绝策略。
无界任务队列:使用 LinkedBlockingQueue 类实现。和有界任务队列类似,只不过系统线程数到达corePoolSize后就不在增加。后续任务都会放入阻塞队列中,直到耗尽系统资源
优先任务队列: 通过 PriorityBlockingQueue 实现。可以控制任务的执行先后顺序,是一个特殊的无界队列。
ThreadPoolExecutor 最后一个参数 handler 指定了拒绝策略,有如下几种:
AbortPolicy策略:直接抛出异常,阻止系统正常工作。
CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
DiscardOledestPolicy策略:丢弃最老的一个请求(即将被执行的任务),并尝试再次提交当前任务。
DiscardPolicy策略:丢弃无法处理的任务,不作任何处理。 以上拒绝策略都实现了RejectedExecutionHandler接口,我们也可以扩展这个接口来实现自己的拒绝策略。
8.1 Concurrent。util常用类
1.CyclicBarrier使用: 假设有只有的一个场景:每个线程代表一个怕不运动员,当运动元都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。 实例:
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UseCyclicBarrier {
static class Runner implements Runnable { private CyclicBarrier barrier; private String name; public Runner(CyclicBarrier barrier, String name) { this.barrier = barrier; this.name = name; } @Override public void run() { try { Thread.sleep(1000 * (new Random()).nextInt(5)); System.out.println(name + " 准备OK."); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(name + " Go!!"); } } public static void main(String[] args) throws IOException, InterruptedException { CyclicBarrier barrier = new CyclicBarrier(3); // 3 ExecutorService executor = Executors.newFixedThreadPool(3); executor.submit(new Thread(new Runner(barrier, "zhangsan"))); executor.submit(new Thread(new Runner(barrier, "lisi"))); executor.submit(new Thread(new Runner(barrier, "wangwu"))); executor.shutdown(); }
}
2.CountDownLacth使用: 他经常用于监听某些初始化操作,等待初始化执行完毕后,通知主线程继续工作。
import java.util.concurrent.CountDownLatch;
public class UseCountDownLatch {
public static void main(String[] args) { final CountDownLatch countDown = new CountDownLatch(2); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("进入线程t1" + "等待其他线程处理完成..."); countDown.await(); System.out.println("t1线程继续执行..."); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("t2线程进行初始化操作..."); Thread.sleep(3000); System.out.println("t2线程初始化完毕,通知t1线程继续..."); countDown.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("t3线程进行初始化操作..."); Thread.sleep(4000); System.out.println("t3线程初始化完毕,通知t1线程继续..."); countDown.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t2.start(); t3.start();}
}
3.Callable和Future
这个例子其实就是Future模式。JDK给予我们一个实现的封装,使用非常简单。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class UseFuture implements Callable{
private String para;
public UseFuture(String para){ this.para = para;}/** * 这里是真实的业务逻辑,其执行可能很慢 */@Overridepublic String call() throws Exception { //模拟执行耗时 Thread.sleep(5000); String result = this.para + "处理完成"; return result;}//主控制函数public static void main(String[] args) throws Exception { String queryStr = "query"; //构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类 FutureTask<String> future = new FutureTask<String>(new UseFuture(queryStr)); FutureTask<String> future2 = new FutureTask<String>(new UseFuture(queryStr)); //创建一个固定线程的线程池且线程数为1, ExecutorService executor = Executors.newFixedThreadPool(2); //这里提交任务future,则开启线程执行RealData的call()方法执行 //submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值 Future f1 = executor.submit(future); //单独启动一个线程去执行的 Future f2 = executor.submit(future2); System.out.println("请求完毕"); try { //这里可以做额外的数据操作,也就是主程序执行其他业务逻辑 System.out.println("处理实际的业务逻辑..."); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } //调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待 System.out.println("数据:" + future.get()); System.out.println("数据:" + future2.get()); executor.shutdown();}
}
Future模式非常适合在处理很耗时很长的业务逻辑是进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量。
9.1 锁
在java多线程中,我们知道可以使用synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个同步互斥工作,他就是lock对象,我们主要学习两种锁,重入锁和读写锁。他们具有比synchronized更为强大的功能,并且有嗅探锁定,多路分支功能。
重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UseReentrantLock {
private Lock lock = new ReentrantLock();public void method1(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1.."); Thread.sleep(1000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1.."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }}public void method2(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2.."); Thread.sleep(2000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2.."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }}public static void main(String[] args) { final UseReentrantLock ur = new UseReentrantLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { ur.method1(); ur.method2(); } }, "t1"); t1.start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //System.out.println(ur.lock.getQueueLength());}
}
9.2 锁与等待、通知 我们在使用Lock的时候,可以使用一个新的等待,通知的类,他就是Condition,这个Condition一定是针对具体的某一把锁的。也就是在只有锁的基础上才会产生Condition。我们可以通过一个lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。事例:
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UseCondition {
private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void method1(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态.."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁.."); condition.await(); // Object wait System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行..."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); }}public void method2(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入.."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒.."); condition.signal(); //Object notify } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); }}public static void main(String[] args) { final UseCondition uc = new UseCondition(); Thread t1 = new Thread(new Runnable() { @Override public void run() { uc.method1(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { uc.method2(); } }, "t2"); t1.start(); t2.start();}
}
9.3 ReentrantReadWriteLock(读写锁) 读写锁ReentrantReadWriteLock,其核心就是实现读写分离的锁,在高并发访问下,尤其是读多写少的情况下,性能要高于重入锁。其本质是分为两个锁,既读、写锁。在读锁下,多个线程尅并发的进行访问,但是在写锁的时候,只一个一个的顺序访问。
读读共享,写写互斥,读写互斥。事例代码:
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class UseReentrantReadWriteLock {
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();private ReadLock readLock = rwLock.readLock();private WriteLock writeLock = rwLock.writeLock();public void read(){ try { readLock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); }}public void write(){ try { writeLock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); } catch (Exception e) { e.printStackTrace(); } finally { writeLock.unlock(); }}public static void main(String[] args) { final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { urrw.read(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { urrw.read(); } }, "t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { urrw.write(); } }, "t3"); Thread t4 = new Thread(new Runnable() { @Override public void run() { urrw.write(); } }, "t4"); t1.start(); t2.start(); t1.start(); // R t3.start(); // W t3.start(); t4.start();}
}
9.4锁优化总结
避免死锁
减少锁的持有时间
减少锁的粒度
锁的分离
尽量使用无锁的操作,如原子操作(Atomic系列类)。volatile关键字
- 多线程笔记(三)
- 多线程编程笔记(三)
- 多线程编程笔记(三)
- 多线程学习笔记(三)
- 多线程编程学习笔记(三)
- 线程池---多线程学习笔记(三)
- Java多线程学习笔记(三)
- Java多线程学习笔记(三)
- java多线程学习笔记(三)
- 多线程学习笔记 三
- 多线程学习笔记三
- 《posix多线程编程》笔记(三)
- Java多线程知识-笔记三
- (黑马程序员)学习笔记,多线程(三)
- Posix多线程笔记(三)—线程属性(1)
- Posix多线程笔记(三)—线程属性(2)
- Posix多线程笔记(三)—线程属性(3)
- 看孙鑫老师VC++视频教程笔记 之 多线程编程(三)
- jquery ajax error函数详解
- 多线程笔记(二)
- MySQL修改root密码的多种方法
- SparkSQL与Hive on Spark的比较
- CSS 布局模型
- 多线程笔记(三)
- Spark特征提取---TF-IDF
- 1014 装箱问题
- Android systrace使用
- Parameter index out of range (1 > number of parameters,which is 0).
- 奥迪宝马爆奔驰?
- dubbo组成原理-http服务消费端如何调用
- Go语言网络编程
- sublime text 3 录制并使用宏 自动在行尾加分号