java.util.concurrnet
java.util.concurrent.atomic
java.util.concurrent.locks
进程:一个程序的实例
线程:是一个进程的实体,CPU调度和分配的基本单位
java默认有两个线程,main方法和GC
开启线程的方式:Thread、Runnable、Callable
Java本质是无法开启线程的,是调用本地方法(c++)来开启线程的,java无法操作硬件。
并发编程:并发、并行
并发:多个线程操作同一个资源
并行:多个线程同时进行
public static void main(String[] args) {
int count = Runtime.getRuntime().availableProcessors();
System.out.println("CPU处理器数量:" + count); // 检测cpu逻辑处理器数量
}
并发编程的本质:充分利用CPU的资源
线程的六种状态
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
wait和sleep的区别
属于不同类
wait属于java.lang.Object类
sleep属于java.util.concurrent.TimeUnit类
关于锁的释放
wait会释放锁
sleep不会释放锁
使用范围不同
wait必须在同步代码块使用
sleep可以在任意地方使用
是否需要捕获异常
wait不需要捕获异常
sleep必须需要捕获异常
传统synchronized锁
public class SynchronizedDemo {
public static void main(String[] args) {
// 多个线程操作统一资源,并发
final Ticket ticket = new Ticket();
// 线程A
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.sale();
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "B").start();
// 线程C
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "C").start();
}
}
// 售票
class Ticket {
// 票数
private int number = 30;
// 售票方法
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了票, 剩余" + --number);
}
}
}
Lock锁
public class LockDemo {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
// 线程A
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "B").start();
// 线程C
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "C").start();
}
}
class Ticket2 {
// 票数
private int number = 30;
Lock lock = new ReentrantLock();
// 售票方法
public void sale() {
lock.lock(); // 加锁
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了票, 剩余" + --number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
}
}
synchronized 和 Lock 的区别
synchronized | Lock |
---|---|
java的一个关键字 | 一个接口,有很多实现类 |
无法判断锁的状态 | 可以判断是否获取了锁 |
可以自动释放锁 | 只能手动在finally中释放锁,否则会死锁 |
假设A线程获取锁的时候,B线程等待,如果A线程阻塞了,那么B线程智能永远等待 | Lock可以尝试获取锁,有多种获取锁的方式 |
synchronized是可重入锁,非公平锁,不可以中断 | Lock是可重入锁,默认是非公平锁(可以设置成公平锁),可以中断 |
功能单一,适合锁少量同步代码 | API丰富,灵活度高,适合锁大量同步代码 |
生产者消费者问题是一个典型的并发问题,我们要解决的就是实现同步。一般解决同步问题问题,我们会想到synchronized和Lock,我们先用synchronized实现这个问题。
synchronized版本
/**
* 线程之间的通信问题:生产者和消费者问题
* 线程交替执行, A, B操作同一变量 number = 0
* A :number + 1
* B :number - 1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
// 线程A
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
// 资源类
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=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
改成4条线程同时执行,再次测试
public class A {
public static void main(String[] args) {
Data data = new Data();
// 线程A
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
// 线程
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
// 线程D
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
// 资源类
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();
}
}
四条线程同时执行时,结果有错误,原因:wait方法存在虚假唤醒问题
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
C=>1
A=>2
C=>3
B=>2
C=>3
D=>2
D=>1
D=>0
C=>1
D=>0
C=>1
D=>0
解决办法:将wait方法放入while循环中,可以避免虚假唤醒问题
public class A {
public static void main(String[] args) {
Data data = new Data();
// 线程A
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
// 线程
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
// 线程D
new Thread(() -> {
for (int i = 0; i < 5; 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();
}
}
再次测试:
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
JUC 版的生产者和消费者问题
public class B {
public static void main(String[] args) {
Data2 data2 = new Data2();
// 线程A
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
// 线程C
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
// 线程D
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
class Data2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// +1方法
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 (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);
// 通知其他线程,-1完成了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}
测试结果:结果是正常的,但是ABCD四个线程不是有序执行的
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
A=>1
D=>0
A=>1
D=>0
使用Condition实现精准通知唤醒
/**
* Condition 实现精准唤醒
*/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
// 线程A
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printA();
}
}, "A").start();
// 线程B
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printB();
}
}, "B").start();
// 线程C
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printC();
}
}, "C").start();
}
}
class Data3 {
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA() {
lock.lock(); // 加锁
try {
while (number != 1) {
condition1.await(); // 等待
}
number = 2;
System.out.println(Thread.currentThread().getName() + "->AAAAAAAAA");
condition2.signal(); // 唤醒B
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
public void printB() {
lock.lock(); // 加锁
try {
while (number != 2) {
condition2.await();
}
number = 3;
System.out.println(Thread.currentThread().getName() + "->BBBBBBBBB");
condition3.signal(); // 唤醒C
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
public void printC() {
lock.lock(); // 加锁
try {
while (number != 3) {
condition3.await();
}
number = 1;
System.out.println(Thread.currentThread().getName() + "->CCCCCCCCC");
condition1.signal(); // 唤醒A
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}
以下代码是先输出发短信还是发短信?
结果都是:发短信 电话
synchronized锁的对象是方法的调用者
两个方法用的是同一个锁,谁先拿到谁先执行
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
// 线程A
new Thread(() -> {
phone.sendSms();
}, "A").start();
// 睡眠1秒
TimeUnit.SECONDS.sleep(1);
// 线程B
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendSms() {
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Phone2 phone = new Phone2();
// 线程A
new Thread(() -> {
phone.sendSms();
}, "A").start();
// 线程B
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone2 {
public synchronized void sendSms() {
// 睡眠4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
以下代码输出结果是?
结果:hello 发短信
hello方法没有锁,不是同步方法,不受锁的影响,所以先执行
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
new Thread(() -> {
phone.sendSms();
}, "A").start();
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone3 {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void hello() {
System.out.println("hello");
}
}
以下代码输出结果是?
结果:打电话 发短信
两个对象,两个调用者,他们获取的锁不一样
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone4 {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
以下代码输出结果是?
结果:发短信 打电话
因为调用的方法加了static关键字,所以锁的是同一个类,所以依次执行
public class Test5 {
public static void main(String[] args) {
Phone5 phone = new Phone5();
new Thread(() -> {
phone.sendSms();
}).start();
new Thread(() -> {
phone.call();
}).start();
}
}
class Phone5 {
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
以下代码输出结果是?
结果:发短信 打电话
虽然是两个对象,但是调用的是同一个类,而且锁的是类,所以是按照顺序依次执行
public class Test6 {
public static void main(String[] args) {
Phone6 phone1 = new Phone6();
Phone6 phone2 = new Phone6();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone6 {
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
以下代码输出结果是?
结果:打电话 发短信
因为两个使用的不是同一个锁,static修饰的同步方法锁的是类,非静态的同步方法锁的是该方法
public class Test7 {
public static void main(String[] args) {
Phone7 phone = new Phone7();
new Thread(() -> {
phone.sendSms();
}, "A").start();
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone7 {
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
以下代码输出结果是?
结果:打电话 发短信
因为两个使用的不是同一个锁,static修饰的同步方法锁的是类,非静态的同步方法锁的是该方法,且是两个调用对象
public class Test8 {
public static void main(String[] args) {
Phone8 phone1 = new Phone8();
Phone8 phone2 = new Phone8();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone8 {
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
List不安全
public class ListTest {
public static void main(String[] args) {
// 多线程同时向List中add数据
List<String> list = new ArrayList<>();
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();
}
}
}
测试报错:java.util.ConcurrentModificationException 并发修改异常
解决方案:
使用Vector代替ArrayList, Vector是线程安全的
public class ListTest {
public static void main(String[] args) {
List<String> list = new Vector<>(); // 使用Vector代替ArrayList,Vector是线程安全的
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();
}
}
}
使用Collections.synchronizedList(new ArrayList<>());创建List
public class ListTest {
public static void main(String[] args) {
// 使用Collections.synchronizedList(new ArrayList<>())创建
List<String> list = Collections.synchronizedList(new ArrayList<>());
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();
}
}
}
JUC: 使用CopyOnWriteArrayList代替ArrayList, 写入时复制
public class ListTest {
public static void main(String[] args) {
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();
}
}
}
Set不安全
public class SetTest {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
测试报错:java.util.ConcurrentModificationException 并发修改异常
使用Collections.synchronizedSet(new HashSet<>());创建Set
public class SetTest {
public static void main(String[] args) {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
使用CopyOnWriteArraySet创建Set
public class SetTest {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
Map不安全
public class MapTest {
public static void main(String[] args) {
Map<String, Object> 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();
}
}
}
测试报错:java.util.ConcurrentModificationException 并发修改异常
解决方案:
使用Collections.synchronizedMap(new HashMap<>());创建Map
public class MapTest {
public static void main(String[] args) {
Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
for (int i = 1; i <= 60; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
使用new ConcurrentHashMap<>();创建Map
public class MapTest {
public static void main(String[] args) {
Map<String, Object> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 60; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
callable是创建线程的第三种方式
与之前两种的区别:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); // 有缓存,第二次不会再次输出
Integer result = (Integer) futureTask.get(); // 这个get方法,可能会产生阻塞,把他放在最后,或者异步调用
System.out.println(result);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1234;
}
}
CountDownLatch(减法计数器)
// 减法计数器
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() + "执行成功~");
countDownLatch.countDown(); // 数量-1
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,再继续向下执行
System.out.println("六个线程全部执行成功!");
}
}
CyclicBarrier(加法计数器)
// 加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 集齐7颗龙珠召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("7颗龙珠已集齐,召唤神龙~");
});
for (int i = 1; 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();
}
}
}
Semaphore(计数信号量)
// 计数信号量
public class SemaphoreDemo {
public static void main(String[] args) {
// 假设有3个车位,6个车去停
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取,如果已经满了,等待,等待被释放为止
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2); // 睡眠两秒,模拟使用过程完成
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放
}
}, String.valueOf(i)).start();
}
}
}
ReadWriteLock维护了一堆关联的locks,一个只用于读,一个用于写,读的时候可以多线程去读,写的时候只能一个线程去写
也被称作:独占锁和共享锁
public class ReadWriteLockDemo {
public static void main(String[] args) {
// 5个线程同时写入自定义缓存,要求一个一个写,防止写入错误,五个线程同时读缓存,读的顺序任意
MyCache cache = new MyCache();
// 5个线程同时写
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
cache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 5个线程同时读取
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
cache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁
// 存,写入
public void put(String key, Object value) {
readWriteLock.writeLock().lock(); // 写锁加锁
try {
System.out.println(Thread.currentThread().getName() + "正在写入,Key:" + key);
map.put(key, value); // 写入
System.out.println(Thread.currentThread().getName() + "写入成功");
} 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" + key);
map.get(key); // 读取
System.out.println(Thread.currentThread().getName() + "读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock(); // 读锁释放
}
}
}
什么时候阻塞?
BlockingQueue:阻塞队列
BlockingDueue:阻塞双端队列
父类:Queue,Collection
BlockingQueue的四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 等待,阻塞(一直等待) | 等待,阻塞(超时退出) |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(e, timeout, timeUnit) |
移除 | remove() | poll() | take() | poll(timeout, timeUnit) |
检测队列首部元素 | element() | peek() | - | - |
抛出异常方式
/**
* 抛出异常方式
*/
public static void test1() {
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3); // 队列大小为3
System.out.println(queue.add("a"));
System.out.println(queue.add("b"));
System.out.println(queue.add("c"));
System.out.println(queue.element()); // 检测队列首部元素
// System.out.println(queue.add("d")); // 插入数据大于队列长度抛出异常:java.lang.IllegalStateException: Queue full 队列已满
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
// System.out.println(queue.remove()); // 队列中没有数据,再次移除会抛出异常:java.util.NoSuchElementException 没有元素异常
}
当添加的数据大于队列大小时会抛出异常:java.lang.IllegalStateException: Queue full 队列已满
当移除数据时队列为空时会抛出异常:java.util.NoSuchElementException 没有元素异常
有返回值,不抛出异常
/**
* 有返回值,不抛出异常
*/
public static void test2() {
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("a")); // 返回值为true
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
System.out.println(queue.element()); // 检测队列首部元素
// System.out.println(queue.offer("d")); // 超出队列长度,返回值为false
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// System.out.println(queue.poll()); // 取出时队列为空,返回值为null
}
当添加数据大于队列大小时,不抛出异常,返回值为false
当移除数据队列为空时,不抛出异常,返回值为null
等待,阻塞(一直等待)
/**
* 等待,阻塞(一直等待)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(3);
queue.put("a"); // 无返回值
queue.put("b");
queue.put("c");
// queue.put("d"); // 超出队列长度,会一直等待队列有位置再添加到队列
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);
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
// System.out.println(queue.offer("d", 2, TimeUnit.SECONDS)); // 如果队列满了,等待两秒,超过两秒后退出,返回false
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 同步队列
同步队列没有容量,put进去了一个元素,必须take出来,才能继续使用
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " put a");
queue.put("a");
System.out.println(Thread.currentThread().getName() + " put b");
queue.put("b");
System.out.println(Thread.currentThread().getName() + " put c");
queue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " take " + queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " take " + queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " take " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
三大方法,七大参数,四种拒绝策略
Executors 线程池工具类创建线程的三大方法
// Executors 线程池工具类
public class PoolDemo {
public static void main(String[] args) {
// 创建线程的三种方法
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 创建单一线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建固定数量线程
ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的创建线程,根据所用主机性能
try {
for (int i = 1; i <= 50; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
源码分析
// newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, // 最大Integer.MAX_VALUE 约等于20亿,可能会导致OOM
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 发现这个三个方法底层都是调用了 new 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.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;
}
自定义线程池
public class PoolDemo {
public static void main(String[] args) {
// 自定义线程池
// 四种拒绝策略
// ThreadPoolExecutor.AbortPolicy() 默认拒绝策略,队列满了,对后面的不进行处理,抛出异常
// ThreadPoolExecutor.CallerRunsPolicy() 队列满了,让后面的回去,由调用该方法的线程自己执行
// ThreadPoolExecutor.DiscardPolicy() 队列满了,丢掉后面的任务,不抛出异常
// ThreadPoolExecutor.DiscardOldestPolicy() 队列满了,尝试和最早进入的线程竞争,如果竞争失败,丢掉后面的任务,不抛出异常
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心线程池大小
5, // 线程池最大核心大小
3, // 核心等待超时时长
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(3), // 阻塞队列大小为3
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.DiscardOldestPolicy()); // 拒绝策略
try {
// 最大处理线程 = 阻塞队列大小 + 最大线程池大小
for (int i = 1; i <= 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
最大线程应该如何去设置?
CPU密集型:
根据CPU线程数去设置最大线程大小
Runtime.getRuntime().availableProcessors() // 获取CPU线程数
IO密集型:
判断你的程序中十分耗IO的线程数,设置最大的线程数大于十分耗IO的线程数,一般是两倍
函数式接口:只有一个方法的接口
Function
函数型接口,有一个输入参数,有一个输出参数
源码:
@FunctionalInterface
public interface Function<T, R> {
// 传入参数T,返回类型R
R apply(T t);
}
例子:
public class Demo1 {
public static void main(String[] args) {
// 传入什么就返回什么
/*Function function = new Function() {
@Override
public String apply(String str) {
return str;
}
};*/
// 使用lamdba表达式简化
Function<String, String> function = (str) -> {
return str;
};
System.out.println(function.apply("abc"));
}
}
Predicate 断定性接口,有一个输入参数,返回值只能是布尔值
源码:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
例子:
public class Demo2 {
public static void main(String[] args) {
// 判断传入字符串是够为空
/*Predicate predicate = new Predicate() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};*/
// 使用lamdba表达式简化
Predicate<String> predicate = (str) -> {
return str.isEmpty();
};
System.out.println(predicate.test(""));
}
}
Consumer 消费型接口,一个输入参数,没有返回值
源码:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
例子:
public class Demo3 {
public static void main(String[] args) {
// 输入什么就打印什么
/*Consumer consumer = new Consumer() {
@Override
public void accept(String str) {
System.out.println(str);
}
};*/
// 使用lamdba表达式简化
Consumer<String> consumer = (str) -> {
System.out.println(str);
};
consumer.accept("哈哈");
}
}
Supplier 供给型接口,没有输入参数,有一个返回值
源码:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
例子:
public class Demo4 {
public static void main(String[] args) {
// 返回1024
/*Supplier supplier = new Supplier() {
@Override
public Integer get() {
return 1024;
}
};*/
// 使用lamdba表达式简化
Supplier<Integer> supplier = () -> {
return 1024;
};
System.out.println(supplier.get());
}
}
例子:
public class Test {
public static void main(String[] args) {
User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 23);
User user4 = new User(4, "d", 24);
User user5 = new User(6, "e", 25);
List<User> users = Arrays.asList(user1, user2, user3, user4, user5);
users.stream()
.filter((u) -> {
return u.getId() % 2 ==0;}) // 筛选id为偶数的用户
.filter((u) -> {
return u.getAge() > 23;}) // 筛选age大于23的用户
.map((u) -> {
return u.getName().toUpperCase();}) // 将用户名转成大写
.sorted((u1, u2) -> {
return u2.compareTo(u1);}) // 倒序
.limit(1) // 只截取1个用户
.forEach(System.out :: println); // 遍历输出结果
}
}
例子:计算1 ~ 1000000000的值的和
/**
* 自定义ForkJoin任务, 加法计算
*/
public class MyForkJoinTask extends RecursiveTask<Long> {
private Long start; // 开始值
private Long end; // 结束值
private Long temp = 10000L; // 临界值
public MyForkJoinTask(Long start, Long end) {
this.start = start;
this.end = end;
}
// 计算方法
@Override
protected Long compute() {
if ((end - start) < temp) {
// 如果 结束值 - 开始值 < 临界值,直接使用for循环计算
Long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2; // 取中间值
MyForkJoinTask task1 = new MyForkJoinTask(start, middle);
task1.fork(); // 拆分任务,将任务压入线程队列
MyForkJoinTask task2 = new MyForkJoinTask(middle + 1, end);
task2.fork(); // 拆分任务,将任务压入线程队列
return task1.join() + task2.join(); // 两个任务的结果相加
}
}
}
/**
* 计算1~10_0000_0000相加的值
*/
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1(); // 计算结果:500000000500000000消耗时间:2566
// test2(); // 计算结果:500000000500000000消耗时间:1876
test3(); // 计算结果:500000000500000000消耗时间:206
}
// 普通方法
public static void test1() {
long start = System.currentTimeMillis();
Long sum = 0L;
for (int i = 1; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("计算结果:" + sum + "消耗时间:" + (end - start));
}
// ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
MyForkJoinTask task = new MyForkJoinTask(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task); // 提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("计算结果:" + sum + "消耗时间:" + (end - start));
}
// Stream并行流计算
public static void test3() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(1L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("计算结果:" + sum + "消耗时间:" + (end - start));
}
}
无返回值的异步回调 runAsync()
// 无返回值的异步回调 runAsync()
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " runAsync执行...");
});
System.out.println("11111");
runAsync.get();
}
}
有返回值的异步回调 supplyAsync()
// 有返回值的异步回调 supplyAsync()
public class Demo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
int i = 10 / 0;
System.out.println(Thread.currentThread().getName() + "supplyAsync正在执行...");
return 1024;
});
// 参数1是成功时的响应,失败是为null,参数2是失败时的异常,成功时为null
supplyAsync.whenComplete((i, e) -> {
System.out.println("i = " + i);
System.out.println("e = " + e);
}).exceptionally((e) -> {
// 失败时的处理
System.out.println("e.getMessage() = " + e.getMessage());
return 500;
}).get();
}
}
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操作之前,必须把此变量同步回主内存
谈谈你对 volatile 的理解
volatile是java虚拟机提供轻量级的同步机制
1.保证可见性
问题:线程B修改了值,线程A不能及时可见
解决方法:在主内存的变量中用volatile修饰,保证其可见性
模拟代码:
public class VolatileTest {
private volatile static int num = 0; // 主内存中的变量,加了volatile关键字才能保证其可见性
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
// 线程1, 判断num的值是否为0,如果为零,则一直运行
while (num == 0) {
}
}).start();
TimeUnit.SECONDS.sleep(2);
num = 1; // main线程,将num的值修改为1
System.out.println(num);
}
}
不保证原子性
原子性:不可分割
线程A在执行任务时,不能被其他线程打扰,也不能被分割,要么同时成功,要么同时失败
// volatile不保证原子性
public class VolatileTest2 {
private volatile static int num = 0; // 添加了volatile关键字结果还不是20000,所以volatile不保证原子性
private static void add() {
num++; // 不是原子性操作
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
// 20条线程,每个线程执行1000次add方法,理论结果应该是20000
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
//判断存活线程数是否大于2,main线程 和 GC线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " num: " + num); // 结果不是20000
}
}
那么该如何保证其原子性呢?
加锁可以保证其原子性 synchronized 和 lock锁
使用原子类解决原子性问题
public class VolatileTest2 {
private volatile static AtomicInteger num = new AtomicInteger();
private static void add() {
// num++; // 不是原子性操作
num.getAndIncrement(); // AtomicInteger的+1方法
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
// 20条线程,每个线程执行1000次add方法,理论结果应该是20000
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
//判断存活线程数是否大于2,main线程 和 GC线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " num: " + num); // 结果不是20000
}
}
禁止指令重排
什么是指令重排?
计算机在执行程序的时候,为了提高性能,编译器和处理器会对指令进行重排序!
所以加上volatile关键字之后,禁止指令重排,在多线程环境下,可以保证数据的一致性
饿汉式
在类一加载的时候就创建对象
// 饿汉式
public class Hungary {
public Hungary() {
}
private static final Hungary hungary = new Hungary();
public static Hungary getInstance() {
return hungary;
}
}
懒汉式
类加载的时候不创建对象,被调用的时候才创建对象
// 懒汉式单例
public class Lazy {
public Lazy() {
}
private static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
单线程下懒汉式单例是没有问题的,但是多线程单例下会有问题,如下:
// 懒汉式单例
public class Lazy {
public Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
lazy.getInstance(); // 多线程环境下实例化的对象达不到需要实例的数量
}).start();
}
}
}
DCL懒汉式
// 双重锁模式的懒汉式单例 DCL懒汉式
public class DCLLazy {
public DCLLazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static DCLLazy dclLazy; // 添加volatile关键字可以防止指令重排
public static DCLLazy getInstance() {
if (dclLazy == null) {
synchronized (DCLLazy.class) {
if (dclLazy == null) {
dclLazy = new DCLLazy();
}
}
}
return dclLazy;
}
}
DCL懒汉式单例模式也是不安全的,可以用反射破坏单例,只有枚举实现的单例是安全的
枚举单例
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
cas:(compareAndSet)比较当前内存中的值和主内存中的值,如果这个值是期望的值,那么就执行操作,如果不是期望的值,就一直循环。
缺点:
ABA问题
线程a和线程b操作同一个资源,线程a先对资源进行了cas操作,更改了值,但是又再次更改回原来的值了,这个时候线程b再去操作资源时,这个资源是已经被线程a操作过的,我们需要知道线程a已经对该资源进行了操作,所以引入原子引用
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
new Thread(() -> {
atomicInteger.compareAndSet(1, 2);
System.out.println("a1: " + atomicInteger.get());
atomicInteger.compareAndSet(2, 1);
System.out.println("a2: " + atomicInteger.get());
}, "a").start();
new Thread(() -> {
atomicInteger.compareAndSet(1, 2020);
System.out.println("b: " + atomicInteger.get());
}, "b").start();
}
}
// 这个时候线程b是不知道自己操作的资源是已经被a线程修改过的,但是具体业务中是需要表现出该操作的
原子引用,对应乐观锁的思想
public class CASDemo {
public static void main(String[] args) {
AtomicStampedReference stampedReference = new AtomicStampedReference("a", 1);
new Thread(() -> {
stampedReference.compareAndSet("a", "b" ,
stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println("线程a对资源进行了cas操作,把a修改成了b => " + stampedReference.getReference() + "新的时间戳为:" + stampedReference.getStamp());
stampedReference.compareAndSet("b", "a",
stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println("线程a再次对资源进行了cas操作,把b又修改回a => " + stampedReference.getReference() + "新的时间戳为:" + stampedReference.getStamp());
}, "a").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet("a", "lzp",
stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println("线程b对资源进行了cas操作,把a修改成了lzp => " + stampedReference.getReference() + "新的时间戳为:" + stampedReference.getStamp());
}, "b").start();
}
}
公平锁:线程之间运行时不能插队的,必须先来后到。
非公平锁:线程之间运行时可以插队的,默认都是非公平锁!
synchronized
public class Demo1 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
new Thread(() -> {
phone1.sms();
}, "A").start();
new Thread(() -> {
phone1.sms();
}, "B").start();
}
}
class Phone1{
public synchronized void sms() {
System.out.println(Thread.currentThread().getName() + "=> sms");
call(); // call方法也有一把锁,获取sms的锁时,会自动获取到call的锁
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "=> call");
}
}
Lock
Lock锁必须成对出现
public class Demo2 {
public static void main(String[] args) {
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone2.sms();
}, "A").start();
new Thread(() -> {
phone2.sms();
}, "B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms() {
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();
}
}
}
当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)
自定义自旋锁
// 自旋锁
public class MySpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock() {
Thread thread = Thread.currentThread(); // 获取当前线程
System.out.println(thread.getName() + " => myLock");
while (!atomicReference.compareAndSet(null, thread)) {
// 如果期望值不为null,则拿到锁的线程一直等待,等待之前获取锁的线程释放
}
}
// 解锁
public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " => myUnLock");
atomicReference.compareAndSet(thread, null); // 将期望值thread替换成null,达到解锁目的
}
}
测试
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
MySpinLock mySpinLock = new MySpinLock();
new Thread(() -> {
mySpinLock.myLock(); // 加锁
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mySpinLock.myUnLock(); // 解锁
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
mySpinLock.myLock(); // 加锁
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mySpinLock.myUnLock(); // 解锁
}
}, "B").start();
}
}
测试结果:
A => myLock
B => myLock
A => myUnLock
B => myUnLock
线程A先获取锁,线程B再去获取锁,线程B等待线程A锁被释放后才会释放
线程1锁住了A,线程2锁住了B,但是线程1想获取B锁,线程2想获取A锁,所以造成了死锁
// 死锁
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA, lockB)).start();
new Thread(new MyThread(lockB, lockA)).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 + "想获取" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "lock: " + lockB + "想获取" + lockA);
}
}
}
}
死锁如何排查?