JUC概述
JUC简介
在Java中 线程部分是一个重点 , JUC也是关于线程的.JUC就是java.util.concurrent工具包的简称.这是一个处理线程的工具包JDK 1.5出现的
进程和线程
·进程:是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配和调度的基本单位,是操作系统结构的基础 在当代面向线程设计的计算机结构中 进程是线程的容器 程序是指令、数据及其组织形式的藐视,进程是程序的实体.是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配和调度的基本单位,是操作系统结构的基础.程序是指令、数据及其组织形式的描述,进程是程序的实体
·线程:是操作系统能够进行运算调度的最小单位 它被包含在进程之中 是进程中的实际运作单位 一条线程指的是进程中一个单一顺序的控制流 一个进程中可以并发多个线程 每条线程并行执行不同的任务
总结:
①进程:指在系统中正在运行的一个应用程序 程序一旦运行就是进程
进程-资源分配的最小单位
②线程:系统分配处理器时间资源的基本单元 或者说进程之内独立执行的一个单元执行流
线程-程序执行的最小单位
线程状态枚举类
NEW,(新建)
RUNNABLE,(准备就绪)
BLOCKED,(阻塞)
WAITING,(不见不散)
TIMED_WAITING,(过时不候)
TERMINATED;(终结)
wait/sleep 的区别
①sleep是Thread的静态方法 wait是Object的方法 任何对象都能调用
②sleep不会释放锁 它也不需要占用锁 wait会释放锁 但调用它的前提是当前
线程占有锁(即代码要在synchronized中)
③它们都可以被interrupted方法中断
并发和并行
1.串行模式
串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能 运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。
串行是一次只能取得一个任务,并执行这个任务。
2.并行模式
并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模 式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上 则依赖于多核 CPU。
3.并发
并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可 以同时运行或者多指令可以同时运行。
tips:
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:春运抢票 电商秒杀...
并行:多项工作一起执行,之后再汇总
例子:泡方便面,电水壶烧水,一边撕调料倒入桶中
管程(监视器、锁)
是一种同步机制 保证同一个时间 只有线程访问被保护的数据或代码
管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同 一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行
JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁
执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程
用户线程和守护线程
1.用户线程:平时用到的普通线程,自定义线程
2.守护线程:运行在后台,是一种特殊的线程,比如垃圾回收
3.当主线程结束后,用户线程还在运行,JVM 存活
4.如果没有用户线程,都是守护线程,JVM 结束
Lock接口
Synchronized
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep 方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。
Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。
线程间通信
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模 型来实现的。我们来基本一道面试常见的题目来分析
场景---两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信
代码:synchronized 方案
import java.util.concurrent.locks.ReentrantLock;
public class TestJUC {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.jiayi();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.jianyi();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
}
}
class Share{
//初始值
private int number = 0;
//+1的方法
public synchronized void jiayi() throws InterruptedException {
if(number!=0){
this.wait();//若number值不是0 等待
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其他线程
this.notifyAll();
}
//-1的方法
public synchronized void jianyi() throws InterruptedException {
if(number!=1){
this.wait();//若number值不是1 等待
}
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其他线程
this.notifyAll();
}
}
虚假唤醒: AA、BB、CC、DD 谁先执行不一定 比如AA先执行 通知其他线程时 可能BB抢到了也可能CC或者DD抢到了
对于某一个参数的方法 实现终端和虚假唤醒是可能的 而且此方法应始终在循环中使用 即: 判断条件放到if中会产生虚假唤醒
import java.util.concurrent.locks.ReentrantLock;
public class TestJUC {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.jiayi();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.jianyi();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.jiayi();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.jianyi();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
class Share{
//初始值
private int number = 0;
//+1的方法
public synchronized void jiayi() throws InterruptedException {
if(number!=0){
this.wait();//若number值不是0 等待
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其他线程
this.notifyAll();
}
//-1的方法
public synchronized void jianyi() throws InterruptedException {
if(number!=1){
this.wait();//若number值不是1 等待
}
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其他线程
this.notifyAll();
}
}
通常的解决方法是将if改为while
代码:Lock 方案
一般来说,当等待Condition时,允许发生“虚假的唤醒”,作为对底层平台语义的让步。这对大多数应用程序几乎没有实际影响,因为应该始终在循环中等待Condition,测试正在等待的状态谓词。实现可以自由消除虚假唤醒的可能性,但建议应用程序程序员始终假设它们可能发生,因此始终在循环中等待。 即:为了防止虚假唤醒 也需要用while而不是if
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo {
public static void main(String[] args) {
share s = new share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
s.jiayi();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
s.jianyi();
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
s.jiayi();
}
},"CC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
s.jianyi();
}
},"DD").start();
}
}
class share{
//初始值
private int number = 0;
//创建Lock
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void jiayi(){
lock.lock();
try {
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void jianyi(){
lock.lock();
try {
while (number!=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程间定制化通信
A 线程打印 5 次 ,B 线程打印 10 次 ,C 线程打印 15 次 ,按照此顺序循环 10 轮
代码:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo {
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();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
class ShareResource{
//定义标志位
private int flag = 1;//A1 B2 C3
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
// 参数:第几轮
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;//先修改标志位
c2.signal();//通知B
}finally {
lock.unlock();
}
}
// 参数:第几轮
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;//先修改标志位
c3.signal();//通知B
}finally {
lock.unlock();
}
}
// 参数:第几轮
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;//先修改标志位
c1.signal();//通知B
}finally {
lock.unlock();
}
}
}
集合的线程安全
以ArrayList为例
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/*
ArrayList、LinkedList 线程不安全
*/
public class ThreadEx {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
list.sout 发生的问题 为并发修改的问题 即 内容可能没放进去 或者有多个线程要放
解决方案-Vector(比较古老的方案)
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
/*
ArrayList、LinkedList 线程不安全
*/
public class ThreadEx {
public static void main(String[] args) {
List<String> vector = new Vector<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
vector.add(UUID.randomUUID().toString().substring(0,1));
System.out.println(vector);
},String.valueOf(i)).start();
}
}
}
add 方法被 synchronized 同步修辞,线程安全!因此没有并发异常
解决方案-Collections (比较古老的方案)
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/*
ArrayList、LinkedList 线程不安全
*/
public class ThreadEx {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Collections 提供了方法 synchronizedList 保证 list 是同步线程安全的
解决方案-CopyOnWriteArrayList(重要 JUC中提供的类 写时复制技术)
代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/*
ArrayList、LinkedList 线程不安全
*/
public class ThreadEx {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
①HashSet
同样出现 java.util.ConcurrentModificationException
解决方案: Set<String> hashSet = new CopyOnWriteArraySet<>();
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ThreadEx {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
hashSet.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
}
②HashMap
同样出现 java.util.ConcurrentModificationException
解决方案: Map<String,String> hashSet = new ConcurrentHashMap<>()
import java.util.*;
public class ThreadEx {
public static void main(String[] args) {
Map<String,String> hashSet = new HashMap<>();
for (int i = 0; i < 10; i++) {
String key = String.valueOf(i);
new Thread(()->{
hashSet.put(key,UUID.randomUUID().toString().substring(0,8));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
}
多线程锁
代码演示:
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
/**
* @Description: 8锁
*
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS
*/
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
// phone.sendEmail();
// phone.getHello();
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
分析:
①标准访问 短信邮件都是普通synchronized 、②停4秒在短信方法内
都是先短信后邮件 synchronized 锁的是当前的对象this
因为"AA").start();后Thread.sleep(100); 所以一定是先AA后BB
③新增普通的hello方法
没有synchronized 所以先执行
④两部手机
两个不同的对象 不是同一把锁 先邮件 因为短信等了四秒
⑤、⑥两个静态同步方法(1个、2个手机)
先短信后邮件 static synchronized 锁的是Class 当前类的大class
⑦、⑧1个静态同步方法,1个普通同步方法(1个、2个手机)
先邮件后短信 静态锁的是Class 非静态锁的是this 不是同一把锁 先邮件 短信等了四秒
公平锁和非公平锁
private ReentrantLock lock = new ReentrantLock();//无参构造器 默认非公平锁
private ReentrantLock lock = new ReentrantLock(true);//公平锁
private ReentrantLock lock = new ReentrantLock(false);//非公平锁
非公平锁: 缺点线程饿死 优点效率高
举例:比如卖票问题 可能出现AA一个人卖了100张 BB CC 一张都没卖
公平锁: 缺点效率相对低 优点雨露均沾 各个线程都有机会
可重入锁(也称 递归锁)
sychronized(隐式 自动加锁解锁)和Lock(显示 手动加锁解锁)都是可重入锁
代码:sychronized
public class SyncLockDemo {
public synchronized void add(){
add();
}
public static void main(String[] args) {
new SyncLockDemo().add();
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName() + " outside");
synchronized (o){
System.out.println(Thread.currentThread().getName() + " midside");
synchronized (o){
System.out.println(Thread.currentThread().getName() + " inside");
}
}
}
},"A").start();
}
}
add报错 synchronized不报错因为其是可重复锁
代码:lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncLockDemo {
public static void main(String[] args) {
Object o = new Object();
Lock lock = new ReentrantLock();
new Thread(()->{
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " O");
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " I");
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
},"A").start();
}
}
死锁
概念:两个或两个以上进程在执行过程中 因为争夺资源而造成一种互相等待的现象 若没有外力干涉 他们无法再执行下去
产生死锁的原因: ①系统资源不足 ②进行运行推进顺序不合适 ③资源分配不当
死锁的一个例子:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncLockDemo {
static Object A = new Object();
static Object B = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (A){
System.out.println(Thread.currentThread().getName() + " 持有A 视图B");
synchronized (B){
System.out.println(Thread.currentThread().getName() + "获取B");
}
}
},"A").start();
new Thread(()->{
synchronized (B){
System.out.println(Thread.currentThread().getName() + " 持有B 视图A");
synchronized (A){
System.out.println(Thread.currentThread().getName() + "获取A");
}
}
},"B").start();
}
}
验证是否死锁
①jps
②jstack jvm自带堆栈跟踪工具
创建线程的多种方式
①继承Thread类
②实现Runnable接口
③实现Callable接口
④线程池
代码:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
return 1233;
});
new Thread(futureTask2,"z").start();
System.out.println(futureTask2.get());
}
}
或者
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread2(),"BB").start(); 报错
MyThread2 myThread2 = new MyThread2();
FutureTask<Integer> futureTask = new FutureTask<>(myThread2);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
class MyThread2 implements Callable{
@Override
public Integer call() throws Exception {
return 200;
}
}
tips:代码实践发现实现接口才能写成Lambda,继承抽象类不行
FutureTask原理
1.老师上课 口渴了 去买票不合适 讲课线程继续
单开启线程 找同学帮老师买水
把水买回来 需要的时候直接喝(get)
2.4个同学 1同学1+2+3+4+5 2同学算10+....+50
3同学 算60+...62 4同学100+200
2同学计算量比较大 FutureTask单开线程给2同学计算 先汇总1 3 4 最后等
2同学计算完成 再统一汇总
3考试 先做会做的题 最后做不会做的
只计算一次
JUC的辅助类
演示:六个人陆续离开后 班长才关锁门 下面这种写法是不对的 应该是1-6走了锁门
public class Demo {
//六个人陆续离开后 班长才关锁门
public static void main(String[] args){
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"号同学离开了教室");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName()+"锁门");
}
}
解决办法:使用CountDownLatch辅助类
import java.util.concurrent.CountDownLatch;
public class Demo {
//六个人陆续离开后 班长才关锁门
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"号同学离开了教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待 若计数器值等于0 再往下走
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"锁门");
}
}
循环栅栏 CyclicBarrier
可以将 CyclicBarrier 理解为加 1 操作
例子:集齐7颗龙珠 召唤神龙
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
public class Demo {
//集齐7颗龙珠
private static final int NUMBER = 7;
public static void main(String[] args) {
//两个参数 第一个是固定值 第二个是达到固定值后 要做什么
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
System.out.println("YESYES");
});
for (int i = 1; i <=7 ; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"star(s)");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class Demo {
//抢车位, 6 部汽车 3 个停车位
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
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();
}
}
}
读写锁
读写锁介绍
代码:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Myche myche = new Myche();
//创建线程放数据
for (int i = 1; i <=5 ; i++) {
int num = i;
new Thread(()->{
myche.put(num+"",num+"");
},String.valueOf(i)).start();
}
//创建线程取数据
for (int i = 1; i <=5 ; i++) {
int num = i;
new Thread(()->{
myche.get(num+"");
},String.valueOf(i)).start();
}
}
}
class Myche{
private volatile Map<String,Object> map = new HashMap<>();
public void put(String key,Object value) {
System.out.println(Thread.currentThread().getName() + "正在写操作" + key);
//暂停一下
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写完了" + key);
}
public Object get(String key){
Object result = null;
System.out.println(Thread.currentThread().getName() + "正在取操作" + key);
//暂停一下
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = map.get(key);
System.out.println(Thread.currentThread().getName() + "取完了" + key);
return result;
}
}
这么写 很明显有问题 应该是先写 写完了再取
用读写锁优化后:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Myche myche = new Myche();
//创建线程放数据
for (int i = 1; i <=5 ; i++) {
int num = i;
new Thread(()->{
myche.put(num+"",num+"");
},String.valueOf(i)).start();
}
//创建线程取数据
for (int i = 1; i <=5 ; i++) {
int num = i;
new Thread(()->{
myche.get(num+"");
},String.valueOf(i)).start();
}
}
}
class Myche{
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private volatile Map<String,Object> map = new HashMap<>();
public void put(String key,Object value) {
//添加写锁
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "正在写操作" + key);
//暂停一下
try {
TimeUnit.MICROSECONDS.sleep(300);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写完了" + key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
public Object get(String key){
readWriteLock.readLock().lock();
Object result = null;
System.out.println(Thread.currentThread().getName() + "正在取操作" + key);
//暂停一下
try {
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + "取完了" + key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
return result;
}
}
写锁是独占锁 读锁是共享锁
一个线程可以被多个读线程访问 或者可以被一个写线程访问 但是不能同时存在读写线程,读写互斥,读读共享
锁降级
·为了提高数据的可见性 将写入锁 降级为读锁 读锁不能升级为写错
·遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
public static void main(String[] args) throws InterruptedException {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
//锁降级
writeLock.lock();
//1.获取写锁
System.out.println("111111");
//2.获取读锁
readLock.lock();
System.out.println("read");
//释放写锁
writeLock.unlock();
//释放读锁
readLock.unlock();
}
意思就是写操作后 写锁还没有unlock 也可以获取到读锁 但是如果先获取读锁 再获取写锁就不行了 因为读锁不能升级为 写锁
eg:增删改 写
查 读
阻塞队列
![](https://i-blog.csdnimg.cn/blog_migrate/48abeda05998fce53fdf229430bbf4f7.png)
BlockingQueue的常见方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
①抛出异常
1)当阻塞队列满时 再往队列里add插入元素会抛IllegalStateException:Queue full
2)当阻塞队列空时 再从队列里remove移除元素会抛NoSuchElementException
②特殊值
1)插入方法 成功true 失败false
2)移除方法 成功返回出队列的元素 队列里没有就返回null
③一直阻塞
1)当阻塞队列满时 生产者线程继续往队列里put元素 队列会一直阻塞生产者线程直
到put数据or相应中断退出
2)当阻塞队列空时 消费者线程试图从队列里take元素 队列会一直阻塞消费者线程直到队
列可用
④超时退出
当阻塞队列满时 队列会阻塞生产者线程一定时间 超过限时后生产者线程会退出
由数组结构组成的有界阻塞队列
·LinkedBlockingQueue(常用)
由链表结构组成的有界阻塞队列(大小默认值为Integer.MAX_VALUE)
·DelayQueue
使用优先级队列实现的延迟无界阻塞队列
· PriorityBlockingQueue
支持优先级排序的无界阻塞队列
·SynchronousQueue
不存储元素的阻塞队列,也即单个元素的队列。
·LinkedTransferQueue
由链表组成的无界阻塞队列。
·LinkedBlockingDeque
由链表组成的双向阻塞队列
代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class Demo {
public static void main(String[] args) throws InterruptedException {
//创建阻塞队列
BlockingQueue<String> blockingDeque = new ArrayBlockingQueue<>(3);
//第一组
System.out.println(blockingDeque.add("A"));
System.out.println(blockingDeque.add("B"));
System.out.println(blockingDeque.add("C"));
System.out.println(blockingDeque.remove());
System.out.println(blockingDeque.remove());
System.out.println(blockingDeque.remove());
//第二组
System.out.println(blockingDeque.offer("a"));
System.out.println(blockingDeque.offer("b"));
System.out.println(blockingDeque.offer("c"));
System.out.println(blockingDeque.poll());
System.out.println(blockingDeque.poll());
System.out.println(blockingDeque.poll());
//第三组 不会报错 会出现阻塞
blockingDeque.put("Q");
blockingDeque.put("W");
blockingDeque.put("3");
System.out.println(blockingDeque.take());
System.out.println(blockingDeque.take());
System.out.println(blockingDeque.take());
//第四组 阻塞 超时后会出退出
System.out.println(blockingDeque.offer("a"));
System.out.println(blockingDeque.offer("b"));
System.out.println(blockingDeque.offer("c"));
System.out.println(blockingDeque.offer("d",2L, TimeUnit.SECONDS));
}
}
线程池
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
例子: 10 年前单核 CPU 电脑,假的多线程,像马戏团小丑玩多个球,CPU 需要来回切换。 现在是多核电脑,多个线程各自跑在独立的 CPU 上,不用切换效率高。
线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池在构造前(new操作)是初始状态,一旦构造完成线程池就进入了执行状态RUNNING。严格意义上讲线程池构造完成后并没有线程被立即启动,只有进行“预启动”或者接收到任务的时候才会启动线程。但是线程池是出于运行状态,随时准备接受任务来执行。
![](https://i-blog.csdnimg.cn/blog_migrate/bed76d69ef3403b912f94531a0bed84e.png)
1.corePoolSize - 核心线程数,也即最小的线程数。
2.workQueue - 阻塞队列 。
3.maximumPoolSize - 最大线程数
①newCachedThreadPool(常用)
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
public static void main(String[] args){
//一个线程池 可扩容线程 比如模拟现在银行有5个窗口 但是有10个顾客
ExecutorService executorService = Executors.newCachedThreadPool();
//10个顾客请求
try {
for (int i = 1; i <=10 ; i++) {
//执行
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "在办理业务");
});
}
} catch (Exception e){
System.out.println(e.getMessage());
}finally {
executorService.shutdown();
}
}
}
特征:
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
public static void main(String[] args){
//一个线程池 N个线程 比如模拟现在银行有5个窗口 但是有10个顾客
ExecutorService executorService = Executors.newFixedThreadPool(5);
//10个顾客请求
try {
for (int i = 1; i <=10 ; i++) {
//执行
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "在办理业务");
});
}
} catch (Exception e){
System.out.println(e.getMessage());
}finally {
executorService.shutdown();
}
}
}
作用 :创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool 不同,可保证无需重新配置此方法所返回的执行程序即
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
public static void main(String[] args){
//一个线程池 1个线程 比如模拟现在银行有1个窗口 但是有10个顾客
ExecutorService executorService = Executors.newSingleThreadExecutor();
//10个顾客请求
try {
for (int i = 1; i <=10 ; i++) {
//执行
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "在办理业务");
});
}
} catch (Exception e){
System.out.println(e.getMessage());
}finally {
executorService.shutdown();
}
}
}
场景: 适用于需要多个后台线程执行周期任务的场景
场景: 适用于大耗时,可并行执行的场景
![](https://i-blog.csdnimg.cn/blog_migrate/7d21b5b1f4327ffff4910eb1d3f0865e.png)
2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
4.2 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
![](https://i-blog.csdnimg.cn/blog_migrate/f9c2e84557bd8e9dbed0bf6ff52ce69b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fe0e1cc6722276c315866f70f0363f8f.png)
import java.util.concurrent.*;
public class Demo {
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){
System.out.println(e.getMessage());
}finally {
threadPool.shutdown();
}
}
}
Fork/Join
Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。Fork/Join 框架要完成两件事情:
![](https://i-blog.csdnimg.cn/blog_migrate/6737e1e50352e3637489343a144a2f70.png)
1. 任务分割:首先 Fork/Join 框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
2. 执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程 分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里, 启动一个线程从队列里取数据,然后合并这些数据。
在 Java 的 Fork/Join 框架中,使用两个类完成上述操作
• ForkJoinTask:我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。
该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
a.RecursiveAction:用于没有返回结果的任务
b.RecursiveTask:用于有返回结果的任务
• ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行
• RecursiveTask: 继承后可以实现递归(自己调自己)调用的任务
Fork 方法
代码: 比如计算1+2+3+……+100 相加两数差不超过10 超过10就拆分
import java.security.Key;
import java.security.KeyStore;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask(0,100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);
//获取合并后的结果
Integer integer = submit.get();
System.out.println(integer);
//关闭池对象
forkJoinPool.shutdown();
}
}
class MyTask extends RecursiveTask<Integer>{
//拆分差值不能超过10
private static final Integer VALUE = 10;
private int left;//拆分开始值
private int right;//拆分结束值
private int result;//返回结果
//带参构造
public MyTask(int left, int right) {
this.left = left;
this.right = right;
}
//拆分和合并的过程
@Override
protected Integer compute() {
//判断相加两个数值是否大于10
if((right-left)<=VALUE){
//相加
for (int i = left; i <=right ; i++) {
result+=i;
}
}else {
//继续拆分
int mid = (left+right)/2;
//拆左边
MyTask myTask1 = new MyTask(left,mid);
//拆右边
MyTask myTask2 = new MyTask(mid+1,right);
//调用方法拆分
myTask1.fork();
myTask2.fork();
//合并结果
result = myTask1.join()+myTask2.join();
}
return result;
}
}
异步回调
同步与异步
同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//异步调用没有返回值
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName() + "1");
});
completableFuture1.get();
//异步调用有返回值
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "2");
return 1024;
});
completableFuture2.whenComplete((t,u)->{
System.out.println(t+ "t");//方法的返回值
System.out.println(u+ "u");//异常信息
}).get();
}
}