JUC
一、什么是JUC
在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的。JUC就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK1.5 开始出现的。
线程与进程
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程—
—资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个
单元执行流。线程——程序执行的最小单位。
线程的状态
线程通常有五种状态,创建,就绪,运⾏、阻塞和死亡状态:
- 新建状态(New):新创建了⼀个线程对象。
- 就绪状态(Runnable):线程对象创建后,其他线程调⽤了该对象的start⽅法。该状态的线程位于可运⾏线程池中,变得可运⾏,等待获取CPU的使⽤权。
- 运⾏状态(Running):就绪状态的线程获取了CPU,执⾏程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使⽤权,暂时停⽌运⾏。直到
- 死亡状态(Dead):线程执⾏完了或者因异常退出了run⽅法,该线程结束⽣命周期。
阻塞的情况⼜分为三种:
- 等待阻塞:运⾏的线程执⾏wait⽅法,该线程会释放占⽤的所有资源,JVM会把该线程放⼊“等待池”中。进⼊这个状态后,是不能⾃动唤醒的,必须依靠其他线程调⽤notify或notifyAll⽅法才能被唤醒,wait是object类的⽅法
- 同步阻塞:运⾏的线程在获取对象的同步锁时,若该同步锁被别的线程占⽤,则JVM会把该线程放⼊“锁池”中。
- 其他阻塞:运⾏的线程执⾏sleep或join⽅法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终⽌或者超时、或者I/O处理完毕时,线程重新转⼊就绪状
态。sleep是Thread类的⽅法
wait/sleep 的区别
(1)sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都
能调用。
(2)sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提
是当前线程占有锁(即代码要在 synchronized 中)。
(3)它们都可以被 interrupted 方法中断。
并发与并行
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:春运抢票 电商秒杀…
并行:多项工作一起执行,之后再汇总
用户线程和守护线程
用户线程:平时用到的普通线程,自定义线程
守护线程:运行在后台,是一种特殊的线程,比如垃圾回收
当主线程结束后,用户线程还在运行,JVM 存活
如果没有用户线程,都是守护线程,JVM 结束
Lock 接口
Synchronized
synchronized 是 Java 中的关键字,是一种同步锁。
售票案例:
class Ticket {
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale() {
//判断:是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" :
"+(number--)+" "+number);
}
}
}
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执
行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里
获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。
Lock 与的 Synchronized 区别
-
- Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
-
- synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
-
- Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
-
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
-
- Lock 可以提高多个线程进行读操作的效率。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。
Lock 接口源码
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
线程间通信
两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信
synchronized 方案
public class TestVolatile {
/**
* 交替加减
* @param args
*/
public static void main(String[] args){
DemoClass demoClass = new DemoClass();
new Thread(() ->{
for (int i = 0; i < 5; i++) {
demoClass.increment();
}
}, "线程 A").start();
new Thread(() ->{
for (int i = 0; i < 5; i++) {
demoClass.decrement();
}
}, "线程 B").start();
}
}
package com.atguigu.test;
class DemoClass{
//加减对象
private int number = 0;
/**
* 加 1
*/
public synchronized void increment() {
try {
while (number != 0){
this.wait();
}
number++;
System.out.println("--------" + Thread.currentThread().getName() + "加一成
功----------,值为:" + number);
notifyAll();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 减一
*/
public synchronized void decrement(){
try {
while (number == 0){
this.wait();
}
number--;
System.out.println("--------" + Thread.currentThread().getName() + "减一成
功----------,值为:" + number);
notifyAll();
}catch (Exception e){
e.printStackTrace();
}
}
}
Lock 方案
class DemoClass{
//加减对象
private int number = 0;
//声明锁
private Lock lock = new ReentrantLock();
//声明钥匙
private Condition condition = lock.newCondition();
/**
* 加 1
*/
public void increment() {
try {
lock.lock();
while (number != 0){
condition.await();
}
number++;
System.out.println("--------" + Thread.currentThread().getName() + "加一成
功----------,值为:" + number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 减一
*/
public void decrement(){
try {
lock.lock();
while (number == 0){
condition.await();
}
number--;
System.out.println("--------" + Thread.currentThread().getName() + "减一成
功----------,值为:" + number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
线程间定制化通信(重要)
newCondition
关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类也可以实现等待/通知模式。
- 用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以
进行选择性通知, Condition 比较常用的两个方法: - await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重
新获得锁并继续执行。 - signal()用于唤醒一个等待的线程。
A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮
//第一步 创建资源类
class ShareResource {
//定义标志位
private int flag = 1; // 1 AA 2 BB 3 CC
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while(flag != 1) {
//等待
c1.await();
}
//干活
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//通知
flag = 2; //修改标志位 2
c2.signal(); //通知BB线程
}finally {
//释放锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 2) {
c2.await();
}
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 3;
//通知CC线程
c3.signal();
}finally {
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 3) {
c3.await();
}
for (int i = 1; i <=15; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 1;
//通知AA线程
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
synchronized具体表现(重要)
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步代码块,锁是synchronized括号里配置的对象
集合的线程安全
ArrayList(线程不安全)
/**
* 多个线程同时对集合进行修改
* @param args
*/
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 100; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "线程" + i).start();
}
}
java.util.ConcurrentModificationException
Vector(线程安全)
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Collections 提供了方法 synchronizedList(线程安全)
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
HashMap和HashSet
同理通过 synchronized 关键字实现,效率较低
解决:
copyonwriteHashSet
concurrentHashMap
CopyOnWriteArrayList(重点) (线程安全)
- 独占锁效率低:采用读写分离思想解决
- 写线程获取到锁,其他写线程阻塞
- 复制思想:
- 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据。
多线程锁
一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的一个 synchronized 方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法
总结
具体表现为以下 3 种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的 Class 对象。
- 对于同步方法块,锁是 Synchonized 括号里配置的对象
Callable&Future 接口
Callable
不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable
Future 接口
当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Runnable接口创建线程
new Thread(new MyThread1(),"AA").start();
//Callable接口,报错
// new Thread(new MyThread2(),"BB").start();
//FutureTask
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
//lam表达式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName()+" come in callable");
return 1024;
});
//创建一个线程
new Thread(futureTask2,"lucy").start();
new Thread(futureTask1,"mary").start();
// while(!futureTask2.isDone()) {
// System.out.println("wait.....");
// }
//调用FutureTask的get方法
System.out.println(futureTask2.get());
System.out.println(futureTask1.get());
System.out.println(Thread.currentThread().getName()+" come over");
}
JUC 三大辅助类
CountDownLatch: 减少计数
CountDownLatch 主要有两个方法,
- 当一个或多个线程调用 await 方法时,这些线程会阻塞
- 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
- 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
//演示 CountDownLatch
public class CountDownLatchDemo {
//6个同学陆续离开教室之后,班长锁门
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室之后
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
//计数 -1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
}
}
循环栅栏 CyclicBarrier
CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作。
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier =
new CyclicBarrier(NUMBER,()->{
System.out.println("*****集齐7颗龙珠就可以召唤神龙");
});
//集齐七颗龙珠过程
for (int i = 1; i <=7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
信号灯 Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方法获得许可证,release 方法释放许可
//6辆汽车,停3个车位
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置许可数量
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 抢到了车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+" ------离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
读写锁
一个资源可以被多个读线程访问或者一个写线程访问,不能读写同时进行,读读共享,读写互斥。
//资源类
class MyCache {
//创建map集合
private volatile Map<String,Object> map = new HashMap<>();
//创建读写锁对象
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//放数据
public void put(String key,Object value) {
//添加写锁
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放写锁
rwLock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
//添加读锁
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放读锁
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
TimeUnit.MICROSECONDS.sleep(300);
//创建线程取数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
锁降级
//演示读写锁降级
public class Demo1 {
public static void main(String[] args) {
//可重入读写锁对象
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
//锁降级
//1 获取写锁
writeLock.lock();
System.out.println("atguigu");
//2 获取读锁
readLock.lock();
System.out.println("---read");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
}
}
阻塞队列
BlockingQueue 核心方法
//阻塞队列
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
//创建阻塞队列
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//第一组
// System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//System.out.println(blockingQueue.element());
//System.out.println(blockingQueue.add("w"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//第二组
// System.out.println(blockingQueue.offer("a"));
// System.out.println(blockingQueue.offer("b"));
// System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("www"));
//
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
//第三组
// blockingQueue.put("a");
// blockingQueue.put("b");
// blockingQueue.put("c");
// //blockingQueue.put("w");
//
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
//第四组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("w",3L, TimeUnit.SECONDS));
}
}
线程池
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。
//演示线程池三种常用分类
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//一池五线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
//一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
//一池可扩容线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool3.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool3.shutdown();
}
}
火车站 3 个售票口, 10 个用户买票
//自定义线程池创建
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}
}
}