一、阻塞队列
概念:
本身是一种队列(先进先出)数据结构,和其他队列比起来,多了阻塞机制,从而可以在多个线程之间进行存取队列的操作,而不会有线程并发安全问题.所以称之为阻塞式队列。可以简单的理解为,阻塞式队列是专门设计用来在多个线程间通过队列共享数据。
运行原理:
在阻塞式队列中,如果队列满了,仍然有线程向其中写入数据,则这次写入操作会被阻塞住,直到有另外的线程从队列中消费了数据,队列有了空间,阻塞才会被放开,写入操作才可以执行。
同样,如果队列是空的,仍然有线程从队列中获取数据,则读取操作将会被阻塞住,直到有另外的线程向队列中写入了数据,队列不再为空,阻塞才会被放开,读取操作才可以执行。
主要的方法:
抛出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
· 在BlockingQueue中 插入 移除 和 检查数据 有四组方法,这四组方法在处理 队列满的时候插入 队列空的时候获取 时 会有不同的处理机制包括:
抛出异常
返回特殊值
产生阻塞
其中产生阻塞 但是阻塞具有超时时间 一旦超过指定时间 阻塞自动放开。在使用BlockingQueue过程中 可以根据需要选择对应的方法。
二、ConcurrentMap-并发Map
概述:
ConcurrentMap是一个线程安全的Map,可以防止多线程并发安全问题。
HashTable也是线程安全的,但是ConcurrentMap性能要比HashTable好的多,所以推荐使用ConcurrentMap。
Hashtable和ConcurrentMap性能的比较:
1.锁更加精细
Hashtable的加锁是当线程操作HashMap时为整个HashMap加锁,其他线程无法对Map进行操作。
ConcurrentMap将锁加在分桶上,只锁所要操作的部分数据,不影响其他的操作,执行效率高。
2.引入了读写锁机制
在多线程并发操作的过程中,多个并发操作的读不需要隔离,只要有写的操作就需要隔离;
HashTable没有考虑到这一点,只要有操作就给整个HashMap加锁。
ConcurrentMap区分了读写的操作,即读的时候加读锁,写的时候加写锁。读锁和读锁不互斥,可以共存;写锁和任意锁互斥,这样就达到了读写操作分离的效果,从而极大的提高了开发的效率。
三、CountDownLatch闭锁
概述:
是Concurrent包提供的一种新的并发的构造。可以协调线程的执行过程,协调某个线程阻塞到其他线程达到一定条件时放开阻塞继续执行的效果。
主要API:
构造方法,需要在构造的过程中直接传入一个数字作为闭锁的计数器的初始值,构造闭锁。
构造方法摘要:
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。
在闭锁上调用此方法,可以阻塞当前线程,阻塞到CountDownLatch中的计数器count值变为0,自动放开阻塞。
void await()
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
调用此方法可以将闭锁中的计数器数值-1,如果减到零,await的阻塞会自动放开
void countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
案例:
下面我们就用一个小小的案例来讲解。做饭线程 需要等到 买锅 买米 买菜的线程 执行完成后 才能执行
public class CountDownLatchDemo01 {
public static void main(String[] args) {
//1.创建闭锁 计数器初始值设置为3
CountDownLatch cdl = new CountDownLatch(3);
new Thread(new MaiGuo(cdl)).start();
new Thread(new MaiMi(cdl)).start();
new Thread(new MaiCai(cdl)).start();
new Thread(new ZuoFan(cdl)).start();
}
}
class ZuoFan implements Runnable{
private CountDownLatch cdl = null;
public ZuoFan(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try {
//--调用await等待 达到执行条件
cdl.await();
System.out.println("开始做饭...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MaiGuo implements Runnable{
private CountDownLatch cdl = null;
public MaiGuo(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("锅买回来了...");
//--在闭锁上-1
cdl.countDown();
}
}
class MaiCai implements Runnable{
private CountDownLatch cdl = null;
public MaiCai(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("菜买回来了...");
//--在闭锁上-1
cdl.countDown();
}
}
class MaiMi implements Runnable{
private CountDownLatch cdl = null;
public MaiMi(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("米买回来了...");
//--在闭锁上-1
cdl.countDown();
}
}
· 执行的效果就是锅、菜、米所有都买回来以后,才能开始做饭。
四、CyclicBarrier-栅栏
概述:
Concurrent包提供的一种并发构造。可以实现多个线程并发进行操作时,在某一结点进行阻塞,直到所有线程到达指定的位置后,一起放开阻塞一起运行的效果。
重要API:
构造方法,接收一个初始值,指定了栅栏要等待的线程的数量
构造方法摘要:
CyclicBarrier(int parties)
创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
当线程到达了栅栏时,可以调用此方法,进入阻塞等待的状态,线程被挂起,直到在栅栏上等待的线程的数量达到了栅栏上设定的要等待的线程的数量,所有线程的阻塞同时被放开,一起继续执行。
int await()
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
案例:
public class CyclicBarrierDemo01 {
public static void main(String[] args) {
//构造栅栏,指定要等待的线程的数量
CyclicBarrier cb = new CyclicBarrier(5);
new Thread(new Horse(cb)).start();
new Thread(new Horse(cb)).start();
new Thread(new Horse(cb)).start();
new Thread(new Horse(cb)).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Horse(cb)).start();
}
}
class Horse implements Runnable{
private CyclicBarrier cb = null;
public Horse(CyclicBarrier cb) {
this.cb = cb;
}
@Override
public void run() {
System.out.println("马到达了栅栏,开始等待...");
try {
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("马跑了出去...");
}
}
五、 ExecutorService-执行器服务
概述:
ExecutorServive时Concurrent包提供的接口。主要用来实现:线程池。
所谓池就是对用来存储重用对象的集合,可以减少对象创建和销毁时造成的资源消耗。由于线程是重量级的,所以在线程的创建和销毁的过程中是十分消耗资源的,如果需要频繁并且大量的使用线程的话,不建议每次创建和销毁线程,而是利用线程池的机制,实现线程对象的共享,提升程序的效率。
ExecutorService用法-通过ThreadPoolExecutor实现类的创建线程池
构造方法:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler)
用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor。
参数的意义:
corePoolSize:核心的线程的数量。
maximumPoolSize:总的可以连接的最大线程数(corePoolSize+workQueue中的等待数量+临时的线程数量(该临时的线程在执行完毕后立即销毁))。
BlockingQueue workQueue:在执行的线程达到corePoolSize时,将还没有执行的任务先放入此队列中。
long keepAliveTime, TimeUnit unit:long keepAliveTime是时间的数字,TimeUnit unit是时间的单位。当所有线程都闲置下来,如果超过了这个时间,就会将临时线程(corePoolSize+workQueue个数还需要处理的任务而创建出来的线程)销毁。
RejectedExecutionHandler handler:当线程中处理的线程加上等待的线程,加上临时的线程的数量如果超过了maximumPoolSize,就会用handler去处理。
线程池的工作方式:
为了更加直观的看到线程池的工作方式,在这里用图的方式描述线程池的工作原理。
· 在线程池刚创建出来时,线程池中没有任何线程,当有任务提交过来时,如果线程池中管理的线程的数量小于corePoolSize,则无论是否有闲置的线程都会创建新的线程来使用.而当线程池中管理的线程的数量达到了corePoolSize,再有新任务过来时,会复用闲置的线程。
当所有的核心池大小中的线程都在忙碌,则再有任务提交,会存入workQueue中,进行排队,当核心池大小中的线程闲置后,会自动从workQueue获取任务执行。
而当所有的核心池大小中的线程都在忙碌,workQueue也满了,则会再去创建新的临时线程来处理提交的任务,但是,无论如何,总的线程数量,不允许超过maximumPoolSize
· 而当所有的核心池大小中的线程都在忙碌,workQueue也满了,也创建了达到了maximumPoolSize的临时线程,再有任务提交,此时会交予RJHandler来拒绝该任务
· 当任务高峰过去,workQueue中的任务也都执行完成,线程也依次闲置了下来,则在此时,会将闲置时间超过keepAliveTime(单位为unit)时长的线程关闭掉,但是关闭时会至少保证线程池中管理的线程的数量 不少于corePoolSize个
通过Executor工具类的静态方法创建线程池:
API:
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们
newCachedThreadPool()
一个擅长处理 大量短任务的线程池
corePoolSize=0
maximumPoolSize = Integer.MaxValue
keepAliveTime = 60
TimeUnit = Seconds
· newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
可以实现使用单一线程处理任务,多个任务在无界的阻塞式队列中排队等待处理
corePoolSize=1
maximumPoolSize = 1
workQueue = new LinkedBlockingQueue())
· newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
可以实现使用指定数量的线程处理任务,多个任务在无界的阻塞式队列中排队等待处理
corePoolSize=nTHreads
maximumPoolSize = nTHreads
workQueue = new LinkedBlockingQueue())
示例:
public class ExecutorServiceDemo01 {
public static void main(String[] args) {
//1.手动创建线程池
ExecutorService s1 = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5)
, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("不好意思,线程池实在是太忙了..您这个任务只能给你拒绝了..["+r+"]");
}
});
//2.利用工具类的静态方法快速创建线程池
ExecutorService s2 = Executors.newCachedThreadPool();
ExecutorService s3 = Executors.newSingleThreadExecutor();
ExecutorService s4 = Executors.newFixedThreadPool(5);
}
}
六、向线程池中提交任务
execute(Runnable)
最普通的提交任务的方法,直接传入一个Runnable接口的实现类对象,即可要求线程池取执行这个任务,这种方式提交的任务无法监控线程的执行 也无法在线程内向调用者返回返回值。
execute(Runnable command) 在未来某个时间执行给定的命令。
案例:
s.execute(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("run.."+i);
}
}
});
submit(Runnable)
此方法也可以通过传入一个Runnable接口实现类对象的方式来向线程池提交任务,不同之处在于此方法带有一个Future类型的返回值,可以通过Future对象的get()方法来检测线程是否执行结束,如果线程未执行结束get()方法将会阻塞。
案例:
Future<?> future = s.submit(new Runnable() {
@Override
public void run() {
try {
for(int i=0;i<10;i++){
System.out.println("run.."+i);
}
Thread.sleep(5000);
System.out.println("Runnable结束了..");
} catch (Exception e) {
e.printStackTrace();
}
}
});
future.get();
System.out.println("main线程走下来了...");
submit(Callable)
和上面submit(Runnable)方法非常类似,只不过这个方法传入的是Callable接口的实现类,Callable接口功能和Runnable接口基本一致,唯一的不同在于,内部的方法叫call,且可以返回返回值,这个返回值可以通过Future对象通过get()方法得到。
案例:
Future<String> future = s.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("子线程开始执行了..");
Thread.sleep(2000);
System.out.println("子线程执行结束了..");
return "aaabbbccc";
}
});
String str = future.get();
System.out.println(str);
invokeAny(…) 可以接收若干Callable组成的集合,此方法将会自动从中选择任意一个执行
案例:
List<Callable> list = new ArrayList<>();
list.add(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "aaa";
}
});
list.add(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "bbb";
}
});
list.add(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "ccc";
}
});
String str = s.invokeAny((Collection<? extends Callable<String>>) list);
System.out.println(str);
invokeAll(…) 可以接收若干Callable组成的集合,此方法将会执行所有的Callable将结果组成集合返回。
案例:
List<Callable> list = new ArrayList<>();
list.add(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "aaa";
}
});
list.add(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "bbb";
}
});
list.add(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "ccc";
}
});
List<Future<String>> rs = s.invokeAll((Collection<? extends Callable<String>>) list);
for(Future<String> f : rs){
System.out.println(f.get());
}
七、关闭线程池
· 线程池中维护了大量线程,很耗费资源,所以当使用线程池结束时,应该手动关闭线程池,释放资源。
· 关闭线程池的方法,即使调用也不会立即关闭所有线程,而是不再接收新的任务,之前已经提交但尚未完成执行的线程仍然会继续执行,直到所有的任务都执行完,线程池关闭所有线程,退出。
shutdown()
· 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
shutdownNow()
试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
八、Lock锁概述
· java.util.concurrent.locks.Lock 是一个类似于 synchronized 块的线程同步机制.但是 Lock比 synchronized 块更加灵活、精细。
重要方法
构造方法创建一把锁:
· ReentrantLock()
创建一个 ReentrantLock 的实例。
ReentrantLock(boolean fair)
创建一个具有给定公平策略的 ReentrantLock。
锁住锁:
void lock()
打开锁:
void unlock()
案例:
public class LockDemo2 {
public static String name = "夏洛";
public static String gender = "男";
//--创建锁对象
public static void main(String[] args) {
Lock lock = new ReentrantLock(true);
new Thread(new ChangeThread2(lock)).start();
new Thread(new PrintThread2(lock)).start();
}
}
class ChangeThread2 implements Runnable{
private Lock lock = null;;
public ChangeThread2(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
while(true){
lock.lock();
if("夏洛".equals(LockDemo2.name)){
LockDemo2.name = "马冬梅";
LockDemo2.gender = "女";
}else{
LockDemo2.name = "夏洛";
LockDemo2.gender = "男";
}
lock.unlock();
}
}
}
class PrintThread2 implements Runnable{
private Lock lock = null;
public PrintThread2(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
while(true){
lock.lock();
System.out.println("姓名:"+LockDemo2.name);
System.out.println("性别:"+LockDemo2.gender);
lock.unlock();
}
}
}
lock和syncronized对别:
lock可以配置公平策略,实现线程按照先后顺序获取锁。提供了trylock方法 可以试图获取锁 获取到 或 获取不到时 返回不同的返回值 让程序可以灵活处理。
lock()和unlock()可以在不同的方法中执行,可以实现同一个线程在上一个方法中lock()在后续的其他方法中unlock(),比syncronized灵活的多
读写锁
对于多线程并发安全问题,其实只在设计到并发写的时候才会发生,多个并发的读并不会有线程安全问题,所以在Concurrent包中提供了读写锁的机制,可以实现,分读锁和写锁来进行并发控制。多个读锁可以共存,而写锁和任意锁都不可共存,从而实现多个并发读并行执行提升效率,而任意时刻写都进行隔离,保证安全。这是一种非常高效而精细的锁机制。
重要API
构造方法:
ReentrantReadWriteLock()
使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock。
ReentrantReadWriteLock(boolean fair)
使用给定的公平策略创建一个新的 ReentrantReadWriteLock。
得到读写锁内部的读锁:
readLock()
返回用于读取操作的锁。
得到读写所内部的写锁:
writeLock()
返回用于写入操作的锁。
案例:
public class ReadWriteLockDemo01 {
public static String name = "夏洛";
public static String gender = "男";
public static void main(String[] args) {
ReadWriteLock rwLock = new ReentrantReadWriteLock();
new Thread(new Thread_A(rwLock)).start();
new Thread(new Thread_B(rwLock)).start();
new Thread(new Thread_C(rwLock)).start();
}
}
class Thread_C implements Runnable{
private ReadWriteLock rwLock = null;
public Thread_C(ReadWriteLock rwLock) {
this.rwLock = rwLock;
}
@Override
public void run() {
rwLock.writeLock().lock();
if("夏洛".equals(ReadWriteLockDemo01.name)){
LockDemo01.name = "马冬梅";
LockDemo01.gender = "女";
}else{
LockDemo01.name = "夏洛";
LockDemo01.gender = "男";
}
rwLock.writeLock().unlock();
}
}
class Thread_B implements Runnable{
private ReadWriteLock rwLock = null;
public Thread_B(ReadWriteLock rwLock) {
this.rwLock = rwLock;
}
@Override
public void run() {
while(true){
rwLock.readLock().lock();
System.out.println("B:"+ReadWriteLockDemo01.name);
System.out.println("B:"+ReadWriteLockDemo01.gender);
rwLock.readLock().unlock();
}
}
}
class Thread_A implements Runnable{
private ReadWriteLock rwLock = null;
public Thread_A(ReadWriteLock rwLock) {
this.rwLock = rwLock;
}
@Override
public void run() {
while(true){
rwLock.readLock().lock();
System.out.println("A:"+ReadWriteLockDemo01.name);
System.out.println("A:"+ReadWriteLockDemo01.gender);
rwLock.readLock().unlock();
}
}
}