JUC编程
源码+官方文档
java.util 工具包
current
业务
2、线程和进程
线程、进程
- 进程:一个程序,程序的集合一个
- 进程:包含多个线程,至少包含一个
并发和并行
并发:同一时间段,多个任务都在执行。(多线程操作同一个资源)
cpu 单核 模拟多条线程,快速交替并行:单位时间内,多个任务同时执行
多核,多个线程可以同时执行
java默认有几个线程?
main线程、GC
线程:某个单个小功能就是由线程控制的。java真的可以开启线程吗?
只能通过native方法(底层的C++),不能自己开启
并发编程:充分利用CPU的资源
回顾多线程
线程状态
- new 新生
- Runnable 运行
- Blocked阻塞
- wait 等待
- time_waitting 超时等待
- terminated 终止
创建线程的方式
- 继承:Thread
- 接口: Runnable
- 接口: Callable
- 通过线程池创建对象
wait 和sleep的区别
1、来自不同的类
wait object类
sleep Thread类
2、关于锁的释放
wait会释放锁,sleep睡觉了,抱着锁睡觉,不会释放
3、使用的范围是不同的
wait 必须在同步代码块中
sleep可以在任何地方睡
4、是否需要捕获异常
wait不需要捕获异常
sleep必须要捕获异常
3、Lock锁(重点)
3.1、Synchronized(关键字,JVM)
传统synchronized
package com.zmy.demo;
//基本的卖票
/*
多线程
线程就是一个单独的资源类,没有任何的附属的操作
1、属性
2、方法
*/
public class SaleTicket {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源丢入线程
final Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口 jdk1.8 lamada表达式 (参数)-> { 代码 }
new Thread(()->{
for (int i = 1; i < 500; i++){
ticket.sale();
}
},"a").start();
new Thread(()->{
for (int i = 1; i < 500; i++){
ticket.sale();
}
},"b").start();
new Thread(()->{
for (int i = 1; i < 500; i++){
ticket.sale();
}
},"c").start();
}
}
class MyThread implements Runnable{
public void run() {
}
}
//资源类OOP
class Ticket{
private int number = 500;
//卖票的方式
//不加synchronized,结果是乱的
//synchronized本质是:队列,锁
//
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+
"卖出了第"+(number--)+"票,剩余"+number);
}
}
}
3.2、Lock(类)
Lock
Lock三部曲
- new ReentrantLock()
- lock.lock(); 加锁
- finally => lock.unlock(); 解锁
- 锁的分类:
package com.zmy.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//基本的卖票
/*
多线程
线程就是一个单独的资源类,没有任何的附属的操作
1、属性
2、方法
*/
public class SaleTicket01 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源丢入线程
Ticket1 ticket = new Ticket1();
//@FunctionalInterface 函数式接口 jdk1.8 lamada表达式 (参数)-> { 代码 }
new Thread(()->{
for (int i = 1; i < 500; i++){
ticket.sale();
}
},"a").start();
new Thread(()->{
for (int i = 1; i < 500; i++){
ticket.sale();
}
},"b").start();
new Thread(()->{
for (int i = 1; i < 500; i++){
ticket.sale();
}
},"c").start();
}
}
class Ticket1{
//卖票的方式
//Lock三部曲
//1、new ReentrantLock();
//2、lock.lock(); 加锁
//3、finally => lock.unlock(); 解锁
private int number = 500;
Lock lock= new ReentrantLock();
public void sale(){
lock.lock();
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+
"卖出了第"+(number--)+"票,剩余"+number);
};
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
3.3、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. 生产者和消费者的关系
4.1、Synchronized 版本
package com.zmy.ProAndConsumer;
/**
* 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一变量资源 num = 0
* A = num + 1
* B = num - 1
*/
public class ConsumeAndProduct {
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();
}
}
//判断等待,业务,通知
class Data { //数字 资源类
private int num = 0;
// +1
public synchronized void increment() throws InterruptedException {
// 判断等待
if (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 判断等待
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 -1 执行完毕
this.notifyAll();
}
}
4.2、存在问题
如果存在四个线程,会出现虚假唤醒
A=>1
B=>0
C=>1
A=>2
C=>3
B=>2
B=>1
解决方式 ,
if 改为while即可,防止虚假唤醒
package com.zmy.ProAndConsumer;
/**
* 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一变量资源 num = 0
* A = num + 1
* B = num - 1
*/
public class ConsumeAndProduct {
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.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();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
//判断等待,业务,通知
class Data { //数字 资源类
private int num = 0;
// +1
public synchronized void increment() throws InterruptedException {
// 判断等待
while (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 判断等待
while (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 -1 执行完毕
this.notifyAll();
}
}
A=>1
B=>0
C=>1
B=>0
A=>1
B=>0
C=>1
4.3、Lock版
package com.zmy.ProAndConsumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一变量资源 num = 0
* A = num + 1
* B = num - 1
*/
public class LockPAC {
public static void main(String[] args) {
Data1 data = new Data1();
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 Data1 { //数字 资源类
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); //替代同步监视器
//condition.await(); //等待
//condition.signalAll(); //唤醒全部
// +1
public void increment() throws InterruptedException {
try {
lock.lock();
//业务代码
// 判断等待
while (num != 0) {
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
try {
lock.lock();
//业务代码
// 判断等待
while (num == 0) {
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 -1 执行完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.4、Condition的优势
精准的通知和唤醒线程!
指定通知的下一个进行顺序怎么办? 可以用Condition来指定通知进程
package com.zmy.ProAndConsumer;
/**
* A执行完调用B
* B执行完调用C
* C执行完调用A
*
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Condition {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 10;i++){
try {
data2.printA();
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10;i++){
try {
data2.printB();
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10;i++){
try {
data2.printC();
} catch (Exception e) {
e.printStackTrace();
}
}
},"C").start();
}
}
class Data2{ //资源类 Lock
private Lock lock = new ReentrantLock();
private java.util.concurrent.locks.Condition condition1 = lock.newCondition();
private java.util.concurrent.locks.Condition condition2 = lock.newCondition();
private java.util.concurrent.locks.Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA(){
try {
lock.lock();
//业务代码
while (number != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"====AAAAAA");
//唤醒指定的人 B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
public void printB(){
try {
lock.lock();
//业务代码
while (number != 2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"====BBBB");
//唤醒指定的人 B
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
public void printC(){
try {
lock.lock();
//业务代码
while (number != 3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"====CCCCC");
//唤醒指定的人 B
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
}
5、八锁现象
8锁,就是关于锁的八个问题(有延迟才和普通方法显示差别!!!)
1、标准情况下,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
2、发短信延迟四秒,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到(代码的执行顺序)谁先执行!另外一个则等待!
3、增加了一个普通方法后! 执行的顺序又是什么 普通方法
4、两个对象,两个同步方法 和延迟时间有关系(延迟时间短的先执行)
5、一个对象,两个静态同步方法 执行顺序是什么? 锁的是类,与类的执行代码顺序有关系
6、两个对象,两个静态同步方法 两个对象用的是同一个Class
7、一个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间
8、两个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间和这个执行顺序
package com.zmy.lock;
/**
* 8锁,就是关于锁的八个问题(有延迟才和普通方法显示差别!!!)
* 1、标准情况下,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
* 2、发短信延迟四秒,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
* 3、增加了一个普通方法后! 执行的顺序又是什么 普通方法
* 4、两个对象,两个同步方法 和延迟时间有关系(延迟时间短的先执行)
* 5、一个对象,两个静态同步方法 执行顺序是什么? 锁的是类,与类的执行代码顺序有关系
* 6、两个对象,两个静态同步方法 两个对象用的是同一个Class
* 7、一个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间
* 8、两个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间和这个执行顺序
*/
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
//锁的存在
new Thread(()->{
phone2.sendSms();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}).start();
new Thread(()->{
phone.call();
}).start();
new Thread(()->{
phone.hello();
}).start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者 也就是phone
//两个方法用的是同一把锁,谁先拿到谁先执行
//static 静态方法
//类一加载就有了! Class模板
public static synchronized void sendSms(){
System.out.println("发短信");
};
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打电话");
}
//这里没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
6、集合类不安全
6.1、List不安全
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
public static void main(String[] args) {
List<Object> arrayList = new ArrayList<>();
for(int i=1;i<=10;i++){
new Thread(()->{
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
},String.valueOf(i)).start();
}
}
}
会导致 java.util.ConcurrentModificationException 并发修改异常!
ArrayList 在并发情况下是不安全的
解决方案:
public class ListTest {
public static void main(String[] args) {
/**
* 解决方案
* 1. List<String> list = new Vector<>();
* 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3. List<String> list = new CopyOnWriteArrayList<>();
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <=10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。
多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;
CopyOnWriteArrayList比Vector厉害在哪里
- Vector底层是使用synchronized关键字来实现的:效率特别低下。
- CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
6.2、set不安全
Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
- 使用Collections工具类的synchronized包装的Set类
- 使用CopyOnWriteArraySet 写入复制的JUC解决方案
public class SetTest {
public static void main(String[] args) {
/**
* 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2. Set<String> set = new CopyOnWriteArraySet<>();
*/
// Set<String> set = new HashSet<>();
Set<String> set = new CopyOnWriteArraySet<>();
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();
}
}
}
HashSet底层是什么?
hashSet底层就是一个HashMap;
6.3、Map不安全
//map 是这样用的吗? 不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量
- currentHashMap
7、Callable
1、可以有返回值;
2、可以抛出异常;
3、方法不同,run()/call()
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
for (int i = 1; i < 10; i++) {
MyThread1 myThread1 = new MyThread1();
FutureTask<Integer> futureTask = new FutureTask<>(myThread1);
// 放入Thread中使用,结果会被缓存
new Thread(futureTask,String.valueOf(i)).start();
// 这个get方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信
int a = futureTask.get();
System.out.println("返回值:" + s);
}
}
}
class MyThread1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
8. 常用的辅助类
8.1、CountDownLatch
public class CountDownLatchDemo {
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();
}
countDownLatch.await(); // 等待计数器归零 然后向下执行
System.out.println("close door");
}
}
主要方法:
- countDown 减一操作;
- await 等待计数器归零
await 等待计数器归零,就唤醒,再继续向下运行
8.2、CyclickBarrier
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 主线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
System.out.println("召唤神龙");
});
for (int i = 1; i <= 7; i++) {
// 子线程
int finalI = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集了第" + finalI + "颗龙珠");
try {
cyclicBarrier.await(); // 加法计数 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3、Semaphore
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量,停车位,限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i <= 6; i++) {
new Thread(() -> {
// acquire() 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
}catch (Exception e) {
e.printStackTrace();
}finally {
semaphore.release(); // release() 释放
}
}).start();
}
}
}
Thread-1抢到车位
Thread-0抢到车位
Thread-2抢到车位
Thread-0离开车位
Thread-2离开车位
Thread-1离开车位
Thread-5抢到车位
Thread-3抢到车位
Thread-4抢到车位
Thread-5离开车位
Thread-3离开车位
Thread-6抢到车位
Thread-4离开车位
Thread-6离开车位
Process finished with exit code 0
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
9. 读写锁
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
int num = 6;
for (int i = 1; i <= num; i++) {
int finalI = i;
new Thread(() -> {
myCache.write(String.valueOf(finalI), String.valueOf(finalI));
},String.valueOf(i)).start();
}
for (int i = 1; i <= num; i++) {
int finalI = i;
new Thread(() -> {
myCache.read(String.valueOf(finalI));
},String.valueOf(i)).start();
}
}
}
/**
* 方法未加锁,导致写的时候被插队
*/
class MyCache {
private volatile Map<String, String> map = new HashMap<>();
public void write(String key, String value) {
System.out.println(Thread.currentThread().getName() + "线程开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "线程写入ok");
}
public void read(String key) {
System.out.println(Thread.currentThread().getName() + "线程开始读取");
map.get(key);
System.out.println(Thread.currentThread().getName() + "线程写读取ok");
}
}
2线程开始写入
2线程写入ok
3线程开始写入
3线程写入ok
1线程开始写入 # 插入了其他线程的写入,导致数据不一致
4线程开始写入
4线程写入ok
1线程写入ok
6线程开始写入
6线程写入ok
5线程开始写入
5线程写入ok
1线程开始读取
1线程写读取ok
2线程开始读取
2线程写读取ok
3线程开始读取
3线程写读取ok
4线程开始读取
4线程写读取ok
5线程开始读取
6线程开始读取
6线程写读取ok
5线程写读取ok
Process finished with exit code 0
所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。
我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache2 myCache = new MyCache2();
int num = 6;
for (int i = 1; i <= num; i++) {
int finalI = i;
new Thread(() -> {
myCache.write(String.valueOf(finalI), String.valueOf(finalI));
},String.valueOf(i)).start();
}
for (int i = 1; i <= num; i++) {
int finalI = i;
new Thread(() -> {
myCache.read(String.valueOf(finalI));
},String.valueOf(i)).start();
}
}
}
class MyCache2 {
private volatile Map<String, String> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void write(String key, String value) {
lock.writeLock().lock(); // 写锁
try {
System.out.println(Thread.currentThread().getName() + "线程开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "线程写入ok");
}finally {
lock.writeLock().unlock(); // 释放写锁
}
}
public void read(String key) {
lock.readLock().lock(); // 读锁
try {
System.out.println(Thread.currentThread().getName() + "线程开始读取");
map.get(key);
System.out.println(Thread.currentThread().getName() + "线程写读取ok");
}finally {
lock.readLock().unlock(); // 释放读锁
}
}
}
1线程开始写入
1线程写入ok
6线程开始写入
6线程写入ok
3线程开始写入
3线程写入ok
2线程开始写入
2线程写入ok
5线程开始写入
5线程写入ok
4线程开始写入
4线程写入ok
1线程开始读取
5线程开始读取
2线程开始读取
1线程写读取ok
3线程开始读取
2线程写读取ok
6线程开始读取
6线程写读取ok
5线程写读取ok
4线程开始读取
4线程写读取ok
3线程写读取ok
Process finished with exit code 0
10. 阻塞队列
10.1、BlockQueue
是Collection的一个子类
什么情况下我们会使用阻塞队列
多线程并发处理、线程池
BlockingQueue 有四组api
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞,等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(timenum.timeUnit) |
移出 | remove | poll | take | poll(timenum,timeUnit) |
判断队首元素 | element | peek | - | - |
/**
* 抛出异常
*/
public static void test1(){
//需要初始化队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//抛出异常:java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//如果多移除一个
//这也会造成 java.util.NoSuchElementException 抛出异常
System.out.println(blockingQueue.remove());
}
=======================================================================================
/**
* 不抛出异常,有返回值
*/
public static 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"));
//添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//弹出 如果没有元素 只会返回null 不会抛出异常
System.out.println(blockingQueue.poll());
}
=======================================================================================
/**
* 等待 一直阻塞
*/
public static 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 static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
System.out.println("开始等待");
blockingQueue.offer("d",2, TimeUnit.SECONDS); //超时时间2s 等待如果超过2s就结束等待
System.out.println("结束等待");
System.out.println("===========取值==================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println("开始等待");
blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
System.out.println("结束等待");
}
10.2、同步队列
同步队列 没有容量,也可以视为容量为1的队列;
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
put方法 和 take方法;
Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;
put了一个元素,就必须从里面先take出来,否则不能再put进去值!
并且SynchronousQueue 的take是使用了lock锁保证线程安全的。
package com.marchsoft.queue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
/**
* Description:
*
* @author jiaoqianjin
* Date: 2020/8/12 10:02
**/
public class SynchronousQueue {
public static void main(String[] args) {
BlockingQueue<String> synchronousQueue = new java.util.concurrent.SynchronousQueue<>();
// 网queue中添加元素
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "put 01");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 02");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 03");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 取出元素
new Thread(()-> {
try {
System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
}catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
Thread-0put 01
Thread-1take1
Thread-0put 02
Thread-1take2
Thread-0put 03
Thread-1take3
Process finished with exit code 0
11. 线程池
线程池:三大方式、七大参数、四种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术
线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
11.1、线程池的好处:
1、降低资源的消耗;
2、提高响应的速度;
3、方便管理;
线程复用、可以控制最大并发数、管理线程;
11.2、线程池:三大方法
- ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
- ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
- ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//线程池用完必须要关闭线程池
try {
for (int i = 1; i <=100 ; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
11.3、七大参数
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;
}
阿里巴巴规范
阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。
public class PollDemo {
public static void main(String[] args) {
// 获取cpu 的核数
int max = Runtime.getRuntime().availableProcessors();
ExecutorService service =new ThreadPoolExecutor(
2,
max,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i <= 10; i++) {
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
service.shutdown();
}
}
}
11.4、四种拒绝策略
- new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize
new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理
new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常
11.5、如何设置线程池的大小
1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
// 获取cpu 的核数
int max = Runtime.getRuntime().availableProcessors();
ExecutorService service =new ThreadPoolExecutor(
2,
max,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
2、I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
小结和扩展
了解io密集型、CPU密集型 调优
池的最大线程如何设置
12、四大函数式接口
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//超级多的@FunctionalInterface
//简化编程模型,在新版本的框架底层大量应用
//foreach()的参数也是一个函数式接口,消费者类的函数式接口
16、JMM
请你谈谈你对Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存;
- 线程加锁前,必须读取主存中的最新值到工作内存中;
- 加锁和解锁是同一把锁;
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操作之前,必须把此变量同步回主内存
17、volatile
17.1、保证可见性
public class JMMDemo01 {
// 如果不加volatile 程序会死循环
// 加了volatile是可以保证可见性的
private volatile static Integer number = 0;
public static void main(String[] args) {
//main线程
//子线程1
new Thread(()->{
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//子线程2
new Thread(()->{
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
number=1;
System.out.println(number);
}
}
17.2、不保证原子性
原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功shi,要么同时失败。
/**
* 不保证原子性
* number <=2w
*
*/
public class VDemo02 {
private static volatile int number = 0;
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();
}
while (Thread.activeCount()>2){
//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",num="+number);
}
}
如果不加lock和synchronized ,怎么样保证原子性?
解决方法:使用JUC下的原子包下的class;
public class VDemo02 {
private static volatile AtomicInteger number = new AtomicInteger();
public static void add(){
// number++;
number.incrementAndGet(); //底层是CAS保证的原子性
}
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();
}
while (Thread.activeCount()>2){
//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",num="+number);
}
}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;
原子类为什么这么高级?
17.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指令。作用:
1、保证特定的操作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
18、单例模式
18.1、饿汉式
/**
* 饿汉式单例
*/
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;
}
}
18.2、DCL懒汉式
//懒汉式单例模式
public class LazyMan {
private static boolean key = false;
private LazyMan(){
synchronized (LazyMan.class){
if (key==false){
key=true;
}
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
//需要加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 就有可能出现指令重排问题
* 比如执行的顺序是1 3 2 等
* 我们就可以添加volatile保证指令重排问题
*/
}
}
}
return lazyMan;
}
//单线程下 是ok的
//但是如果是并发的
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//Java中有反射
// LazyMan instance = LazyMan.getInstance();
Field key = LazyMan.class.getDeclaredField("key");
key.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视了私有的构造器
LazyMan lazyMan1 = declaredConstructor.newInstance();
key.set(lazyMan1,false);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyMan1);
System.out.println(instance == lazyMan1);
}
}
19、深入理解CAS
19.1、什么是CAS?
public class casDemo {
//CAS : compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//boolean compareAndSet(int expect, int update)
//期望值、更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
//因为期望值是2020 实际值却变成了2021 所以会修改失败
//CAS 是CPU的并发原语
atomicInteger.getAndIncrement(); //++操作
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
19.2、unsafe类(java操作内存的后门)
总结:
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
- 循环会耗时;
- 一次性只能保证一个共享变量的原子性;
- 它会存在ABA问题
CAS:ABA问题?(狸猫换太子)
线程1:期望值是1,要变成2;
线程2:两个操作:
- 1、期望值是1,变成3
- 2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
package com.zmy.CAS;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
//boolean compareAndSet(int expect, int update)
//期望值、更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
//==========================捣乱的线程==================
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021,2020));
System.out.println(atomicInteger.get());
//==================期望的线程============================
//因为期望值是2020 实际值却变成了2021 所以会修改失败
//CAS 是CPU的并发原语
//atomicInteger.getAndIncrement(); //++操作
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
}
}
20、原子引用
解决ABA问题,对应的思想:就是使用了乐观锁~
带版本号的 原子操作!
坑:如果泛型是包装类,要注意对象得引用问题-
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升!
那么如果我们使用小于128的时候:
21、各种锁的理解
21.1、公平锁、非公平锁
公平锁:非常公平;不能插队的,必须先来后到;
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
非公平锁:非常不公平,允许插队的,可以改变顺序。(默认都是非公平锁)
/**
* 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();
}
21.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");
}
}
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 必须数量相同;
- 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
21.3、自旋锁
spinlock
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
自我设计自旋锁:
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();
}
}
21.4、死锁
死锁测试,怎么排除死锁:
package com.ogj.lock;
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
进程进程号 找到死锁信息
一般情况信息在最后: