public static void shortSleep(Duration duration) {
try {
TimeUnit.MILLISECONDS.sleep(duration.toMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized
问题一:synchronized
锁的是什么?
对象
在另一篇博文 线程&Synchronized 中有介绍,synchronized
是通过改变对象头中 Mark Word
中的锁标志位来实现加锁。
synchronized 锁 -> 永远都是锁对象(Markword)
// 锁住的是对象 o
Object o = new Object();
synchronized(o);
// 锁住的是调用这个代码块的对象
synchronized(this);
// 类锁 1
public static synchronized void method() {
// 此处锁的是 类 -> T.class 对象
// 对整个类(static 方法上锁)该类的所有实例在调用这个方法时都要 获取锁
}
// 类锁 2 -> 这个例子更直观地展示了锁的是该类的 .class 类对象
public void method() {
synchronized(T.class) {
}
}
问题二:synchronized
是否可重入?
是
synchronized
如果不可重入,那么根本无法实现父子类锁 -> synchronized(this)
在父类和子类中是同一把锁。
public class Father {
public void doSth() {
System.out.println("father wait lock");
synchronized (this) {
System.out.println("father get lock");
shortSleep(Duration.ofMillis(500));
System.out.println("father release lock");
}
}
}
public class Son extends Father {
public void doOther() {
System.out.println("son wait lock");
synchronized (this) {
System.out.println("son get lock");
shortSleep(Duration.ofMillis(800));
System.out.println("son release lock");
}
}
public static void main(String[] args) {
Son s = new Son();
// 在同一个实例 s 中分别调用本类和父类的 synchronized(this) 方法,他们是申请同一把锁
new Thread(s::doOther).start();
new Thread(s::doSth).start();
}
}
synchronized
可重入验证
public class T01_ReentrantLock {
private synchronized void m1() {
for (int i = 0; i < 10; i++) {
shortSleep(Duration.ofSeconds(1));
System.out.println(i);
if (i == 2) {
/*
此处虽然也是使用的同一把锁,但是是可重入的
这里已经持有该对象锁的 m1() 方法再次调用需要获取锁的 m2() 方法可以成功
*/
m2();
}
}
}
private synchronized void m2() {
System.out.println("m2 ...");
}
public static void main(String[] args){
T01_ReentrantLock rl = new T01_ReentrantLock();
new Thread(rl::m1).start();
shortSleep(Duration.ofSeconds(1));
// m2 虽然已经启动,但是因获取不到锁而等待
new Thread(rl::m2).start();
}
}
ReentrantLock
ReentrantLock
基础
public class T02_ReentrantLock {
// ReentrantLock 是 Java 1.5 中实现的可重入锁
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
shortSleep(Duration.ofSeconds(1));
System.out.println(i);
if (i == 2) {
/*
此处虽然也是使用的同一把锁,但是是可重入的
这里已经持有该对象锁的 m1() 方法再次调用需要获取锁的 m2() 方法可以成功
*/
m2();
}
}
} finally {
// 使用锁实例,最终都要释放锁
lock.unlock();
}
}
void m2() {
try {
lock.lock();
System.out.println("m2 ...");
} finally {
lock.unlock();
}
}
public static void main(String[] args){
T02_ReentrantLock rl = new T02_ReentrantLock();
new Thread(rl::m1).start();
shortSleep(Duration.ofSeconds(1));
// m2 虽然已经启动,但是因获取不到锁而等待
new Thread(rl::m2).start();
}
}
ReentrantLock
tryLock
可设置锁的等待超时时间,但是只是尝试获取锁,到达等待时间后会返回等待的结果,但是不会报错。
public class T03_ReentrantLock {
Lock lock = new ReentrantLock();
private void m1() {
try {
lock.lock();
// 3次能拿到锁,10次拿不到
for (int i = 0; i < 3; i++) {
sleep(Duration.ofSeconds(1));
System.out.println(i);
}
} finally {
lock.unlock();
}
}
private void m2() {
boolean locked = false;
try {
locked = lock.tryLock(5, TimeUnit.SECONDS);
System.out.println("m2 ... " + locked);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) {
lock.unlock();
}
}
}
public static void main(String[] args) {
T03_ReentrantLock rl = new T03_ReentrantLock();
new Thread(rl::m1).start();
shortSleep(Duration.ofSeconds(1));
// m2 虽然已经启动,但是因获取不到锁而等待
new Thread(rl::m2).start();
}
}
ReentrantLock
lock.lockInterruptibly()
获取锁,允许通过线程的 interrupt
打断该等待。
public class T04_ReentrantLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
System.out.println("t1 start");
lock.lock();
System.out.println("t1 locked");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
System.out.println("t1 interrupted");
} finally {
lock.unlock();
}
});
t1.start();
Thread t2 = new Thread(() -> {
boolean locked = false;
try {
System.out.println("t2 start");
lock.lock(); // 不可被打断
// lock.lockInterruptibly();
locked = true;
System.out.println("t2 locked");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end");
} catch (InterruptedException e) {
System.out.println("t2 interrupted");
} finally {
if (locked) {
lock.unlock();
}
}
});
t2.start();
sleep(Duration.ofSeconds(2));
// 打断线程 2 的等待
t2.interrupt();
// 结束线程
sleep(Duration.ofSeconds(5));
t1.interrupt();
}
}
ReentrantLock
实现公平锁
公平锁 : 所有尝试获取锁的线程,在尝试获取锁时进入等待队列,按照队列中的顺序依次获取锁。如果是非公平锁,没有等待队列,所有尝试获取锁的线程会阻塞,并根据 CPU 的时间片调度来分配锁 -> 谁先抢到就是谁的,和谁先来的没有关系。
public class T05_ReentrantLock extends Thread {
/**
* 公平锁 - 可以看到线程1和线程2 在一定程度上 交替运行
*/
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try {
// shortSleep(Duration.ofMillis(10));
System.out.println(Thread.currentThread().getName() + " 获得锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
T05_ReentrantLock tl5 = new T05_ReentrantLock();
Thread th1 = new Thread(tl5);
Thread th2 = new Thread(tl5);
th1.start();
th2.start();
}
}
之所以是在一定程度上交替运行,是因为进入等待队列的时间不一定是交替的,有可能线程1释放锁后很快又再次进入队列等锁。如果在执行时加上一点延时,便可以看到严格的依次执行。
CountDownLatch
一种锁同步工具,可以控制线程的同步,效率和 join
差不多
public class T06_CountDownLatch {
public static void main(String[] args) {
long start = System.currentTimeMillis();
System.out.println("start: " + start);
usingJoin();
System.out.println("join cost: " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
usingCountDownLatch();
System.out.println("count cost: " + (System.currentTimeMillis() - start));
}
private static void usingCountDownLatch() {
Thread[] threads = new Thread[100];
CountDownLatch latch = new CountDownLatch(threads.length);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int result = 0;
for (int j = 0; j < 10000; j++) {
result += j;
}
shortSleep(Duration.ofSeconds(3));
latch.countDown();
});
}
for (Thread thread : threads) {
thread.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end latch");
}
private static void usingJoin() {
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int result = 0;
for (int j = 0; j < 10000; j++) {
result += j;
}
shortSleep(Duration.ofSeconds(3));
});
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
// 在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end join");
}
}
CyclicBarrier
一种 可循环
的 栅栏
,当调用 barrier.await()
的线程数到达一定数量时,就会触发给定的方法。触发后重新计数。
public class T07_CyclicBarrier {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
@Override
public void run() {
System.out.println("满人, 发车!");
}
});
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
shortSleep(Duration.ofMillis(100));
new Thread(() -> {
try {
barrier.await();
// System.out.println(Thread.currentThread().getName() + " 发车");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}
Phaser
phaser
可以理解成一种 多步骤 的栅栏,预先定义参加该 活动 的线程数,每个线程执行完一个阶段后,会决定是执行下一个阶段还是退出。phaser
可以控制 参与本阶段的线程都执行完毕后 再一起执行下一个阶段
public class T07_Phaser {
private static Random r = new Random();
private static MarriagePhaser phaser = new MarriagePhaser();
public static void main(String[] args) {
phaser.bulkRegister(7);
for (int i = 0; i < 5; i++) {
new Thread(new Person("p" + i)).start();
}
new Thread(new Person("新郎")).start();
new Thread(new Person("新娘")).start();
}
static class MarriagePhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("所有人到齐了! " + registeredParties);
return false;
case 1:
System.out.println("所有人吃完了! " + registeredParties);
return false;
case 2:
System.out.println("所有人离开了! " + registeredParties);
return false;
case 3:
System.out.println("婚礼结束了! 新郎新娘入洞房" + registeredParties);
return true;
default:
return true;
}
}
}
@AllArgsConstructor
static class Person implements Runnable {
String name;
public void arrive() {
shortSleep(Duration.ofMillis(r.nextInt(1000)));
System.out.printf("%s 到达现场!\n", name);
phaser.arriveAndAwaitAdvance();
}
public void eat() {
shortSleep(Duration.ofMillis(r.nextInt(1000)));
System.out.printf("%s 吃完!\n", name);
phaser.arriveAndAwaitAdvance();
}
public void leave() {
shortSleep(Duration.ofMillis(r.nextInt(1000)));
System.out.printf("%s 离开!\n", name);
phaser.arriveAndAwaitAdvance();
}
private void hug() {
if (name.equals("新郎") || name.equals("新娘")) {
shortSleep(Duration.ofMillis(r.nextInt(1000)));
System.out.printf("%s 洞房!\n", name);
phaser.arriveAndAwaitAdvance();
} else {
phaser.arriveAndDeregister();
}
}
@Override
public void run() {
arrive();
eat();
leave();
hug();
System.out.println(name + " 结束");
}
}
}
ReadWriteLock
读写锁
读锁 -> 共享锁
写锁 -> 独占锁
问题一:读为什么也要加锁?
读如果不加锁的话,会造成脏读(读到别人修改过的数据)
同一把锁(读写锁),分为共享锁和独占锁两个部分。共享锁是可以共享的一种锁,当前如果共享锁被某个线程持有,其他多个尝试获取共享锁的线程都可以得到共享锁并执行。此时如果有线程想要获取独占锁,需要等全部持有共享锁的线程都释放掉。获取写锁的线程仍然可以继续获取读锁。
读锁的获取
- 存在写锁,并且写锁不是当前线程则直接去排队
- 读锁是否该阻塞,对于非公平模式下写锁获取优先级会高,如果存在要获取写锁的线程则读锁需要让步,公平模式下则先来先到
- CAS修改读锁状态,实际上是读锁状态+1
- 锁获取成功后,需要记录线程状态
- 从
ThreadLocal
中获取当前线程重入读锁的次数,然后自增 - 进入死循环获取读锁模式
读锁的释放
- 清理ThreadLocal中保存的获取锁数量信息
- CAS修改读锁个数,实际上是自减一
写锁的获取
- 如果读锁数量不为0或者写锁数量不为0,并且不是重入操作,则获取失败
- 如果当前锁的数量为0,也就是不存在1的情况,那么该线程是有资格获取到写锁,因此修改状态,设置独占线程为当前线程
写锁的释放
- 如果当前线程没有获取写锁却释放,则直接抛异常
- 因为写锁是可以重入,所以在都释放完毕后要把独占标识清空
写锁降级策略
- 一个线程获取写锁之后再获取读锁,然后读锁释放掉写锁的过程
公平与非公平
- 公平下的Sync实现策略是所有获取的读锁或者写锁的线程都需要入队排队,按照顺序依次去尝试获取锁。
- 非公平下由于抢占式获取锁,写锁是可能产生饥饿,因此解决办法就是提高写锁的优先级,换句话说获取写锁之前先占坑。
public class T09_ReadWriteLock {
private static Lock lock = new ReentrantLock();
private static int value;
private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(false);
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
public static void read(Lock lock, CountDownLatch c) {
try {
lock.lock();
shortSleep(Duration.ofSeconds(1));
System.out.println(Thread.currentThread().getName() + " read over: " + value);
// 模拟读取操作
} finally {
c.countDown();
lock.unlock();
}
}
public static void write(Lock lock, int v, CountDownLatch c) {
try {
lock.lock();
shortSleep(Duration.ofSeconds(2));
value = v;
System.out.println(Thread.currentThread().getName() + " write over!");
// 模拟写操作
} finally {
c.countDown();
lock.unlock();
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
CountDownLatch c = new CountDownLatch(30);
// Runnable readR = () -> read(lock, c);
Runnable readR = () -> read(readLock, c);
// Runnable writeR = () -> write(lock, new Random().nextInt(), c);
Runnable writeR = () -> write(writeLock, new Random().nextInt(), c);
for (int i = 0; i < 18; i++) {
new Thread(readR, "Read Thread " + i).start();
}
new Thread(writeR, "Write Thread 1").start();
for (int i = 0; i < 10; i++) {
new Thread(readR, "Read Thread " + (i + 18)).start();
}
new Thread(writeR, "Write Thread 2").start();
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("value: " + value);
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}
Semaphore
信号量
决定同时可允许多少个线程运行
public class T10_Semaphore {
public static void main(String[] args) {
Semaphore s = new Semaphore(2);
// 允许 2 个线程同时执行
new Thread(() -> {
try {
// 得不到就阻塞等待
s.acquire();
System.out.println("T1 running...");
shortSleep(Duration.ofMillis(200));
System.out.println("T1 running...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
new Thread(() -> {
try {
s.acquire();
System.out.println("T2 running...");
shortSleep(Duration.ofMillis(300));
System.out.println("T2 running...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
new Thread(() -> {
try {
s.acquire();
System.out.println("T3 running...");
shortSleep(Duration.ofMillis(200));
System.out.println("T3 running...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
}
}
Exchanger
两个线程之间进行数据交换的一种方式
public class T11_Exchanger {
/**
* 线程之间交换数据的一种方式
* 第一个调用 exchange 的线程会阻塞,等待下一个来和它交换的线程调用 exchange
*/
private static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(() -> {
String s = "T1";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t1").start();
new Thread(() -> {
String s = "T2";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t2").start();
}
}
LongAdder
提供的一种基于分段锁的数据递增方式
public class T12_LongAdder {
static AtomicLong num1 = new AtomicLong(0);
static LongAdder num2 = new LongAdder();
static Long num3 = 0L;
final static Object o = new Object();
private static final int THREAD_NUM = 1000;
private static final int ADD_NUM = 100000;
private static void updateByLock() throws InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch count = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(() -> {
for (int j = 0; j < ADD_NUM; j++) {
synchronized (o) {
num3++;
}
}
count.countDown();
}).start();
}
count.await();
System.out.println("Lock cost: " + (System.currentTimeMillis() - start) + " num3: " + num3);
}
private static void updateByAtomic() throws InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch count = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(() -> {
for (int j = 0; j < ADD_NUM; j++) {
num1.incrementAndGet();
}
count.countDown();
}).start();
}
count.await();
System.out.println("AtomicLong cost: " + (System.currentTimeMillis() - start) + " num1: " + num1.get());
}
public static void updateByLongAdder() throws InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch count = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(() -> {
for (int j = 0; j < ADD_NUM; j++) {
num2.increment();
}
count.countDown();
}).start();
}
count.await();
System.out.println("LongAdder cost: " + (System.currentTimeMillis() - start) + " num2: " + num2.longValue());
}
public static void main(String[] args) throws InterruptedException {
updateByLock();
updateByAtomic();
updateByLongAdder();
}
}
分段锁
所谓分段锁 -> 维护一个锁的数组,将1000个线程分为多个组,分别获取不同的锁。最后将所有部分的计算结果加和
ConcurrentHashMap
就是使用的分段锁