JUC并发编程
解释 | 归属 | 备注 | |
---|---|---|---|
util | |||
Callable | 可赎回 | concurrent | |
concurrent | 并发 | ||
TimeUnit | 时间单位 | ||
DAYS | 天 | TimeUnit | |
SECONDS | 秒 | TimeUnit | |
Condition | 条件,精准通知 | lock | |
Reentrant | 可重入的 | lock | |
notify all | 通知所有人 | synchronized | |
await | 等待(加锁) | Condition | |
signal | 信号(解锁) | Condition | |
Vector | 矢量 | 集合锁 | |
Collections | 收藏 | 集合锁 | |
CopyOnWriteArrayList<> | 写入时复制 | 集合锁 | |
call | 呼叫 | Callable | |
Future tas | 适配类 | Runnable | |
CountDownLatch | 减计数器(倒计时器) | 辅助类 | |
CyclicBarrier | 加法计数器 | 辅助类 | |
Semaphore | 信号量 | 辅助类 | |
acquire() | 得到 | Semaphore | |
release( | 释放 | Semaphore | |
ReadWriteLock | 读写锁 | lock | |
Queue | 排队 | ||
lockingQueue | 阻塞 | 阻塞队列 | |
ArrayDeque | 双端队列 | 阻塞队列 | |
BlockingQueue | 阻塞队列 | FIFO | |
Array | 数组 | ||
remove | 去除 | ArrayBlockingQueue | 抛出异常,boolean |
element | 首元素 | ArrayBlockingQueue | 抛出异常,boolean |
offer | 给与 | ArrayBlockingQueue | 不抛,有返回值 |
poll | 得到 | 不抛,有返回值 | |
peek | 偷看 | 不抛,有返回值 | |
take | 拿 | 阻塞,等待 | |
SynchronousQueue | 同步队列 | 阻塞队列 | 写<->读 |
Executors | 执行器 | 线程池 | |
Single | 单 | Executors | 单线程 |
Fixed | 固定 | Executors | 固定大小 |
Cached | 缓存 | Executors | 可伸缩 |
shutdown | 关闭 | Executors | 线程池关闭 |
ThreadPoolExecutor | 线程池执行器 | ThreadPoolExecutor | |
corePoolSize | 核心线程池大小 | ThreadPoolExecutor | |
maximumPoolSize | 最大线程池大小 | ThreadPoolExecutor | |
keepAliveTime | 保持活动时间 | ThreadPoolExecutor | |
workQueue | 阻塞队列大小 | ThreadPoolExecutor | |
Rejectedr…Handler | 拒绝策略 | ThreadPoolExecutor | 拒绝后的动作 |
AbortPolicy | 终止策略 | Rejected…Handler | 不处理,抛出 |
CallerRuns | 呼叫方运行 | 哪来去哪去 | |
Discard | 丢弃 | 丢弃,不抛异常 | |
DiscardOldest | 丢弃最旧的 | 和最早的试竞争,不抛 | |
execute | 执行 | ThreadPoolExecutor | |
Runtime | 运行时 | ||
availableProcessors | 可用处理器 | 获取电脑的核数 | |
foreach | 函数包 | foreach | |
function | 函数接口 | foreach | 输入值1,返回值1 |
Predicate | 断定型接口 | foreach | 输入值1,返回boolean |
Consumer | 消费型接口 | foreach | 只有输入,没有返回 |
Supplier | 供给型接口 | foreach | 没有参数,只有返回值 |
filter | 过滤器 | stream | 继承Predicate |
map | 绘制 | stream | 继承Function |
toUpperCase | 改为大写 | stream | |
sorted | 分类 | stream | 继承Consumer |
limit | 限量 | stream | |
forEach | 对于每个 | stream | 继承Consumer |
ForkJoin | 双端队列 | ForkJoin | 工作窃取 |
fork | 交叉 | ForkJoin | 把任务压入线程列队 |
join | 参加 | ForkJoin | 结果合并 |
ForkJoinTask | ForkJoin父类 | ForkJoin | 任务要继承 |
ForkJoinPool | ForkJoin执行 | ForkJoin | |
.execute | 计算任务:执行 | ForkJoin执行 | 没有返回结果 |
.submit | 计算任务:提交 | ForkJoin执行 | 有返回结果 |
rangeClosed | 范围已关闭,半包括 | Stream | |
parallel | 并行 | Stream | |
reduce | 归约计算 | Stream | |
Future | 异步回调,未来 | Future | 未来建模 |
CompletableFuture | 完全未来 | Future | 比较常用的实现类 |
runAsync | 异步运行 | Future | 没有返回值 |
supplyAsync | 异步供给 | Future | 有返回值 |
whenComplete | 完成时 | Future | 正常,错误 |
Volatile | 不稳定 | JMM | 同步机制 |
Read | 读取 | 内存交互操作 | |
Load | 载入 | 内存交互操作 | |
Use | 使用 | 内存交互操作 | |
assign | 赋值 | 内存交互操作 | |
store | 存储 | 内存交互操作 | |
activeCount | 活动计数 | Thead | |
enum | 枚举 | ||
compareAndSet | 比较并交换 | CAS | |
expect | 期望值 | CAS | |
update | 更新值 | CAS | |
getStamp | 获得版本号 | CAS | |
ReentrantLock | 公平锁 | 各种锁 | |
spinlock | 自旋锁 | 各种锁 |
准备工作
确认IEDA jdk版本号
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210705093845509.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3J6ejY1NDUyMDY0,size_16,color_FFFFFF,t_70
- 修改IDEA中准备环境
1、什么是JUC
学习方向:源码+官方文档,面试高频问
图 官方文档java.util
java.util 工具包、包、分类
业务:普通的线程代码 Thread
Runnable 没有返回值、效率相比 Callable 相对较低!
图 Callable
图 Lock
- 以前多线程所学的 Callable 和 Lock 都存在于java.util.concurrent包下
2、线程和进程
线程、进程,如果不能使用一句话说出来的技术,不扎实!
进程:一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程?2个 mian、GC
线程例如:开了一个进程Typora,写字,自动保存,这些功能都是线程负责
对于Java而言:Thread、Runable、Callable之前我们使用这三个开启线程。
提问?JAVA真的可以开启线程吗? 开不了的!
start调用的 native 是本地方法,属于底层的 C++,Java 无法直接操作硬件
代码:start源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
并发、并行
并发编程:并发、并行
并发: 多线程操作同一个资源
- CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。
并行: 多个人一起行走
- CPU多核,多个线程可以同时执行。 我们可以使用线程池
代码:获取CPU的核数
public class Test1 {
public static void main(String[] args) {
//获取CPU的核数
//CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
程序就是在现有的基础上更有效的利用CPU的资源
线程有几种状态
6种
代码:线程状态源码
* @since 1.5
* @see #getState
*/
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
//新生
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
//运行
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
//阻塞
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
//等待,一直等
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
//超时等待,限时等待
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
//终止
TERMINATED;
}
wait / sleep 区别
-
来自不同的类
wait => Object
sleep => Thread
一般情况企业中使用休眠是:
TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s
-
关于锁的释放
wait 会释放锁;
sleep睡觉了,不会释放锁;
-
使用的范围是不同的
wait 必须在同步代码块中;
sleep 可以在任何地方睡;
-
是否需要捕获异常
wait是不需要捕获异常;
sleep必须要捕获异常;
3、Lock锁(重点)
传统的Synchronized
多线程 学习阶段 vs 公司开发
学习阶段的多线程
代码:多线程开启
package JVMclass.lock01;
/*
* 学习阶段的多线程
* 1.创造一个多线程
* 2.重写run方法
* 3.new Thread开启
* 4....
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
}
}
公司开发生成资源类,再用多线程开启其中的某一方法
代码:资源类多线程开启
package JVMclass.lock01;
//卖票系统
/*
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性、方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口,jdk1.8 lambda表达式(参数)->{代码},”name“
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{ for (int i = 0; i < 60; i++) {
ticket.sale();
}},"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) {
ticket.sale();
}},"C").start();
}
}
//资源类 OOP面向对象编程
//如果直接implements Runnable接口,他将只是单独的线程了,会有较高的耦合性
class Ticket {
//属性 方法
private int number =50;
//卖票的方式
//synchronized本质:排队、锁
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
}
}
Lock 接口
图 Lock帮助文档
-
Condition (精确通知)
-
Lock 锁
-
ReadWriteLock 读写锁
Lock 锁
图 加锁,解锁使用格式
图 Lock 三种实现类
-
ReentrantLock 可重入锁(常用)
-
ReadLock 读锁
-
WriteLock 写锁
ReentrantLock 可重入锁(常用)
图 ReentrantLock源码,公平锁,非公平锁
公平锁: 十分公平,必须先来后到~;
非公平锁: 十分不公平,可以插队;(默认为非公平锁)
代码:ReentrantLock 可重入锁
package com.juc.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test1 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
//简化
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"C").start();
}
//Lock三部曲
/*
* 1.new ReentrantLock();
* 2. .lock(); //加锁
* 3. .unlock(); //解锁 (解锁需要在代码块中)
*/
static class Ticket2 {
//属性,方法
private int number =50;
Lock lock = new ReentrantLock();
public synchronized void sale(){
lock.lock(); //加锁
try {
//业务代码
if(number>0){
System.out.println(
Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}
}
Synchronized 和 Lock区别
1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock可以判断
3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
4、Synchronized 线程1(获得锁->阻塞)、线程2(等待->一直等待);
lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
5、Synchronized 是可重入锁,不可以中断的,非公平的;
Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
锁是什么?如何判断锁的是谁
4、生成者和消费者问题
面试的:单例模式、排序模式、生产者和消费者、死锁
代码:生产,消费,+1,-1
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B").start();
}
}
/*
* 1.当程序第一次过来number = 0时,如果执行B~decrement(),由于if成立,B等待,不能执行。
* 经过increment(),发现if不成立,所以直接++,并通知其他线程可执行,B~decrement()解冻。
* 2.第二次当number = 1时,经过如果A~increment(),if成立,所以A等待。
* B执行,经过decrement(),if不成立,所以--,并解冻A~increment()
* 3.第三次number = 0,由于A~increment()已经被解冻,并且if不成立,所以++
*...
*/
class Data{
//数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if(number!=0){
//等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我-1完毕了
this.notifyAll();
}
}
问题存在,A线程B线程,现在如果我有四个线程A B C D!
当四个线程时,或更多线程时,使用 if 时会出现虚假唤醒,多次加、减
解决方案: if 改为while即可,防止虚假唤醒
图 虚假唤醒问题
代码:if 改为while,防止虚假唤醒
package com.juc.test.PC;
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"C").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"D").start();
}
}
class Data{
//数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
while (number!=0){
//等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我-1完毕了
this.notifyAll();
}
}
JUC版本的生产者和消费者问题
通过Lock 找到 Condition
图 Synchronized等待通知,Lock的Condition等待通知
图 Condition 内的Lock 等待通知
代码 Lock Condition 等待 通知
package com.juc.test.PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B").start();
new Thread(()->{for(int i=0;i<12;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"C").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"D").start();
}
}
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await(); 加锁
//condition.signal(); 解锁
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
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(); //解锁
}
}
}//signalAll();全部唤醒,.signal指定唤醒
问题:
- Lock 和 Synchronized 并无两样
- 多线程是随机的,多为先运行AB,再运行CD,但我们想让它A,B,C,D有序的运行
- 所以…
任何一个新的技术,绝对不是仅仅为了覆盖原来的技术,它会更有优势,或得到了补充
Condition的优势:精准的通知和唤醒的线程!
如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~
代码:用Condition来指定通知进程
/**
* A 执行完 调用B
* B 执行完 调用C
* C 执行完 调用A
*/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printA();
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printB();
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printC();
}
},"C").start();
}
}
class Data3{
//资源类
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; //1A 2B 3C
public void printA(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while(number!=1){
//等待
condition1.await();
}
//操作
System.out.println(Thread.currentThread().getName()+"-》AAAAAA");
//唤醒指定的线程
number=2;
condition2.signal(); // 唤醒2
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"-》BBBBBB");
//唤醒3
number=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while(number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"-》CCCCCC");
//唤醒1
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、8锁现象
如何判断锁的是谁!锁到底锁的是谁?
锁会锁住:对象、Class
深刻理解我们的锁
- 问题1:先“发短信”还是先“打电话”?
- 问题2:“发短信“有延迟后,先“发短信”还是先“打电话”?
代码:synchronized,发短信,打电话
//先“发短信”还是先“打电话”?import java.util.concurrent.TimeUnit;public class Test1 { public static void main(String[] args) { //同一个锁 Phone phone = new Phone(); new Thread(()->{phone.sendSms();},"A").start(); try { TimeUnit.SECONDS.sleep(1); //等待时间 } catch (Exception e) { e.printStackTrace(); } new Thread(()->{phone.call();},"B").start(); }}class Phone{ //synchronized 锁的对象是方法的调用者 //两个方法用的是同一个锁,谁先拿到谁先执行 public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); //等待时间 } catch (Exception e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); }}
为什么即使又了延迟,还一直都是先“发短信”后“打电话”呢?
由于“A”和“B”同时使用的一个锁,所以谁先获得锁,谁就会先执行
- 问题3:一个延迟锁方法“发短信”,一个普通方法“hello”,谁先执行
- 问题4:两个对象,先“发短信”还是先“打电话”?
代码:两把锁
package com.juc.test.Lock8;import java.util.concurrent.TimeUnit;public class Test1 { public static void main(String[] args) { //两个对象 Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{phone1.sendSms();},"A").start(); try { TimeUnit.SECONDS.sleep(1); //等待时间 } catch (Exception e) { e.printStackTrace(); } new Thread(()->{phone2.call();},"B").start(); }}class Phone{ public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); //等待时间 } catch (Exception e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } public void hello(){ System.out.println("hello"); }}
当出现普通方法和两把锁时,就不再受锁的影响,由于“发短信”有延迟,所以都是后者先执行
- 问题5:增加static,先“发短信”后“打电话”?
- 问题6:增加static,有两个对象,先“发短信”后“打电话”?
代码:static 静态同步方法
import java.util.concurrent.TimeUnit;public class Test3 { public static void main(String[] args) { //两个对象的class类模板只有一个,static,锁的是class Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); new Thread(()->{phone1.sendSms();},"A").start(); try { TimeUnit.SECONDS.sleep(1); //等待时间 } catch (Exception e) { e.printStackTrace(); } new Thread(()->{phone2.call();},"B").start(); }}class Phone3{ //synchronized 锁的对象是方法的调用者 //static 静态方法 是在类一开始加载的时候,就已经存在了 //锁的是Class //hone3 唯一的一个Class对象,所以类似使用着同一把锁 public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); //等待时间 } catch (Exception e) { e.printStackTrace(); } System.out.println("发短信"); } public static synchronized void call(){ System.out.println("打电话"); }}
synchronized 锁的对象是方法的调用者,static 静态方法 是在类一开始加载的时候,就已经存在了,锁的是Class,hone3 唯一的一个Class对象,所以类似使用着同一把锁
- 问题7:一个static 静态同步方法,一个普通同步方法,同一个对象,先“发短信”后“打电话”?
- 问题8:一个static 静态同步方法,一个普通同步方法,二个对象,先“发短信”后“打电话”?
代码:静态同步方法和普通同步方法,两个对象
import java.util.concurrent.TimeUnit;public class Test4 { public static void main(String[] args) { Phone4 phone1 = new Phone4(); Phone4 phone2 = new Phone4(); new Thread(()->{phone1.sendSms();},"A").start(); try { TimeUnit.SECONDS.sleep(1); //等待时间 } catch (Exception e) { e.printStackTrace(); } new Thread(()->{phone2.call();},"B").start(); }}class Phone4{ //静态的同步方法 锁的是 Class 类模板 public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (Exception e) { e.printStackTrace(); } System.out.println("发短信"); } //普通的同步方法 锁的是调用者 public synchronized void call(){ System.out.println("打电话"); }}
两个都先执行“打电话”,由于不管是静态的同步方法和普通的同步方法,还是两个对象,它们都使用的不同的锁,后者不需要等待第一个执行完再执行,由于延迟影响,所以先执行后者
小结
new 出来的 this 是具体的一个对象
static Class 是唯一的一个模板
6、集合类不安全
6.1、List不安全
代码:单线程集合传统写法
import java.util.Arrays;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("1", "2", "3");
list.forEach(System.out::println);
}
代码:ArrayList<>多线程集合,但是报异常
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
public static void main(String[] args) {
//并发下 ArrayList<>不安全的
List<String> lsit = new ArrayList<>();
for(int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
解决方案:
- 改为使用 Vector<>
List<String> lsit = new Vector<>();
- 使用 Collections.synchronizedList 工具类转换
List<String> lsit = Collections.synchronizedList(new ArrayList<>());
- 使用 Lock 锁
List<String> lsit = new CopyOnWriteArrayList<>();
图 CopyOnWriteArrayList 帮助文档
- CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
- 多个线程调用的时候,list,读取的时候是固定的,但写入(存在覆盖操作);
- 在写入的时候避免覆盖,造成数据错乱的问题;
- 读写分离的思想
CopyOnWriteArrayList比Vector厉害在哪里?
-
Vector底层是使用synchronized关键字来实现的:效率特别低下。
-
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
小狂神的学习方法推荐:1、先会用;2、货比三家,寻找其他解决方案;3、分析源码
6.2、Set 不安全
代码:HashSet<>()多线程,报异常java.util.ConcurrentModificationException
public class SeyTset {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for(int i=1;i<=30;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
解决方案:
- 使用工具类转换
Set<String> set = Collections.synchronizedSet(new HashSet<>());
- 使用 Lock 锁
Set<String> set = new CopyOnWriteArraySet<>();
HashSet底层是什么?
//hashSet底层就是一个HashMap;
public HashSet() {
map = new HashMap<>();
}
//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT是一个常量 不会改变的常量 无用的占位
private static final Object PRESENT = new Object();
6.3、Map 不安全
回顾map的基本操作
Map<String, String> map = new HashMap<>(16,0.75);
-
map 是这样用的吗? 不是,工作中不使用这个
-
默认加载因子是0.75,默认的初始容量是16
//异常java.util.ConcurrentModificationException 并发修改异常
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
for (int i = 1; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
解决方案:
- 使用Collections.synchronizedMap(new HashMap<>());处理;
- 使用ConcurrentHashMap进行并发处理
研究ConcurrentHashMap底层原理
…
7、Callable(简单)
图 Callable 帮助文档
1、可以有返回值;
2、可以抛出异常;
3、方法不同,run()/call()
代码测试
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class callableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Runnable 方法
// new Thread(new MyThread()).start();
/*Thread 接口只有Runnable,
但Runnable 无法和Callable 相连,怎么启动 Callable?
利用 FutureTas 适配类
Runnable 可以和 FutureTask 相连
FutureTask 可以和 Callable 相连
new Thread(new FutureTask<>()).start();
new Thread(new FutureTask<>(Callable)).start();
*/
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread); //适配类
new Thread(futureTask,"A").start();
//new Thread(futureTask,"B").start();
//当两个线程启动时,只会打印出一个,结果会被缓存,提高效率
//获取 callable 的返回结果
Integer o = (Integer) futureTask.get();
//如果返回值是比较耗时的操作,这个get 方法可能会产生阻塞,所以把他放到最后
//或者使用异步通信来处理
System.out.println(o);
}
}
class MyThread implements Callable<Integer> {
//泛型的参数,等于方法的返回值
public Integer call() {
System.out.println("call");
return 1024;
}
}
FutureTask 适配类
图 FutureTask 帮助文档
FutureTask 可以将new Thread(new FutureTask<>(Callable)).start();
FutureTask细节:
- 有缓存
- 结果可能需要等待,会阻塞
8、常用的辅助类(必会)
8.1、 CountDownLatch
“减法计数器”
图 CountDownLatch 帮助文档
package com.juc.test;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go out");
countDownLatch.countDown(); //数量-1
},String.valueOf(i)).start(); //基本数据型态转换成 String
}
countDownLatch.await(); //等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
原理
countDownLatch.countDown();
数量-1
countDownLatch.await();
等待计数器归零,然后再向下执行
每次有线程调用 .countDown() 数量 -1 ,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行
8.2、CyclicBarrier
图 CyclicBarrier 帮助文档
“加法计数器”
package com.juc.test.callableClass;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//默认总数量,完成后线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤龙珠成功!");
});
for (int i = 1; i <=7 ; i++) {
final int temp = i; //由于Thread不能直接获取i,所以需要final进行转换
new Thread(
()->{
System.out.println(Thread.currentThread().getName()+"号机器人获得第"+temp+"颗龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)
).start();
}
}
}
如果 for 循环结束都没达到计数器要求,程序就会一直卡在哪里
final int temp = i;
由于 for()和 Thread()作用域的关系,i 不能直接在线程中使用,需要使用 final 修饰变成常量(还是不太懂)
分开两次 for 循环
package com.juc.test.callableClass;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤龙珠成功!");
});
for (int i = 1; i <=5 ; i++) {
final int temp = i; //由于Thread不能直接获取i,所以需要final进行转换
new Thread(
()->{
System.out.println(Thread.currentThread().getName()+"号机器人获得第"+temp+"颗龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)
).start();
}
for (int i = 6; i <=7 ; i++) {
final int temp = i;
new Thread(
()->{
System.out.println(Thread.currentThread().getName()+"号兽人获得第"+temp+"颗龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)
).start();
}
}
}
8.3 、Semaphore
Semaphore:信号量
图 Semaphore 帮助文档
package com.juc.test.callableClass;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量:停车位,主要功能限流
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
//acquire() 得到
//release() 释放
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2); //等待两秒
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
原理
emaphore.acquire();
获得,假设如果已经满了,等待,等待被释放为止
semaphore.release();
释放,会将当前的信号量释放 +1 ,然后唤醒等待的线程
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数
9、读写锁
ReadWriteLock
先对于不加锁的情况:
如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;
我们采用五个线程去写入,使用十个线程去读取。
我们来看一下这个的效果,如果我们不加锁的情况!
不加锁
import java.util.HashMap;
import java.util.Map;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写
for (int i = 1; i <=7 ; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <=7 ; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
//自定义缓存
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
//存,写
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value); //写入
System.out.println(Thread.currentThread().getName()+"写入OK");
}
//取,写
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
写入没有按顺序完成写入,会被其他线程插入其中
ReadWriteLock 锁
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
独占锁 (写锁) 一次只能被一个线程占用
共享锁 (读锁) 多个线程可以同时占有
ReadWriteLock
读 - 读 可以共存
读 - 写 不能共存
写 - 写 不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写
for (int i = 1; i <=10 ; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <=10 ; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
//private Lock lock = new ReentrantLock(); 旧模式加锁
//应该使用读写锁更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存,写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
//写,加锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value); //写入
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
readWriteLock.writeLock().unlock();
}
}
//取,读,所以人都可以去读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
synchronized 和 Lock 锁都可以解决,但是没有 ReadWriteLock 更细粒
10、阻塞队列
10.1、阻塞 队列
图 阻塞 队列 原理
图 BlockingQueue 帮助文档
BlockingQueue 不是新的东西
什么情况下我们会使用 阻塞队列?
- 多线程并发处理
- 线程池
图 BlockingQueue上端Queue 帮助文档
ArrayDeque 双端队列,就是可以两端对向输出
图 BlockingQueue树
学会使用队列
添加、移除
10.2、四组API
- 抛出异常
- 不会抛出异常
- 阻塞 等待
- 超时 等待
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞 等待 | 超时 等待 |
---|---|---|---|---|
添加 | add | offer | put | offer |
移除 | remove | poll | take | poll |
检查队列首元素 | element | peek | - | - |
抛出异常解决方案
package com.juc.test.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
new Test().test1();
}
/*
抛出异常
*/
public void test1(){
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
//blockingQueue.add 返回一个boolean 如果添加成功返回 true
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//没有可添加的位置抛出异常:IllegalStateException: Queue full 队列已满
//System.out.println(blockingQueue.add("D"));
System.out.println("====================");
//查看队首元素
System.out.println(blockingQueue.element());
//弹出 队列先进先出
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//没有可移除的元素抛出异常:NoSuchElementException 没有元素的
System.out.println(blockingQueue.remove());
}
}
不会抛出异常,有返回值
/*
不会抛出异常,有返回值
*/
public void test2(){
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//没有可添加的位置:false 不抛出异常
//System.out.println(blockingQueue.offer("d"));
//查看队首元素
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//没有可移除的元素:null 不抛出异常
//System.out.println(blockingQueue.poll());
}
阻塞 等待
public void test3() throws InterruptedException {
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
//添加
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d"); //队列没有位置,一直阻塞
//移除
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); //队列没有元素,一直阻塞
}
超时 等待
public void test4() throws InterruptedException {
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//等待超过2秒就退出 添加内容 秒数 时间单位
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println("===============");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//等待超过2秒就退出
blockingQueue.poll(2,TimeUnit.SECONDS);
}
10.3、同步队列
SynchronousQueue 同步队列
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
/*
同步队列
和其他的 SynchronousQueue 不一样, SynchronousQueue 不存储元素
put 了一个元素,必须从里面先 take 取出来,否则不能 put 进去值
*/
//put() 写
//take() 读
//写
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
//读
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3); //等待三秒
System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
学了技术,不会用?主要还是看得少,做的少
多次测试后发现会有多次put输出情况
bibi[彳亍口巴粉了]解释:
我理解:
-
T1-sout1 打印—
-
T1-put1 执行
-
T2-=>1 输出
-
T1-sout2 打印—
-
T1-put2 执行
-
T1-sout3 打印–
-
T1-put3 阻塞(T1执行两次,打印两次,但实际put3阻塞中)
-
T2-=>2 输出
-
T1-put3 执行 (没有返回值,看不出来)
-
T2-=>2 输出 (输出两次)
11、线程池(重点)
线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ! ==> 池化技术
线程池、JDBC的连接池、内存池、对象池 等等。。。
资源的创建、销毁十分消耗资源,尽量减少开与关
- 最小池 :2
- 最大池:max
**池化技术:**事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复用、可以控制最大并发数、管理线程;
11.1、线程池
线程池:三大方法
图 阿里巴巴java开发规范
ExecutorService threadPool = Executors.newSingleThreadExecutor();
单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5);
创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool();
可伸缩的
单个线程
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor(); //单个线程
//Executors.newFixedThreadPool(5); //创建一个固定的线程池,数量大小
//Executors.newCachedThreadPool(); //可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i <10 ; i++) {
//使用了线程池之后,使用线程池来创建线程
service.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}catch (Exception e){
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
service.shutdown();
}
}
}
多线程
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
// ExecutorService service = Executors.newSingleThreadExecutor(); //单个线程
ExecutorService service =Executors.newFixedThreadPool(5); //创建一个固定的线程池,数量大小
//Executors.newCachedThreadPool(); //可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i <10 ; i++) {
//使用了线程池之后,使用线程池来创建线程
service.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}catch (Exception e){
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
service.shutdown();
}
}
}
可变线程
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
//ExecutorService service = Executors.newSingleThreadExecutor(); //单个线程
//ExecutorService service =Executors.newFixedThreadPool(5); //创建一个固定的线程池,数量大小
ExecutorService service =Executors.newCachedThreadPool(); //可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i <100 ; i++) {
//使用了线程池之后,使用线程池来创建线程
service.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}catch (Exception e){
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
service.shutdown();
}
}
}
11.2、7大参数
源码分析,ThreadPoolExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //约等于21亿
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时,无人调用Mox线程池就被释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,创建线程时,一般不用动
RejectedExecutionHandler handler //拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
7大参数示意图
- 最先开发核心Core区域运行
- 多余的人在阻塞区队列
- 待阻塞区满后,开启Max区运行
- 待Core和Max区和阻塞队列都满以后,再进入的人就将被进行拒绝策略
11.3、手动创建线程池
package com.juc.test.bq;
import java.util.concurrent.*;
/*
ThreadPoolExecutor.AbortPolicy()); //拒绝策略,不处理这个人并抛出异常
ThreadPoolExecutor.CallerRunsPolicy()); //那来的去哪里,如果是主线程,就被main方法输出
ThreadPoolExecutor.DiscardPolicy()); //队列满了,丢掉任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了, 尝试去和最早的竞争,也不会抛出异常
*/
public class Demo01 {
public static void main(String[] args) {
//自定义线程池 !ThreadPoolExecutor()
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3) , //阻塞队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了, 尝试去和最早的竞争,也不会抛出异常
try {
for (int i = 1; i <=11 ; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}catch (Exception e){
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
11.4、四种拒绝策略
ThreadPoolExecutor.AbortPolicy());
拒绝策略,不处理这个人并抛出异常
ThreadPoolExecutor.CallerRunsPolicy());
那来的去哪里,如果是主线程,就被main方法输出
ThreadPoolExecutor.DiscardPolicy());
队列满了,丢掉任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy());
队列满了, 尝试去和最早的竞争,也不会抛出异常
11.4、CPU密集型和IO密集型!:(调优)
如何去设置线程池的最大大小如何去设置
1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
System.out.println(Runtime.getRuntime().availableProcessors());
2、I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
12、四大函数式接口(必需掌握)
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
@FunctionalInterface
高级多- 简化编程模式,在新版本的框架底层大量应用
- foreach 函数包(消费者类函数接口)
12.1、Function函数式接口
Function 函数式接口,有一个输入参数,有一个输出
import java.util.function.Function;
/*
Function 函数式接口,有一个输入参数,有一个输出
只要是函数型接口 可以 用 Lambda 表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
/*
Function function = new Function<String,String>() {
@Override
public String apply(String str) {
return str;
}
};
*/
Function<String,String> function = (str)->{
return str;
};
System.out.println(function.apply("asb"));
}
}
12.2、Predicate断定型接口
断定型接口:有一个输入参数,返回值只能是 布尔值
import java.util.function.Predicate;
public class Demo02 {
//断定型接口:有一个输入参数,返回值只能是 布尔值
//判断字符串是否为空
public static void main(String[] args) {
/*Predicate <String> predicate = new Predicate<String>() {
@Override
public boolean test(String str) {
if (str.isEmpty()){
}
return str.isEmpty();
}
};*/
Predicate <String> predicate =str->{
return str.isEmpty();
};
System.out.println(predicate.test(""));
}
}
12.3、Consumer消费型接口
package JVMclass.function;
import java.util.function.Consumer;
/*
Consumer 消费型接口:只有输入,没有返回值
*/
public class Demo03 {
public static void main(String[] args) {
// Consumer <String> consumer = new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer <String> consumer = str ->{
System.out.println(str);
};
consumer.accept("sad");
}
}
12.4、Supplier供给型接口
/*
Supplier 供给型接口 没有参数 只有返回值
*/
public class Demo04 {
public static void main(String[] args) {
/*Supplier <Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
System.out.println("get()");
return 1024;
}
};*/
Supplier <Integer> supplier = ()->{
System.out.println("get()");
return 1024;
};
System.out.println(supplier.get());
}
}
13、Stream流式计算
什么是Stream流失计算
大数据:存储+计算!
存储:集合、MySQL(本质就是存储东西的)
计算:流式计算~(计算都应该交给流来操作)
//有参,get,set,toString
public class User {
private int id;
private String name;
private int age;
//有参构造器Constructor
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
//toString
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
//get\set
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
import java.util.Arrays;
import java.util.List;
/*
1、ID 必须为偶数
2、大于23age
3、用户名转为大写字母
4、用户名字母倒着排序
5、只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(5,"e",25);
User u6 = new User(6,"f",26);
//集合就是存储
List<User> list = Arrays.asList(u1,u2,u3,u4,u5,u6);
//计算交给Stream流
//Lambda 表达式、链式编程、函数式接口、Stream流式计算
list.stream()
//断定型接口:有一个输入参数,返回值只能是 布尔值
.filter(u->{return u.getId()%2==0;})
//断定型接口:有一个输入参数,返回值只能是 布尔值
.filter(user ->{return user.getAge()>23;})
//Function 函数式接口,有一个输入参数,有一个输出
.map(user -> {return user.getName().toUpperCase();})//toUpperCase改为大写
//Comparator比较器
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
//限量
.limit(1)
//Consumer 消费型接口:只有输入,没有返回值
.forEach(System.out::println); //输出
}
}
14、ForkJoin
什么是ForkJoin?
ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!
大数据中:Map Reduce 核心思想->把大任务拆分为小任务!
图 ForkJoi流程图
ForkJoin 特点: 工作窃取!
实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!
ForkJoin的操作
import java.util.concurrent.RecursiveTask;
/*
求和计算的任务
3000(普通for循环) 6000(ForkJoin) 9000(Stream并行流)
*/
/*
如何使用forkJoin
1.forkjoinPool 通过它来执行
2.计算任务forkjoinPool.execute(ForkJoinTask task)
3.计算类要继承ForkJoinTask
*/
public class forkJoinDemo extends RecursiveTask<Long> {
private long start; //最小值
private long end; //最大值
//临界值 切换ForkJoin的值
private long temp = 100000L;
public forkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
public forkJoinDemo() {
}
//计算方法
@Override
protected Long compute() {
if ((end-start)<temp){//小于临界值就直接用for循环计算
long sum = 0;
for (long i = start; i <=end ; i++) {
sum +=i;
}
return sum;
}else {//进行分支合并计算 forkjoin 像递归
long middle = (start+end)/2; //求中间值,用于分开计算
forkJoinDemo task1 = new forkJoinDemo(start,middle);
task1.fork(); //fork()拆分任务,把任务压入线程列队
forkJoinDemo task2 = new forkJoinDemo(middle+1,end);
task2.fork(); //拆分任务,把任务压入线程列队
//join 结果合并
return task1.join()+task2.join();
}
}
}
测试:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/*
求和计算的任务
3000(普通for循环) 6000(ForkJoin) 9000(Stream并行流)
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1(); //时间:967 1839
//test2(); //时间:1045 1711 数值增大,临界值调整,forkJoin越来越快
test3(); //时间:1382
}
//3000(普通for循环)
public static void test1(){
long sum = 0;
long start = System.currentTimeMillis();
for (long i = 1L; i <=20_0000_0000L ; i++) {
sum +=i;
}
long end = System.currentTimeMillis();
System.out.println("sum1="+sum+" 时间:"+(end-start));
}
//6000(ForkJoin)
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
//需要使用ForkJoinPool 通过它来执行
ForkJoinPool forkJoinPool = new ForkJoinPool();
//下面需要传入ForkJoin任务,所以先创建ForkJoin任务
forkJoinDemo task = new forkJoinDemo(0L, 20_0000_0000L);
//.execute()是执行,没有返回值;.submit()提交,有返回值
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
long sum = submit.get(); //get值会阻塞等待
long end = System.currentTimeMillis();
System.out.println("sum2="+sum+" 时间:"+(end-start));
}
//Stream并行流,
public static void test3(){
long start = System.currentTimeMillis();
//rangeClosed (]半包括; parallel并行;reduce归约计算
long sum = LongStream.rangeClosed(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum3="+sum+" 时间:"+(end-start));
}
}
- forkjoinPool 通过它来执行
- 计算任务forkjoinPool.execute(ForkJoinTask task)
- 计算类要继承ForkJoinTask!
小结
-
ForkJoin 必须大数据量,不然不如for
-
ForkJoin 代码更多,大量数据和临界值调整情况下效率比for高
-
Stream 代码更简洁,效率更高,需要更深入的学习
long sum = LongStream.rangeClosed(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
rangeClosed(]半包括
parallel并行
reduce归约计算
15、异步回调
Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 --> 发送ajax异步请求给后端
平时都使用CompletableFuture
没有返回值的异步回调-runAsync
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/*
异步执行
成功回调
失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的 runAsync 异步回调
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
//发起一个异步任务
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ">void");
});
System.out.println("111");
//输出执行结果
future.get(); //获取阻塞执行结果
}
}
有返回值的异步回调-supplyAsync
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/*
异步执行
成功回调
失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//有返回值的 supplyAsync 异步回调
//ajax,成功和失败的回调
//返回的是错误信息
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "supplyAsync->Integer");
int i = 10/0;
return 1024;
});
System.out.println(future.whenComplete((t, u) -> { //异步成功
System.out.println("t=>" + t); //正常的返回结果
System.out.println("u=>" + u); //错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> { //异步失败
System.out.println(e.getMessage());
return 233;
}).get());
}
}
whenComplete: 有两个参数,一个是t 一个是u
T:是代表的 正常返回的结果;
U:是代表的 抛出异常的错误信息;
如果发生了异常,get可以获取到exceptionally返回的值;
16.JMM
请你谈谈你对Volatile 的理解
Volatile 是 Java 虚拟机提供轻量级的同步机制
- 保证可见性 —>与JMM挂钩
- 不保证原子性
- 禁止指令重排
什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存
(线程的调用,是从主存中拷贝一份到自己的工作内存中)
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁;
线程分: 工作内存、主内存
内存交互8种操作
- Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
- Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
JMM对这8种指令的规定:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题:B线程修改主存后,A线程不知道主存中的值已经被修改过了!
17.Volatile
测试:B线程修改主存后,A线程不知道主存中的值已经被修改过了
import java.util.concurrent.TimeUnit;
public class JMMDemo {
private static Integer number = 0;
public static void main(String[] args) {
new Thread(()->{
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number=1);
}
}
1、保证可见性
package com.juc.test.VolatileClass;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
// 如果不加volatile 程序会死循环
// 加了volatile是可以保证可见性的
private volatile static Integer number = 0;
public static void main(String[] args) {
new Thread(()->{
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number=1);
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败
/**
* volatile 不保证原子性
* number <=2w
* */
public class VDemo02 {
private static volatile int number = 0;
//synchronized 保证原子性可以保证
public static void add(){
number++;
//++ 不是一个原子性操作,是两个~3个操作
//
}
public static void main(String[] args) {
//理论上number = 20000
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000 ; j++) {
add();
}
}).start();
}
//Thread.activeCount 还存活的线程数量
while (Thread.activeCount()>2){
//main gc 这两个线程在默认执行
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",num="+number);
}
}
number++
不是一个原子性操作,是两个~3个操作
如果不加lock和synchronized ,怎么样保证原子性?
使用原子类,来解决原子性问题
代码如下:
public class VDemo02 {
//原子类的Integer
private static volatile AtomicInteger number = new AtomicInteger();
public static void add(){
// number++;
number.incrementAndGet(); //AtomicInteger +1 方法
//底层是CAS保证的原子性
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",num="+number);
}
}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在,CAS
3、禁止指令重排
- 什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的
可能造成的影响结果:前提:a b x y这四个值 默认都是0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果: x = 0; y =0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
可能在线程A中会出现,先执行b=1,然后再执行x=a
在B线程中可能会出现,先执行a=2,然后执行y=b
顺序对各自线程没有影响,但是会对其他线程有影响
那么就有可能结果如下:x=2; y=1(理论上会产生)
volatile可以避免指令重排
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令
作用:
- 保证特定的操作的执行顺序;
- 可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
图 内存屏障实现原理
volatile可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式
18、玩转单例模式
饿汉式、DCL懒汉式
饿汉式
/**
* 饿汉式单例
一上来就加载
*/
public class Hungry {
/**
*没有被使用,但已经被创建出来
* 可能会浪费空间
*/
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private byte[] data4=new byte[1024*1024];
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
DCL懒汉式
//懒汉式单例模式
//用的时候在加载
public class LazyMan {
private static boolean Q = false;
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
//单线程下if是ok的
//但是如果是并发就会出现问题
if(lazyMan==null){
//所以需要加锁,进行双重检测
synchronized (LazyMan.class){
//然后再去判断
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
//不是一个原则性操作
/**不是原则性操作会出现以下步骤:
*
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 有可能出现指令重排问题
* 比如执行的顺序是1 3 2 等
* 我们就可以添加volatile保证指令重排问题
*/
}
}
懒汉式不是一个原则性操作
- 不是原则性操作会出现以下步骤:
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象指向这个空间
- 有可能出现指令重排问题
- 比如执行的顺序是1 3 2 等
- 我们就可以添加volatile保证指令重排问题
静态内部类
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.holder;
}
public static class InnerClass{
private static final Holder holder = new Holder();
}
}
//也不安全, 因为有反射
单例不安全,因为有反射
- 第一次破坏
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
declaredConstructor.setAccessible(true); //无视私有构造器
LazyMan instance2 = declaredConstructor.newInstance(); //新建实例
System.out.println(instance);
System.out.println(instance2);
}
LazyMan instance = LazyMan.getInstance();
先通过内部类获得私有属性- 利用
.class.getDeclaredConstructor(null)
获得空参构造器 - declaredConstructor.setAccessible(true); //无视私有构造器
LazyMan instance2 = declaredConstructor.newInstance();
新建实例
- 第一次反击
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
通过另外给LazyMan.class
加锁,lazyMan!=null
就说明已经被重写了,然后抛出异常
- 第二次破坏
public static void main(String[] args) throws Exception {
//LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
declaredConstructor.setAccessible(true); //无视私有构造器
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
不使用LazyMan
,直接使用LazyMan instance = declaredConstructor.newInstance();
,绕过第一次反击的锁
- 第二次反击
public class LazyMan {
private static boolean Q = false; //创建标志位
private LazyMan(){
synchronized (LazyMan.class){
if (Q == false){
Q = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
标记标志位
- 第三次破坏
如果获得了隐藏标记位的名字
public static void main(String[] args) throws Exception {
//LazyMan instance = LazyMan.getInstance();
Field q = LazyMan.class.getDeclaredField("Q");
q.setAccessible(true); //破坏标记位私有权限
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
declaredConstructor.setAccessible(true); //无视私有构造器
LazyMan instance = declaredConstructor.newInstance();
q.set(instance,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
- 道高一尺魔高一丈
package JVMclass.DLC;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class LazyMan {
private static boolean Q = false; //创建标志位
private LazyMan(){
synchronized (LazyMan.class){
if (Q == false){
Q = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
//单线程下if是ok的
//但是如果是并发就会出现问题
if(lazyMan==null){
//所以需要加锁,进行双重检测
synchronized (LazyMan.class){
//然后再去判断
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
//反射,任何代码都不安全
public static void main(String[] args) throws Exception {
//LazyMan instance = LazyMan.getInstance();
Field q = LazyMan.class.getDeclaredField("Q");
q.setAccessible(true); //破坏私有
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
declaredConstructor.setAccessible(true); //无视私有构造器
LazyMan instance = declaredConstructor.newInstance();
q.set(instance,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
利用枚举防止反编译
枚举
package JVMclass.DLC;
import java.lang.reflect.Constructor;
// enum本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
//测试能不能被反射
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
//Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
//没有无参构造,而是有参构造
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
//Cannot reflectively create enum objects
//反射不能破坏枚举
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//提示有空参构造方法
//NoSuchMethodException: JVMclass.DLC.EnumSingle.<init>()
// 但程序运行提示没有空参构造方法
System.out.println(instance1);
System.out.println(instance2);
}
}
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
通过jad,将class文件反编成java文件,反编译后发现实际是个有参构造
19、深入理解CAS
什么是CAS?
大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构
public class casDemo {
//CAS : compareAndSet 比较并交换
public static void main(String[] args) {
//原子类,原子类的底层用的CAS
AtomicInteger atomicInteger = new AtomicInteger(2020);//实际初始值
//boolean compareAndSet(int expect, int update)
//expect期望值、update更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
System.out.println(atomicInteger.compareAndSet(2020, 2021)); //能返回一个boolean,true or false
System.out.println(atomicInteger.get());
//因为期望值是2020 实际值却变成了2021 所以会修改失败
//CAS 是CPU的并发原语
atomicInteger.getAndIncrement(); //++操作
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
Unsafe类
getAndIncrement源码:
自旋锁
总结:
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
- 缺点:
- 循环会耗时;
- 一次性只能保证一个共享变量的原子性;
- 它会存在ABA问题
CAS:ABA问题?(狸猫换太子)
比如:
-
由于线程2速度快,将 A = 1改为3,又改回1
-
线程1实际修改的 A=1 已经不是原理的 A=1 了,而是线程2修改过的 A=1
CAS 代码:
public class casDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//对于我们平时写的SQL : 使用 乐观锁!
//====捣乱的线程 ====
//2020-> 2021
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
//2021 - > 2020
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//====期望的线程 ====
//2020 已经不是原理的 2020 了
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
20、原子引用
解决ABA问题,对应的思想:就是使用了乐观锁~
解决 ABA 问题,引入原子引用!对应的思想:乐观锁
带版本号的 原子操作!
package com.juc.test.CASClass;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
public static void main(String[] args) {
//AtomicInteger atomicInteger = new AtomicInteger(2020);
//注意,如果泛型是一个包装类,注意对象的引用问题
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(10,1);
new Thread(()->{
int stamp = atomicInteger.getStamp(); //获取版本号
System.out.println("A1 => "+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//期望值,希望值,期望版本号,希望版本号
System.out.println("A2-"+atomicInteger.compareAndSet(10, 12,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println();
System.out.println("A2 => "+atomicInteger.getStamp());
System.out.println("A3-"+atomicInteger.compareAndSet(12, 10,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println();
System.out.println("A3 => "+atomicInteger.getStamp());
},"A"
).start();
//如果线程A先走,即使期望值修改回10,但是由于版本号已经改变,线程B无法执行
//如果线程B先走,期望值和版本都所以改变,线程A无法执行
//和乐观锁的原理相同
new Thread(()->{
int stamp = atomicInteger.getStamp(); //获取版本号
System.out.println("B1 => "+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B2-"+atomicInteger.compareAndSet(10, 66, stamp, stamp + 1));
System.out.println("B2 => "+atomicInteger.getStamp());
},"B"
).start();
}
}
- 如果线程A先走,即使期望值修改回10,但是由于版本号已经改变,线程B无法执行
- 如果线程B先走,期望值和版本都所以改变,线程A无法执行
- 和乐观锁的原理相同
注意
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
由于测试代码使用的是个包装类(Integer),正常业务操作中,我们一般使用的是一个个对象,一般情况不会遇到这种情况。
21、各种锁的理解
1、公平锁、非公平锁
公平锁:非常公平;不能插队的,必须先来后到;
加 true 后变成公平锁
Lock lock = new ReentrantLock(true);
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:非常不公平,允许插队的,可以改变顺序
默认都是非公平
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
2、可重入锁
可重入锁(递归锁)
- Synchronized锁
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"=> sms");
call();//这里也有一把锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"=> call");
}
}
正常逻辑 A 执行完 sms() 后就可以解锁,B就有可能执行。
但是 sms() 锁包含着 call() ,这样待A特底执行完 sms() 和 call() 之后,才算执行完毕,才能解锁,B才能执行。
- lock锁
//lock
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock=new ReentrantLock();
public void sms(){
lock.lock(); //细节:这个是两把锁,两个钥匙
//lock锁必须配对,否则就会死锁在里面
try {
System.out.println(Thread.currentThread().getName()+"=> sms");
call();//这里也有一把锁
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "=> call");
}catch (Exception e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
lock锁必须配对,相当于lock和 unlock 必须数量相同,多lock 或多 unlock 会出现锁死或报错情况
在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
3、自旋锁
spinlock
不断迭代,直到成功为止
自我设计自旋锁:
MyLock 自己设计的锁:
public class SpinlockDemo {
//int 0(不写)
//Thread null
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"===> mylock");
//自旋锁 判断是否成立
while (!atomicReference.compareAndSet(null,thread)){
System.out.println(Thread.currentThread().getName()+" ==> 自旋中");
}
}
//解锁
public void myunlock(){
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"===> myUnlock");
//如果为空,就解锁
atomicReference.compareAndSet(thread,null);
}
}
测试代码:
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();
//使用CAS实现自旋锁
SpinlockDemo spinlockDemo =new SpinlockDemo();
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myunlock();
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myunlock();
}
},"t2").start();
}
}
运行结果:
t2进程必须等待t1进程 Unlock 后,才能 Unlock ,在这之前进行自旋等待。。。。
4、死锁排查
死锁是什么?
死锁测试,怎么排除死锁:
import java.util.concurrent.TimeUnit;
public class DeadLock {
public static void main(String[] args) {
String lockA= "lockA";
String lockB= "lockB";
new Thread(new MyThread(lockA,lockB),"t1").start();
new Thread(new MyThread(lockB,lockA),"t2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
}
}
}
}
解决问题
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack-进程号
找到死锁信息
一般情况信息在最后:
面试,工作中!排查问题!
1、日志
2、堆栈信息