JUC:java.util.concurrent包,简称JUC。作者是美国Doug Lea。
一、进程与线程
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。
二、并行与并发
并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行
三、创建线程的三种方式
1、继承Thread类
public class TortoiseThread extends Thread{
@Override
public void run() {
while(true){
System.out.println(this.getName()+" "+this.getPriority()+"乌龟领先了,加油!");
}
}
}
public class Test {
public static void main(String[] args) {
//乌龟
Runnable target;
Thread thread = new TortoiseThread();
//thread.run(); //运行方法
thread.start(); //启动线程
//兔子
while(true){
System.out.println(Thread.currentThread().getName()+" "
+Thread.currentThread().getPriority()+"兔子领先了,加油!");
}
}
}
2、实现Runnable接口
public class TortoiseRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName()+" "
+Thread.currentThread().getPriority()+"乌龟领先了,加油");
}
}
}
public class Test {
public static void main(String[] args) {
Thread.currentThread().setName("兔子线程");
Thread.currentThread().setPriority(8);
//乌龟
Runnable runnable = new TortoiseRunnable();
Thread thread = new Thread(runnable,"乌龟线程");
thread.setPriority(10);
thread.setName("乌龟线程");
thread.start(); //启动线程
//兔子
while(true){
System.out.println(Thread.currentThread().getName()+" "
+Thread.currentThread().getPriority()+"兔子领先了,加油!");
}
}
}
3、实现Callable接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(5000);
TimeUnit.SECONDS.sleep(5);
return new Random().nextInt(10);
}
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.isDone());
Integer result = futureTask.get(); //阻塞直到得到结果
//Integer result = futureTask.get(3,TimeUnit.SECONDS); //只等3秒
System.out.println(futureTask.isDone());
System.out.println(result);
}
}
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable =()->{
return new Random().nextInt(10);
};
FutureTask<Integer> task = new FutureTask(()->{
return new Random().nextInt(10);
});
/*Thread thread = new Thread(task);
thread.start();*/
new Thread(task).start();
Integer result = task.get();
System.out.println(result);
}
}
**Runnable接口与Callable接口的区别**:
相同点:都是接口,都可以编写多线程程序,窦彩勇Thread.start()启动线程
不同点: ①具体方法不同,一个是run,一个是call
②Runnable没有返回值,Callable可以返回执行结果,是个泛型
③Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛<可以将检查时异常转化为运行时异常抛出>
④Callable接口提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
四、锁 (线程同步)
(1)无锁售票
class Ticket{
private Integer num = 200;
public Integer getNum(){
return num;
}
public void sale(){
//无票可卖
if(num<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"开始卖票,"+num);
try {
Thread.sleep(10); //当前线程让出CPU进入阻塞状态,其他线程就会获取CPU,为了模拟线程切换,暴露线程同步问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票完毕,还剩"+--num);
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Runnable runnable = ()->{
while(true){
ticket.sale();
if(ticket.getNum()<=0){
break;
}
}
};
//三个卖票窗口
new Thread(runnable,"AAA").start();
new Thread(runnable,"BBB").start();
new Thread(runnable,"CCC").start();
}
}
(2)同步锁 synchronized
如果执行完毕或者线程执行异常,JVM会自动释放锁
①同步代码块
class Ticket{
private Integer num = 20;
public Integer getNum(){
return num;
}
public void sale(){
//此处省略100句
/**
* 必须是引用数据类型,不能是基本数据类型
* 在同步代码块中可以改变锁对象的值,不能改变其引用
* //改变引用就变成了另一个对象,但改变属性值还是一个对象
* 尽量不要String和包装类做锁对象,如果用了,只要保证代码块中不对其进行任何操作也没有关系
* 建议用final修饰锁对象
*/
synchronized (this){ //同步监视器 this==Ticket
//无票可卖
if(num<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"开始卖票,"+num);
try {
Thread.sleep(10); //当前线程让出CPU进入阻塞状态,其他线程就会获取CPU,为了模拟线程切换,暴露线程同步问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票完毕,还剩"+--num);
}
//此处省略50句
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Runnable runnable = ()->{
while(true){
ticket.sale();
if(ticket.getNum()<=0){
break;
}
}
};
//三个卖票窗口
new Thread(runnable,"AAA").start();
new Thread(runnable,"BBB").start();
new Thread(runnable,"CCC").start();
}
}
②同步方法
class Ticket{
private Integer num = 200;
public Integer getNum(){
return num;
}
public synchronized /*(static)*/ void sale(){ //实例方法同步监视器是this(存在于堆中);如果是static方法,同步监视器是Ticket.class(存在于方法区中)
//此处省略100句
//无票可卖,退出程序
if(num<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"开始卖票,"+num);
try {
Thread.sleep(10); //当前线程让出CPU进入阻塞状态,其他线程就会获取CPU,为了模拟线程切换,暴露线程同步问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票完毕,还剩"+--num);
//此处省略50句
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Runnable runnable = ()->{
while(true){
ticket.sale();
if(ticket.getNum()<=0){
break;
}
}
};
//三个卖票窗口
new Thread(runnable,"AAA").start();
new Thread(runnable,"BBB").start();
new Thread(runnable,"CCC").start();
}
}
同步代码块的效率要比同步方法的效率高
在JDK6之后引入偏向锁和轻量级锁,无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
在markword中标记锁的类型,普通对象在内存中的结构分为多部分,第一部分称为markwork,共64位。在对应锁对象的markword字段的低位字段标记锁的类型。
(3)lock锁
/**
* 使用Lock必须在try{}catch{}块中进行,并且**将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生
*/
class Ticket{
private Integer num = 200;
//购买一把锁
private Lock lock = new ReentrantLock();
public Integer getNum(){
return num;
}
public void sale(){
//此处省略100句
//上锁
lock.lock();
//boolean flag = lock.tryLock(); //有返回值,尝试获取,无论成败都会返回true或false
//boolean flag2 = lock.tryLock(5, TimeUnit.SECONDS);
//lock.lockInterruptibly(); //中断某个线程的等待过程
try {
//无票可卖,退出程序
if(num<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"开始卖票,"+num);
try {
Thread.sleep(10); //当前线程让出CPU进入阻塞状态,其他线程就会获取CPU,为了模拟线程切换,暴露线程同步问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票完毕,还剩"+--num);
}finally {
//解锁
lock.unlock();
}
//此处省略200句
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Runnable runnable = ()->{
while(true){
ticket.sale();
if(ticket.getNum()<=0){
break;
}
}
};
//三个卖票窗口
new Thread(runnable,"AAA").start();
new Thread(runnable,"BBB").start();
new Thread(runnable,"CCC").start();
}
}
(4)可重入锁 ReentrantLock 默认非公平的,可重入锁可一定程度避免死锁
class Ticket{
private Integer num = 200;
//购买一把锁
//private ReentrantLock lock = new ReentrantLock(); //sync = new NonfairSync(); 默认非公平锁
private ReentrantLock lock = new ReentrantLock(true);
public Integer getNum(){
return num;
}
public void sale(){
//此处省略100句
//上锁
lock.lock();
try {
//无票可卖,退出程序
if(num<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"开始卖票,"+num);
try {
Thread.sleep(10); //当前线程让出CPU进入阻塞状态,其他线程就会获取CPU,为了模拟线程切换,暴露线程同步问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票完毕,还剩"+--num);
check();
}finally {
//解锁
lock.unlock();
}
//此处省略200句
}
private void check(){
lock.lock();
try {
System.out.println("检查余票。。。。"+lock.getHoldCount());
check();
} finally {
lock.unlock();
}
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Runnable runnable = ()->{
while(true){
ticket.sale();
if(ticket.getNum()<=0){
break;
}
}
};
//三个卖票窗口
new Thread(runnable,"AAA").start();
new Thread(runnable,"BBB").start();
new Thread(runnable,"CCC").start();
}
}
(5)ReentrantReadWriteLock读写锁
读写锁特点:①写写不可并发 ②读写不可并发 ③读读可以并发
public class TestReentrantReadWriteLock {
public static void main(String[] args) {
//ReentrantReadWriteLock : 可重入的。默认非公平的
ReadWriteLock rwlock = new ReentrantReadWriteLock();
/**
* 多次返回的都是同一把读锁或写锁
*/
Lock readLock = rwlock.readLock();
Lock readLock2 = rwlock.readLock();
System.out.println(readLock == readLock2); //true
Lock writeLock = rwlock.writeLock();
Lock writeLock2 = rwlock.writeLock();
System.out.println(writeLock == writeLock2); //true
}
}
引入可重入锁实现缓存
class MyCache {
private Map<String, String> cache = new HashMap();
private Lock lock = new ReentrantLock(); //不区分读写,都是独占
//写操作
public void put(String key, String value){
//上锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 开始写入!");
Thread.sleep(300);
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入成功!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
//读操作
public String get(String key){
//上锁
lock.lock();
String value =null;
try {
System.out.println(Thread.currentThread().getName() + " 开始读出!");
Thread.sleep(300);
value = cache.get(key);
System.out.println(Thread.currentThread().getName() + " 读出成功!" + value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
return value;
}
}
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
//准备好cache
MyCache myCache = new MyCache();
//启动多个线程
for (int i = 0; i < 5; i++) {
final int n = i;
new Thread(()->{
myCache.put("key"+n,"value"+n);
},"write"+i).start();
}
for (int i = 0; i < 5; i++) {
final int n = i;
new Thread(()->{
myCache.get("key" + n);
},"read"+i).start();
}
}
}
读写锁使用
class MyCache {
private Map<String, String> cache = new HashMap();
//private Lock lock = new ReentrantLock(); //不区分读写,都是独占
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//写操作
public void put(String key, String value){
//上锁
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 开始写入!");
Thread.sleep(300);
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入成功!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
rwLock.writeLock().unlock();
}
}
//读操作
public String get(String key){
//上锁
rwLock.readLock().lock();
String value =null;
try {
System.out.println(Thread.currentThread().getName() + " 开始读出!");
Thread.sleep(300);
value = cache.get(key);
System.out.println(Thread.currentThread().getName() + " 读出成功!" + value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
rwLock.readLock().lock();
}
return value;
}
}
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
//准备好cache
MyCache myCache = new MyCache();
//启动多个线程
for (int i = 0; i < 5; i++) {
final int n = i;
new Thread(()->{
/* try {
Thread.sleep(n*100);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
myCache.put("key"+n,"value"+n);
},"write"+i).start();
}
for (int i = 0; i < 5; i++) {
final int n = i;
new Thread(()->{
myCache.get("key" + n);
},"read"+i).start();
}
}
}
锁降级指的是把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程,读写锁不支持读写锁。
5、线程通信
/**
* 必须先线程同步后线程通信
* 两个线程通信的方法必须使用同一个同步监视器,可以改变同步监视器属性的值,但是不能改变同步监视器引用的值
*/
class ShareData{
private Integer num = 0;
/*
增加1
*/
public synchronized void increment(){ //this
//判断
if(num == 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
num++;
try {
Thread.sleep(100); //释放CPU,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num);
//唤醒、通知
this.notify(); //随机唤醒一个等待的线程 目前只有两个
}
/*
减少1
*/
public synchronized void decrement(){ //this
//判断
if(num == 0){
try {
this.wait(); //释放CPU,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
num--;
try {
Thread.sleep(100); //释放CPU,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num);
//唤醒
this.notify();
}
}
public class TestThreadCommu {
public static void main(String[] args) {
//创建共享资源
ShareData shareData = new ShareData();
Runnable runnable1 = ()->{
for (int i = 0; i < 10; i++) {
shareData.increment();
}
};
//创建两个线程
new Thread(runnable1,"AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.decrement();
}
},"BBB").start();
}
}
** wait()和sleep()的区别联系**
①sleep():线程会让出CPU进入阻塞状态,但不会释放对象锁;wait():线程会让出CPU进入阻塞状态,也会放弃对象锁,进入等待此对象的等待锁定池
②wait只能在同步控制方法或同步控制块里面使用,而sleep可以在任何地方使用
**虚假唤醒
解决方法1:使用while而不是if
解决方法2:使用notifyAll()而不是notify()
class ShareData{
private Integer num = 0;
/*
增加1
*/
public synchronized void increment(){ //this
//判断
while(num == 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
num++;
try {
Thread.sleep(100); //释放CPU,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num);
//唤醒、通知
this.notifyAll(); //随机唤醒一个等待的线程 目前只有两个
}
/*
减少1
*/
public synchronized void decrement(){ //this
//判断
while(num == 0){
try {
this.wait(); //释放CPU,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
num--;
try {
Thread.sleep(100); //释放CPU,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num);
//唤醒
this.notifyAll();
}
}
public class TestThreadCommu {
public static void main(String[] args) {
//创建共享资源
ShareData shareData = new ShareData();
Runnable runnable1 = ()->{
for (int i = 0; i < 10; i++) {
shareData.increment();
}
};
//创建两个线程
new Thread(runnable1,"AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.decrement();
}
},"BBB").start();
new Thread(runnable1,"AAA").start();
new Thread(runnable1,"CCC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.decrement();
}
},"DDD").start();
}
}
**线程通信
调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
class ShareData{
private Integer num = 0;
private Lock lock = new ReentrantLock();
private Condition incrementCondition = lock.newCondition(); // A+C的队列
private Condition decrementCondition = lock.newCondition(); // B+D的队列
/*
增加1
*/
public void increment(){
lock.lock();
try {
//判断
while(num == 1){
try {
incrementCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
num++;
try {
Thread.sleep(100); //释放CPU,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num);
//唤醒、通知
incrementCondition.signal();
}finally {
lock.unlock();
}
}
/*
减少1
*/
public void decrement() {
lock.lock();
try {
//判断
while (num == 0) {
try {
decrementCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
num--;
try {
Thread.sleep(100); //释放CPU,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + num);
//唤醒
decrementCondition.signal();
} finally {
lock.unlock();
}
}
}
public class TestThreadCommu {
public static void main(String[] args) {
//创建共享资源
ShareData shareData = new ShareData();
Runnable runnable1 = ()->{
for (int i = 0; i < 10; i++) {
shareData.increment();
}
};
//创建两个线程
new Thread(runnable1,"AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.decrement();
}
},"BBB").start();
new Thread(runnable1,"AAA").start();
new Thread(runnable1,"CCC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.decrement();
}
},"DDD").start();
}
}
**定制化调用通信
/**
* 案例:
* 多线程之间按顺序调用,实现A->B->C。三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* 。。。打印10轮
*
* 分析实现方式:
*
* 1. 有一个锁Lock,3把钥匙Condition
* 2. 有顺序通知(切换线程),需要有标识位
* 3. 判断标志位
* 4. 输出线程名 + 内容
* 5. 修改标识符,通知下一个
*/
class ShareDataTwo{
private int num = 1; // 线程标识位,通过它区分线程切换
private Lock lock = new ReentrantLock();
private Condition condition5 = lock.newCondition();
private Condition condition10 = lock.newCondition();
private Condition condition15 = lock.newCondition();
public void print5(){
lock.lock();
try {
//判断
while(num != 1){
try {
condition5.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"打印"+i);
}
//唤醒
num = 2;
condition10.signal();
}finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
//判断
while (num != 2){
try {
condition10.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//操作
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+"打印"+i);
}
//唤醒
num = 3;
condition15.signal();
}finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
//判断
while(num != 3){
try {
condition15.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//操作
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"打印"+i);
}
//唤醒
num = 1;
condition5.signal();
}finally {
lock.unlock();
}
}
}
public class TestThreadCommu {
public static void main(String[] args) {
ShareDataTwo shareDataTwo = new ShareDataTwo();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareDataTwo.print5();
}
},"AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareDataTwo.print10();
}
},"BBB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareDataTwo.print15();
}
},"CCC").start();
}
}
6、阻塞队列(BlockingQueue)
被阻塞的原因:
①当队列满了的时候进行入队列操作
②当队列空了的时候进行出队列操作
java.util.concurrent 包里的 BlockingQueue是一个接口,继承Queue接口,Queue接口继承 Collection。
/**
*抛出异常
*/
public class TestBlockingQueue {
public static void main(String[] args) {
//创建一个阻塞队列
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue(3);
//对阻塞队列进行操作:抛出异常
blockingQueue.add(10);
blockingQueue.add(20);
blockingQueue.add(15);
//blockingQueue.add(25); //java.lang.IllegalStateException: Queue full
Integer element = blockingQueue.element();
System.out.println(element); //10
System.out.println(blockingQueue); //[10, 20, 15]
System.out.println(blockingQueue.remove()); //10
System.out.println(blockingQueue.remove()); //20
System.out.println(blockingQueue.remove()); //15
//System.out.println(blockingQueue.remove()); //java.util.NoSuchElementException
}
}
/**
* 特殊值
*/
public class TestBlockingQueue2 {
public static void main(String[] args) {
//创建一个阻塞队列
ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue(3);
//对阻塞队列进行操作:特殊值
System.out.println(blockingQueue.offer(10)); //true
System.out.println(blockingQueue.offer(20)); //true
System.out.println(blockingQueue.offer(15)); //true
//System.out.println(blockingQueue.offer(25)); //false
System.out.println(blockingQueue); //[10, 20, 15]
System.out.println(blockingQueue.peek()); //10 栈头元素
System.out.println(blockingQueue.poll()); //10
System.out.println(blockingQueue.poll()); //20
System.out.println(blockingQueue.poll()); //15
//System.out.println(blockingQueue.poll()); //null
//System.out.println(blockingQueue.peek()); //null
}
}
/**
* 超时
*/
public class TestBlockingQueue3 {
public static void main(String[] args) throws InterruptedException {
//创建一个阻塞队列
//ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue(3);
BlockingQueue blockingQueue = new LinkedBlockingQueue(3);
//对阻塞队列进行操作:超时
System.out.println(blockingQueue.offer(10,5, TimeUnit.SECONDS)); //true
System.out.println(blockingQueue.offer(20,5, TimeUnit.SECONDS)); //true
System.out.println(blockingQueue.offer(15,5, TimeUnit.SECONDS)); //true
System.out.println(blockingQueue.offer(25,5, TimeUnit.SECONDS)); //false 等5秒后false(如果5秒内有元素被取走就可以被放入)
System.out.println(blockingQueue); //[10, 20, 15]
System.out.println(blockingQueue.peek()); //10 栈头元素
System.out.println(blockingQueue.poll()); //10
System.out.println(blockingQueue.poll()); //20
System.out.println(blockingQueue.poll()); //15
System.out.println(blockingQueue.poll(5,TimeUnit.SECONDS)); //null 等5秒后null
}
}
/**
* 阻塞
*/
public class TestBlockingQueue4 {
public static void main(String[] args) throws InterruptedException {
//创建一个阻塞队列
ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue(3);
new Thread(()->{
Integer value = null;
try {
Thread.sleep(5000);
value = blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+value);
}).start();
//对阻塞队列进行操作:阻塞
blockingQueue.put(10); //true
blockingQueue.put(20); //true
blockingQueue.put(15); //true
//blockingQueue.put(25); //一直等待,直到被放进去
System.out.println(blockingQueue.take()); //10
System.out.println(blockingQueue.take()); //20
System.out.println(blockingQueue.take()); //15
//System.out.println(blockingQueue.take()); //一直等待,知道拿到
}
}
/* *
* SynchronousQueue不存储元素的阻塞队列,也即单个元素的队列
*/
public class TestSynchronousQueue {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new SynchronousQueue();
new Thread(()->{
Object val = null;
try {
Thread.sleep(5000);
val = blockingQueue.take();
System.out.println(Thread.currentThread().getName()+":"+val);
blockingQueue.put(100); //5秒后100
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
blockingQueue.put(10); //没有线程接就不放,放不进去
}
}
7、线程池(ThreadPool)
线程池:创建和销毁对象是非常耗费时间的。创建对象:需要分配内存等资源;销毁对象:虽然不需要程序员操心,但是垃圾回收器会在后台一直跟踪并销毁。对于经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:创建好多个线程,放入线程池中,使用时直接获取引用,不使用时放回池中。可以避免频繁创建销毁、实现重复利用
自JDK1.5起,提供了内置线程池
线程池的好处:①提高响应速度(减少了创建新线程的时间)
② 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
③提高线程的可管理性:避免线程无限制创建、从而销耗系统资源,降低系统稳定性,甚至内存溢出或者CPU耗尽
**① 执行没有返回值的Runnable命令
public class TestThreadPool1 {
public static void main(String[] args) {
//创建线程池
//ExecutorService pool = Executors.newSingleThreadExecutor(); //创建单个线程池
//ExecutorService pool = Executors.newFixedThreadPool(10); //创建固定数量的线程池
ExecutorService pool = Executors.newCachedThreadPool(); //创建缓存线程池
//使用线程池(执行大量没有返回值的Runnable命令)
for (int i = 0; i < 20; i++) {
final int n = i; //final可以省略,class文件里会自动优化
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("-----任务" + n + "开始------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----任务" + n + "结束------");
}
};
pool.execute(runnable);
//new Thread(runnable).start();
}
//关闭线程池
pool.shutdown();
}
}
**②执行有返回值的Callable任务
public class TestThreadPool2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
//ExecutorService pool = Executors.newFixedThreadPool(10);
//ExecutorService pool = Executors.newCachedThreadPool();
//使用线程池(执行大量有返回值的Callable任务)
List<Future> futureList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
int n = i;
Callable<Integer> callable = ()->{
System.out.println("------开始执行"+ n + "--------");
Thread.sleep(1000);
int result = new Random().nextInt(10);
System.out.println("------执行"+ n + "结束--------");
return result;
};
Future<Integer> future = pool.submit(callable);
futureList.add(future);
}
//获取结果
for (int i = 0; i < 20; i++) {
Future<Integer> future = futureList.get(i);
Integer result = future.get();
System.out.println(result+"!!!!");
}
//关闭线程池
pool.shutdown();
}
}
或者
public class TestThreadPool3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
//ExecutorService pool = Executors.newSingleThreadExecutor();
//ExecutorService pool = Executors.newFixedThreadPool(10);
ExecutorService pool = Executors.newCachedThreadPool();
//使用线程池(执行大量有返回值的Callable任务)
List<Future> futureList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
//int n = i;
Callable<Integer> callable = new MyCallable(i);
Future<Integer> future = pool.submit(callable);
futureList.add(future);
}
//获取结果
for (int i = 0; i < 20; i++) {
Future<Integer> future = futureList.get(i);
Integer result = future.get();
System.out.println(result+"!!!!");
}
//关闭线程池
pool.shutdown();
}
}
class MyCallable implements Callable<Integer>{
private Integer n;
public MyCallable(int n){
this.n = n;
}
@Override
public Integer call() throws Exception {
System.out.println("------开始执行"+ n + "--------");
Thread.sleep(1000);
int result = new Random().nextInt(10);
System.out.println("------执行"+ n + "结束--------");
return result;
}
}
底层原理:七个重要参数
**线程池底层工作原理
**拒绝策略
如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。
**自定义线程池
强制线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式
public class TestThreadPool3 {
public static void main(String[] args) {
// 自定义连接池
ExecutorService pool = new ThreadPoolExecutor(2, 5,
2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
//new ThreadPoolExecutor.AbortPolicy()
//new ThreadPoolExecutor.CallerRunsPolicy()
//new ThreadPoolExecutor.DiscardOldestPolicy()
//new ThreadPoolExecutor.DiscardPolicy()
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义拒绝策略");
}
}
);
try {
for (int i = 0; i < 19; i++) {
int n = i;
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行了业务逻辑"+n);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
八、JUC的辅助类
1、CountDownLatch
join
/** 2个同学陆续离开教室后值班同学才可以关门。
* 3个线程:
* 同学1线程
* 同学2线程
* 主线程:关门
*/
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread th1 = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个同学离开了");
});
Thread th2 = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二个同学离开了");
});
th1.start();
th2.start();
th1.join();
th2.join();
System.out.println("值班同学关门");
}
}
CountDownLatch
/**
* 6个同学陆续离开教室后值班同学才可以关门。
*/
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
//创建一个CountDownLatch,6个门闩
CountDownLatch latch = new CountDownLatch(6);
//开启六个线程,模拟6个同学离开
for (int i = 0; i < 6; i++) {
int n = i;
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+n+"个同学离开了");
latch.countDown();
}).start();
}
//值班同学关门
latch.await(); //如果门闩不等于0,就在此阻塞
System.out.println("值班同学关门");
}
}
public class TestCountDownLatch2 {
public static void main(String[] args) throws InterruptedException {
//创建一个CountDownLatch,6个门闩
CountDownLatch latch = new CountDownLatch(6);
ExecutorService pool = Executors.newCachedThreadPool();
//开启六个线程,模拟6个同学离开
for (int i = 0; i < 6; i++) {
int n = i;
pool.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+n+"个同学离开了");
latch.countDown();
});
}
//值班同学关门
latch.await(); //如果门闩不等于0,就在此阻塞
System.out.println("值班同学关门");
pool.shutdown();
}
}
**CountDownLatch 与 join 方法的区别**
①调用一个子线程的 join()方法后,该线程会一直被阻塞直到该线程运行完毕。而 CountDownLatch 则使用计数器允许子线程运行完毕或者运行中时候递减计数,也就是 CountDownLatch 可以在子线程运行任何时候让 await 方法返回而不一定必须等到线程结束
②使用线程池来管理线程时候一般都是直接添加 Runnable 到线程池这时候就没有办法在调用线程的 join 方法了,countDownLatch 相比 Join 方法让我们对线程同步有更灵活的控制。
2、CyclicBarrier
具有回环重置功能
/* *
* 排队做核酸10人一组
*/
public class TestCyclicBarrier2 {
public static void main(String[] args) {
//创建回环屏障
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
System.out.println(Thread.currentThread().getName()+"----------最后一个人拿着检查用品和大家一组过去--------------");
});
for (int i = 0; i < 40; i++) {
int n = i;
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"第"+ n +"个人等待做核酸:"+cyclicBarrier.getNumberWaiting());
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"Thread"+i).start();
}
}
}
所有的"过关了"都是由最后到达await方法的线程执行打印的。(直接调用Runnable的run方法,而不需要新开线程)
**CyclicBarrier和CountDownLatch的区别**
①CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
②CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
3、Semaphore
Semaphore翻译成字面意思为 信号量,Semaphore可以控制同时访问的线程个数。非常适合需求量大,而资源又很紧张的情况
/**
* 抢车位 ,6辆车抢3个车位
*/
public class TestSemaphore {
public static void main(String[] args) {
//准备3个车位
Semaphore semaphore = new Semaphore(3);
//6辆车进停车场
for (int i = 1; i < 6; i++) {
int n = i;
new Thread(()->{
System.out.println("进入停车场"+n);
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("待一会"+n);
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(6));
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();
System.out.println("离开停车场"+n);
}).start();
}
}
}
子任务执行完再执行总任务
/* *
* 6名同学值日后关门
*/
public class TestSemaphore2 {
public static void main(String[] args) throws InterruptedException {
//创建一个Semaphore
Semaphore semaphore = new Semaphore(0);
ExecutorService pool = Executors.newCachedThreadPool();
//开启六个线程,模拟6个同学离开
for (int i = 1; i <= 6; i++) {
int n = i;
pool.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + n + "个同学离开了");
semaphore.release();
});
}
//值班同学关门
semaphore.acquire(6);
System.out.println("值班同学关门");
pool.shutdown();
}
}