JUC并发编程
进程和线程
并发和并行
并发(多个线程操作同一个资源)
- CPU一核 模拟出来多条线程、
并行
- CPU多核,多个线程可以同时执行
public class test1 {
public static void main(String[] args) {
//打印CPU核心数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
线程状态 (生命周期)
6个状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待 死死地等
WAITING,
// 超时等待
TIMED_WAITING,
//死亡
TERMINATED;
}
wait、sleep的区别
wait–> Object
sleep -> Thread
wait 会释放锁
sleep 不会释放锁
wait必须在同步代码块中使用(调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁。)
sleep可以在任何地方使用
wait不需要捕获异常
sleep必须捕获异常
LOCK锁(重点)
synchronized (可重入锁 悲观锁 独占锁 )
package com.xin.demo01;
public class test1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"C").start();
}
}
class Ticket{
int num = 50;
public synchronized void sell(){
if(num >0){
System.out.println(Thread.currentThread().getName()+"卖出去了第"+num--+"张票剩余"+num);
}
}
}
synchronized锁升级过程及其实现原理 (偏向锁 -> 轻量锁 -> 重量锁)
参考:https://blog.csdn.net/wangyy130/article/details/106495180/?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-1&spm=1001.2101.3001.4242
为什么需要锁升级?
应为最JDK1.6的时候Synchronized锁是重量锁,每次更新锁的状态就会进行系统调用,系统调用会涉及到用户态和内核态的切换。此过程比较复杂时间比较长,特别是同一个线程操作同一个资源的时候是不需要频繁地切换的,所以锁的升级就来了,事实上就是对synchronized的优化。它们并不是具体的锁,而是锁的几个状态。
锁升级原理: 事实上我们的锁的信息是存在对象头里的,占据3bit,还有使用此对象的线程id,线程获取资源的时候先判断锁的状态再来选择如何获得锁
还有就是有了CAS,也是锁升级的关键,不堵塞的关键
偏向锁:大多数情况下,锁不紧不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。(同一个线程频繁使用加了锁的资源时,且只有一个线程申请锁,默认使用轻量锁,是不堵塞的,且只有在第一次获得锁的时候去获得锁,平时不释放锁,直到要升级锁的时候)高并发时候偏向锁一般没有用,所以关闭偏向锁
获取锁过程:判断是偏向锁后,判断对象里存储的线程ID是否是此线程,是的话直接获得锁 否则通过CAS耗费CPU尝试将自己的线程id存储进当前锁对象的对象头中来获取偏向锁,没有有竞争的话就获得锁,否则开始锁竞争进行锁升级过程,升级为轻量级锁。
轻量锁(自旋锁): 当有一个线程加入竞争轻量锁的队列中,偏向锁会升级为轻量锁,竞争的线程会不断地自旋CAS竞争锁,也是不阻塞的。
重量锁: 当自旋超过一定的时间,或者有第三个线程加入竞争,会升级为重量锁,重量锁就是我们熟悉的Synchronized,独占、阻塞、
几种锁状态优缺点对比:
- 只有一个线程进入临界区 -------偏向锁
- 多个线程交替进入临界区--------轻量级锁
- 多个线程同时进入临界区-------重量级锁
Lock (可重入锁 悲观锁 独占锁 默认不公平锁)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HC9b130C-1615907409126)(image-20201202221545534.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skKYxrBA-1615907409134)(image-20201202225200384.png)]
公平锁:先来后到
不公平锁:可以插队(默认非公平)
public class test2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"C").start();
}
}
class Ticket2{
Lock lock = new ReentrantLock();
int num = 50;
public void sell(){
lock.lock();
try {
if(num >0){
System.out.println(Thread.currentThread().getName()+"卖出去了第"+num--+"张票剩余"+num);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}}
synchronized 和 lock 的区别
1.synchronized是java的一个修饰符 lock是一个类
2.synchronized 可重入锁 不可中断 非公平锁 ;lock 可重入锁 可中断 可以设置公平或非公平锁
3.synchronized 是自动释放锁;lock需要手动加锁解锁
4.synchronized遇到阻塞会死死地等待;lock可以使用trylock()
来尝试获得锁
5.synchronizd适合锁代码量较小的代码块。;lock适合锁代码量大的;
6.synchronizd无法判断锁的状态,lock可以判断是否获得了锁。
7.synchronizd是隐式锁,lock是显示锁
锁是什么?如何判断锁是谁?
生产者和消费者问题
synchronized版
/*
线程之间的通信问题: 生产者和消费者而之间的问题 等待 和通知唤醒
线程交替执行 AB之间操作同一个变量 num=0
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待 业务 通知
class Data {
private int number = 0;
public synchronized void increment() throws InterruptedException {
if(number!=0){
this.wait();//等待
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我+1完毕了
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我-1完毕了
this.notifyAll();
}
}
问题存在:ABCD 四个线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBp1JDXO-1615907409137)(image-20201203131834714.png)]
使用if的话 线程唤醒会从wait处继续执行 if不会继续判断就会导致虚假唤醒 但是while会先进行while判断
为什么加while就可以解决虚假唤醒问题 因为唤醒之后是从wait处继续执行,会再次进入while进行判断,但是if的话直接执行下一步
package com.xin.pc;
/*
线程之间的通信问题: 生产者和消费者而之间的问题 等待 和通知唤醒
线程交替执行 AB之间操作同一个变量 num=0
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待 业务 通知
class Data {
private int number = 0;
public synchronized void increment() throws InterruptedException {
while (number!=0){
this.wait();//等待
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我+1完毕了
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while(number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我-1完毕了
this.notifyAll();
}
}
LOCK版
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csImo8XC-1615907409139)(image-20201203135635626.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V3mDiEao-1615907409142)(image-20201203133029746.png)]
package com.xin.pc;
/*
线程之间的通信问题: 生产者和消费者而之间的问题 等待 和通知唤醒
线程交替执行 AB之间操作同一个变量 num=0
*/
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) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 20; 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();
public void increment() throws InterruptedException {
lock.lock();
try {
while (number!=0){
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public synchronized void decrement() throws InterruptedException {
lock.lock();
try {
while(number==0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
//通知其他线程我-1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
好处 condition精准地唤醒和线程
通过创建多个condition来控制线程
package com.xin.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try {
while(number!=1){
condition1.await();
}
number++;
System.out.println("AAAAAAAAAA"+number );
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number!=2){
condition2.await();
}
number++;
System.out.println("BBBBBB"
+number);
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number!=3){
condition3.await();
}
number=1;
System.out.println("CCCCCCCC");
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
深刻理解我们的锁
1.带有synchronized 锁的方法 谁先拿到锁谁先执行
/*
8锁 就是锁的8个问题
带有synchronized 锁的方法 谁先拿到锁谁先执行
*/
public class test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
//锁的对象是方法的调用者
//两个对象用同一把锁
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call() {
System.out.println("call");
}
}
2.如果有普通的方法是不受锁的限制的
public class test2 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
new Thread(() -> {
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.hello();
}, "B").start();
}
}
class Phone1 {
//锁的对象是方法的调用者
//两个对象用同一把锁
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call() {
System.out.println("call");
}
//这里没有锁不受锁的影响
public void hello(){
System.out.println("hello");
}
}
3.如果调用锁的是两个的对象 就是两把锁 互不干扰
Phone1 phone1 = new Phone1();
Phone1 phone2 = new Phone1();
4.如果是两个static方法,锁的就是class对象模板(唯一) 所以看谁先获得锁
public class test3 {
public static void main(String[] args) {
Phone3 phone3 = new Phone3();
new Thread(() -> {
phone3.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone3.call();
}, "B").start();
}
}
class Phone3 {
//锁的对象是方法的调用者
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public static synchronized void call() {
System.out.println("call");
}
}
5.如果建立两个对象的话也是先执行先获得锁的对象的方法,因为锁的是class对象 只有一个
Phone3 phone3 = new Phone3();
Phone3 phone4 = new Phone3();
6.一个是静态同步方法一个是普通同步方法,一个对象,一个是class一个是调用的对象 所以互不干扰
public class test4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone4 {
//静态同步方法
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
//普通同步方法
public synchronized void call() {
System.out.println("call");
}
}
7.一个是静态同步方法一个是普通同步方法,两个对象,一个是class一个是调用的对象 所以互不干扰
Phone4 phone = new Phone4();
Phone4 phone1 = new Phone4();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.call();
}, "B").start();
集合类不安全
CopyOnWriteAaaryList
//并发修改异常ConcurrentModificationException
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
解决方法
1.使用Collections的synchronizedXXX方法 底层synchronized
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
2.使用线程安全的Vector 底层synchronized
List<String> list = new Vector<>();
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
3.使用JUC包下的copyOnWriteArraylist 底层Lock
1、读写分离,读和写分开 只给写的时候add加锁 lock ,保证只有一个线程在添加,读的时候不加锁 和写锁不同的是可加锁的时候可以读
2、最终一致性 (CopyOnWrite只能保证数据最终的一致性,不能保证数据的实时一致性。)
3、使用另外开辟空间的思路,来解决并发冲突.但是会浪费空间
List<String> list = new CopyOnWriteArrayList<>();
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();//先复制一份模板
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
CopyOnWriteSet
同理发生ConcurrentModificationException异常
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
解决方法
1.使用Collections的synchronizedXXX方法
Set<String> set = Collections.synchronizedSet(new HashSet<>());
2.使用JUC包下的copyOnWriteArraylist 底层Lock
Set<String> set = new CopyOnWriteArraySet<>();
CurrentHashMap
实现原理(写的很好)
https://blog.csdn.net/V_Axis/article/details/78616700?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
1.使用Collections的synchronizedXXX方法
Map<String,String> set = Collections.synchronizedMap(new HashMap<>());
2.使用JUC包下的ConcurrentHashMap
Map<String,String> map = new ConcurrentHashMap<>();
肯定是兼顾了***线程安全和运行效率***的ConcurrentHashMap好点的,首先性能,synchronized会将整个集合上锁,导致其他操作阻塞
原理 :将hashmap再次分成多个map,放在segment中 做两次hash
计算size比较麻烦,为了不锁所有segment,首先乐观地假设size过程中不会有修改。当尝试一定次数,才无奈转悲观,锁住所有segment以保证一致性。
将hashMap分为多个segment,这样我们就可以减小锁的粒度.对同一个segment加锁,不同的segment互不干扰,只对修改操作加锁,对于修改操作只有拥有段锁的才能执行
Callable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwGD5GE6-1615907409147)(image-20201204133109361.png)]
(1).实现Callable接口和Runnable接口实现类,都是可以被线程执行的任务
(2).Callable接口抛异常,Runnable接口不抛异常
(3).Callable任务运行后,可以获取current包Future对象,Future对象可以获取任务返回值,Future表示异步计算的结果,提供了检测计算是否完成的方法get(),通过cancel(task)方法,可以取消执行中的任务 Runnable接口不行
(4).类实现Callable接口,要实现Call方法,类实现Runnable接口,要实现run()方法
public class callableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallTest callTest = new CallTest();
FutureTask<Integer> futureTask = new FutureTask<>(callTest);//包装类 因为FutureTask是runable的实现类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//对于相同的结果会存在缓存中,只执行一次
Integer integer = futureTask.get();//可能会造成阻塞
System.out.println(integer);
}
}
class CallTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
细节:
1、有缓存
2、结果可能需要等待,会阻塞
常见的辅助类(必会)
CountDownLatch –
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UcePPtoW-1615907409149)(image-20201204135730434.png)]
public class CountDownLatchDemo {
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()+"get out");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();//直到为0 不等待
System.out.println("close door");
}
}
原理
CountDownLatch countDownLatch = new CountDownLatch(6);
起始为6
countDownLatch.countDown();
线程调用一次就-1
countDownLatch.await()
直到0结束wait
CyclicBarrier ++
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5joqClB-1615907409150)(image-20201204141014637.png)]
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclic = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for (int i = 0; i < 7; i++) {
final int tmp = i;
new Thread(()->{
System.out.println(tmp+"召唤");
try {
cyclic.await();
System.out.println(tmp+"召唤zhihou");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
/**
*
4召唤
1召唤
2召唤
6召唤
0召唤
5召唤
3召唤
召唤神龙
3召唤zhihou
1召唤zhihou
2召唤zhihou
6召唤zhihou
0召唤zhihou
5召唤zhihou
4召唤zhihou
*/
原理
CyclicBarrier cyclic = new CyclicBarrier(7, () -> { System.out.println("召唤神龙");});
参数是数字加接口 达到这个数字就执行这个接口
cyclic.await();
每次+1等待他执行 到达后其他线程执行后续的操作
Semaphore(相当于阻塞队列) 多个++
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wV47JCpm-1615907409152)(image-20201204142242811.png)]
public class SemaporeDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
final int tmp = i;
new Thread(() -> {
try {
semaphore.acquire(); //++
System.out.println(tmp + "进去了");
TimeUnit.SECONDS.sleep(2);
System.out.println(tmp + "离开了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();// --
}
}, String.valueOf(i)).start();
}
}
}
semaphore.acquire();
请求 ,当信号量满了就会等待
semaphore.release();
释放, 唤醒等待的线程
ReadWriteLock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awvKkEbH-1615907409154)(image-20201204145702212.png)]
/*
共享锁(读锁)
独占锁(写锁)
读锁 读锁 可以共存
读锁 写锁 不能共存
写锁 写锁 不能共存
*/
public class ReadWriteLockDemp {
public static void main(String[] args) {
Cache cache = new Cache();
//写入
for (int i = 1; i <= 6; i++) {
int tmp = i;
new Thread(()->{
cache.put(tmp,tmp);
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 6; i++) {
int tmp = i;
new Thread(()->{
cache.get(tmp);
},String.valueOf(i)).start();
}
}
}
class Cache{
private ReadWriteLock lock = new ReentrantReadWriteLock();//锁的细粒度
private volatile HashMap<Integer,Integer> map = new HashMap<>();
public void put(int k,int v){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入");
map.put(k,v);
System.out.println(Thread.currentThread().getName()+"写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(int k){
lock.readLock().lock();
int result = 0;
try {
System.out.println(Thread.currentThread().getName()+"读取");
map.get(k);
System.out.println(Thread.currentThread().getName()+"读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
阻塞队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dK2JEuuu-1615907409155)(image-20201204154422416.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bu1ODiOq-1615907409160)(image-20201204154527777.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gErMom6V-1615907409162)(image-20201204155146883.png)]
什么情况下我们会使用到阻塞队列:多线程并发处理,线程池
四组API
方式 | 抛出异常 | 返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer |
删除 | remove | poll | take | poll |
移除队列首 | element | peek | - | - |
/*
抛出异常
*/
public class Test {
public static void main(String[] args) {
test1();
}
public static void test1() {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
System.out.println(queue.add(1));
System.out.println(queue.add(2));
System.out.println(queue.add(3));
System.out.println(queue.element());//返回队首
//抛出异常
// System.out.println(queue.add(1));
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
}
}
/*
有放回值没有抛出异常
*/
public static void test2(){
ArrayBlockingQueue<Object> q = new ArrayBlockingQueue<>(3);
System.out.println(q.offer(1));
System.out.println(q.offer(2));
System.out.println(q.offer(3));
System.out.println(q.offer(3));//false
System.out.println(q.peek());//返回队首
System.out.println(q.poll());
System.out.println(q.peek());//返回队首
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());//null
}
/*
阻塞等待
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
queue.put(1);
queue.put(2);
queue.put(3);
//queue.put(3);//会阻塞等待
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());//会阻塞等待
}
/*
超时等待
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(3,2, TimeUnit.SECONDS);//阻塞等待2秒
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll(2,TimeUnit.SECONDS));//null
}
SynchronousQueue 同步队列
/**
* 同步队列
* 和其他的BlockingQueue不一样 SynchronizedQueue 不存储元素
* put一个元素 必须等里面先取出来否则不能再put
*/
public class SynchronizedQueueDmo {
public static void main(String[] args) {
BlockingQueue<Integer> que = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"1");
que.put(1);
System.out.println(Thread.currentThread().getName()+"2");
que.put(2);
System.out.println(Thread.currentThread().getName()+"3");
que.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"取了"+que.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"取了"+que.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"取了"+que.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
SynchronousQueue 不具有任何内部容量,甚至不具有一的容量,我们可以用来在线程间安全的交换单一元素。所以功能比较单一,优势应该就在于轻量吧~
线程池
线程池: 三大方法 7大参数 4种拒绝策略
池化技术
线程池 连接池 内存池 对象池 …创建和销毁十分浪费资源
池化技术: 事先准备好一些资源,有人要用来这里拿,用完再还回去
线程的好处
怎么分析呢?首先线程池最大的作用就是线程的复用,因为线程创建和销毁需要花费大量的CPU资源,那么除了可以线程复用还有什么好处呢?我们可以从线程池的七大参数来分析,首先我们可以设置线程的最大并发数,更好管理线程,其次,对于等待的线程我们可以设置最优的拒绝策略来处理线程,或者使用队列来将线程缓存处理,我们还可以使用不同的线程池处理不同的方向的任务,实现业务独立不互相影响。还有线程池提供一些方法实现定时执行、周期执行。
总结如下:
- 利用线程池管理并复用线程、控制最大并发数等。
- 实现任务线程队列缓存策略和拒绝机制。
- 实现某些与时间相关的功能,如定时执行、周期执行等。
- 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响。
线程复用、可以控制最大并发数、管理线程
线程池 三大方法
public class Demo1 {
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 < 10; i++) {
service.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}
}
7大参数
源码分析
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,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质是:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//超时了 没人调用就会释放
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWTYNdVQ-1615907409164)(image-20201204183650521.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4lbjnpwk-1615907409165)(image-20201204183917491.png)]
自定义一个线程池
//最大线程该如何定义
ExecutorService service = new ThreadPoolExecutor(2,
5,
3L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
四大拒绝策略
/**
AbortPolicy 银行满了 还有人进来 不处理这个人 抛出异常
DiscardPolicy 队列满了 丢掉任务 不会抛出异常
DiscardOldestPolicy 尝试和最早的线程竞争,成功就执行 不成功就丢掉 不会抛出异常
CallerRunsPolicy 哪来的去哪里 有可能抛给main 所有可能随便抛出一个线程
*/
IO密集型和CPU密集型
最大线程数怎么定义
-
CPU密集型: 根据CPU的核数来定义最大的线程数,
Runtime.getRuntime().availableProcessors()
获取系统的CPU核数 保证效率最高 -
IO密集型: 根据最耗IO的线程,选择大于这些IO线程的线程数因为IO线程十分耗资源
四大函数接口
新时代的程序员: lambda表达式 链式编程 函数式接口 Stream流式计算
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13Dj7eiD-1615907409165)(image-20201205131620709.png)]
function
//有一个输入有一个输出
public static void main(String[] args) {
// Function<String, String> stringStringFunction = new Function<String, String>(){
// @Override
// public String apply(String s) {
// return s;
// }
// };
Function<String, String> function = (str)->{return str;};//lambda表达式
System.out.println(function.apply("a"));
}
predicate
public static void main(String[] args) {
/**
* 断定式函数接口 输入一个参数返回一个布尔值
*/
// Predicate<String> predicate = new Predicate<>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate = (s)->{return s.isEmpty();};
System.out.println(predicate.test(""));
}
Consumer 消费型接口
public static void main(String[] args) {
/**
* Consumer 只有输入没有返回值
*/
// Consumer<String> consumer = new Consumer<>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer<String> consumer = (s)->{
System.out.println(s);};
consumer.accept("sdfsdf");
}
Supplier 供给型接口
public static void main(String[] args) {
/**
* Supplier 只有返回值没有输入
*/
// Supplier<Integer> supplier = new Supplier<>() {
//
// @Override
// public Integer get() {
// return 1024;
// }
// };
Supplier<Integer> supplier =()->{return 1024;};
System.out.println(supplier.get());
}
String流式计算
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nkW8eUXY-1615907409167)(image-20201205140446955.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNl5Fo4b-1615907409168)(image-20201205140932664.png)]
ForkJoin
有点像归并排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfMUSEPK-1615907409170)(image-20201205141636449.png)]
工作窃取
使用双端队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wxB0buZR-1615907409171)(image-20201205141824196.png)]
forkjoin
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V27ybVJt-1615907409173)(image-20201205142535249.png)]
/**
* 如何使用forkjoin
* 1.forkjoinpool 通过它来执行
* 2.计算任务forkjoinpool.excute(ForkjoinTask)
* 3.计算类要继承ForkJoinTask
*/
public class forkjoindemo extends RecursiveTask<Long>{
private long start;
private long end;
private long temp = 10000L;//临界值
public forkjoindemo(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((start-end)<temp){
long sum = 0;
for (long i = start; i <=end ; i++) {
sum+=i;
}
return sum;
}else
{//forkjoin//递归
long middle = (end-start)/2;
forkjoindemo task1 = new forkjoindemo(start,middle);
task1.fork();//把线程压入消息队列
forkjoindemo task2 = new forkjoindemo(middle+1,end);
task2.fork();
return task1.join()+task2.join();
}
}
}
测试一下三种方法
普通方法 forkjoin Stream流计算
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1(0, 50_0000_0000L);//2447
//test2(0, 50_0000_0000L);//3511 还是大数据比较好用
//test3(0, 50_0000_0000L);//1227
}
public static long test1(long s,long e){
long sum = 0;
long st = System.currentTimeMillis();
for (long i = s; i <= e; i++) {
sum+=i;
}
long et = System.currentTimeMillis();
System.out.println(sum+" "+(et-st));
return sum;
}
public static long test2(long s,long e) throws ExecutionException, InterruptedException {
long st = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask task = new forkjoindemo(s, e);
ForkJoinTask submit = forkJoinPool.submit(task);
long sum=(long) submit.get();
long et = System.currentTimeMillis();
System.out.println(sum+" "+(et-st));
return sum;
}
public static long test3(long s,long e){
long st = System.currentTimeMillis();
long sum = LongStream.rangeClosed(s, e).parallel().reduce(0, Long::sum);
long et = System.currentTimeMillis();
System.out.println(sum+" "+(et-st));
return sum;
}
}
异步回调
future 初衷 : 对未来某个时间的结果进行建模
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMoJJ2ku-1615907409175)(image-20201205152210170.png)]
package com.xin.frture;
import java.sql.SQLOutput;
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> completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>void");
// });
// System.out.println("1111");
// completableFuture.get();//阻塞获取结果
// System.out.println("1111");
//有返回值的runAsync 异步回调
//ajax 成功和失败的回调
CompletableFuture <Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"runAsync=>Interger");
int v = 1/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=" + t);//正常的返回结果
System.out.println("u=" + u);//错误信息
}).exceptionally((e) -> {//错误的返回结果
System.out.println(e.getMessage());
return 203;
}).get());
}
}
JMM
请你谈谈对Volatile的理解
Volatile是java提供的一个轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
什么是JMM
JMM : java内存模型 虚拟机 概念约定
1.线程解锁前,必须立刻把内存刷回主存
2.线程加锁前,必须读取主存中的最新值到工作内存中
线程 工作内存 主存
八种操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17aaRGwH-1615907409179)(image-20201205160504412.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qh5jkvEE-1615907409180)(image-20201205160614580.png)]
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
-
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
-
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。
问题:线程不知道主存中的值已经发生变化
public class JMMDemo {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num==0){}
}).start();
TimeUnit.SECONDS.sleep(1);
num=1;
System.out.println(num);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0J7PMMnT-1615907409182)(image-20201205161214212.png)]
主内存和本地内存的关系
JMM规定的主内存和本地内存的关系
所有的变量都存储在主内存中, 同时每个线程也有自己独立的工作内存, 工作内存中所有的变量内容都是主内存的拷贝.
线程不能直接读写主内存中的变量, 而是只能操作自己工作内存中的变量, 再同步到主内存中 . 即在修改变量之前, 要先执行第一步规定的,把数据拷贝到工作内存中再修改, 再同步.
主内存是多个线程共享的 , 但线程间不共享工作内存, 如果线程间需要通信, 必须借助主内存中转来完成.
总结: 所有的共享变量, 是存在于主内存中的, 每个线程有自己的本地内存, 而且线程读写共享数据也是通过本地内存交换的,正是由于有交换的过程, 并且这个交换不是实时的, 所以才导致了可见性的问题.
Volatile
参考 https://blog.csdn.net/lc13571525583/article/details/90345760
保证可见性
volatile是两条实现原则:
1.Lock前缀指令会引起处理器缓存会写到内存
当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中
2.一个处理器的缓存回写到内存会导致其他处理器的缓存失效
处理器使用嗅探技术保证内部缓存 系统内存和其他处理器的缓存的数据在总线上保持一致。
上一个问题的解决
public class JMMDemo {
//加上volatile保证可见性
private static volatile int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num==0){}
}).start();
TimeUnit.SECONDS.sleep(1);
num=1;
System.out.println(num);
}
}
不保证原子性
线程A在执行任务的时候,不能被打扰,也不能被分割
public class Demo02 {
private static volatile int num = 0;//不保证原子性
public static synchronized void add(){//保证原子性
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
如果不加lock 和synchronized 怎么保证原子性
使用JUC包下的原子类解决原子性问题
public class Demo02 {
private static AtomicInteger num = new AtomicInteger();//不保证原子性
public static void add(){//保证原子性
//num++;
num.getAndIncrement();//+1 底层CAS
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
禁止指令重排
内存屏障 CPU指令 作用:
1.保证特定操作的执行顺序
2.可以保证内存某个变量的可见性(volatile)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k28pMWaw-1615907409184)(image-20201206211954052.png)]
彻底玩转单例模式
饿汉式
package com.xin.single;
//饿汉式
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 Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
懒汉式 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 Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
使用放射破解单例模式
//使用反射破解单例模式
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan lazyMan1 = LazyMan.getInstance();
Constructor<? extends LazyMan> declaredConstructor = lazyMan1.getClass().getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//破坏私有性
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
反射解决方案
private LazyMan() {
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要试图用反射破坏单例");
}
}
}
再次破解
//只使用放射来建立
LazyMan lazyMan2 = declaredConstructor.newInstance();
LazyMan lazyMan1 = declaredConstructor.newInstance();
再次解决
private static boolean flag = true;
private LazyMan() {
if(flag == true){
flag=false;}
else{
throw new RuntimeException("不要试图用反射破坏单例");
}
}
但是我们也可以通过反射获得flag的值…
最终
枚举使用单例
public enum EnumSingle {
INSTANCE;
private EnumSingle(){}
public static EnumSingle getInstance(){
return INSTANCE;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOrHfrdS-1615907409195)(image-20201206230320387.png)]
深入理解CAS
什么是CAS 乐观锁 无锁算法
public class CASdemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//期望 目标值 如果期望值达到了就更新 CAS是CPU原语
System.out.println(atomicInteger.compareAndSet(2020, 2021));
atomicInteger.getAndIncrement();//cas
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
}
}
Unsafe
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nw8GTyn5-1615907409198)(image-20201207145903128.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wB9gOMSA-1615907409200)(image-20201207150133501.png)]
使用自旋保证原子性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZvnwquW-1615907409203)(image-20201207150237506.png)]
CAS:比较当前工作内存中的值和主存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环
缺点:
1.循环会耗时
2.一次只能保证一个共享变量的原子性
3.ABA问题
CAS ABA问题(狸猫换太子)
乐观锁
public class CASdemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//期望 目标值 如果期望值达到了就更新 CAS是CPU原语
//捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
}
原子引用
AtomicStampedReference//解决ABA
解决ABA问题 对应的思想:乐观锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZSJoAAW-1615907409206)(image-20201207153218758.png)]
public class CASdemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> asr = new AtomicStampedReference<Integer>(1, 1);
//CAS比较并交换
new Thread(() -> {
int stamp = asr.getStamp();
System.out.println("a-" + stamp);//版本号
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(asr.compareAndSet(1, 2, asr.getStamp(), asr.getStamp() + 1));
System.out.println(asr.getReference());
}, "a").start();
new Thread(() -> {
int stamp = asr.getStamp();
System.out.println("b-" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(asr.compareAndSet(1, 2, asr.getStamp(), asr.getStamp() + 1));
}, "b").start();
System.out.println(asr.getReference());
}
}
各种锁的理解
悲观锁与乐观锁
乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
悲观锁 (上锁)
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
乐观锁 (不上锁)
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
https://blog.csdn.net/qq_34337272/article/details/81072874
公平锁和不公平锁
公平锁: 非常公平 不可以插队
不公平锁 非常不公平 可以插队(默认是非公平 )
public ReentrantLock() {
sync = new NonfairSync();
}
new ReentrantLock(true);//构造器改为公平
可重入锁
可重入锁的前提是当前持锁线程的可重入(获得锁的线程可以再次进入获得锁的同步代码块)
递归锁
https://blog.csdn.net/u012545728/article/details/80843595
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaKnoz30-1615907409208)(image-20201207165119871.png)]
synchronized
public class test {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.call();
},"a").start();
new Thread(()->{
phone.call();
},"b").start();
}
}
class Phone {
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
msg();
}
public synchronized void msg(){
System.out.println(Thread.currentThread().getName()+"msg");
}
}
lock
package com.xin.cas;
import com.xin.spincas.SpinLock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class test {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.call();
},"a").start();
new Thread(()->{
phone.call();
},"b").start();
}
}
class Phone {
ReentrantLock lock = new ReentrantLock();
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
msg();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void msg(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"msg");
} finally {
lock.unlock();
}
}
}
自旋锁
spinlock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iQxN9dkO-1615907409238)(image-20201207150237506.png)]
自定义一个锁
package com.xin.spincas;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
static AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void mylock(){
System.out.println(Thread.currentThread()+"mylock");
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myunlock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread()+"myunlock");
atomicReference.compareAndSet(thread, null);
}
}
谁没有拿到锁谁就自旋 等解锁
package com.xin.cas;
import com.xin.spincas.SpinLock;
import java.util.concurrent.TimeUnit;
public class test {
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(()->{
spinLock.mylock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLock.myunlock();
}
},"a").start();
new Thread(()->{
spinLock.mylock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLock.myunlock();
}
},"b").start();
}
}
死锁排查
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xQVkRi3o-1615907409239)(image-20201207170755430.png)]
四大条件 互斥性 循环等待 不可剥夺 请求保持
import java.util.concurrent.TimeUnit;
public class DeadLockDemo implements Runnable {
private String a;
private String b;
public DeadLockDemo(String a, String b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (a){
System.out.println(Thread.currentThread().getName()+"get "+b);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+"get "+a);
}
}
}
}
class test1{
public static void main(String[] args) {
String a = "a";
String b = "b";
new Thread(new DeadLockDemo(a,b),"a").start();
new Thread(new DeadLockDemo(b,a),"b").start();
}
}
怎么排查
jps -l
查看所有的进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qi2c91BE-1615907409243)(image-20201207201323721.png)]
jstack + 进程号
查看堆栈信息
thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myunlock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread()+"myunlock");
atomicReference.compareAndSet(thread, null);
}
}
谁没有拿到锁谁就自旋 等解锁
```java
package com.xin.cas;
import com.xin.spincas.SpinLock;
import java.util.concurrent.TimeUnit;
public class test {
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(()->{
spinLock.mylock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLock.myunlock();
}
},"a").start();
new Thread(()->{
spinLock.mylock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLock.myunlock();
}
},"b").start();
}
}
死锁排查
[外链图片转存中…(img-xQVkRi3o-1615907409239)]
四大条件 互斥性 循环等待 不可剥夺 请求保持
import java.util.concurrent.TimeUnit;
public class DeadLockDemo implements Runnable {
private String a;
private String b;
public DeadLockDemo(String a, String b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (a){
System.out.println(Thread.currentThread().getName()+"get "+b);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+"get "+a);
}
}
}
}
class test1{
public static void main(String[] args) {
String a = "a";
String b = "b";
new Thread(new DeadLockDemo(a,b),"a").start();
new Thread(new DeadLockDemo(b,a),"b").start();
}
}
怎么排查
jps -l
查看所有的进程
[外链图片转存中…(img-qi2c91BE-1615907409243)]
jstack + 进程号
查看堆栈信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d532cZRt-1615907409244)(image-20201207201553531.png)]