小故事
java代码体现
package com.tang.Thread;
public class Thread16 {
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter--;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter);
}
}
-2242
问题分析
以上的结果可能是正数、负数、零。为什么呢?因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析
例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i
而对应 i-- 也是类似:
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i
而 Java 的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:
如果是单线程以上 8 行代码是顺序执行(不会交错)没有问题:
但多线程下这 8 行代码可能交错运行:
出现负数的情况:
出现正数的情况:
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
例如,下面代码中的临界区
static int counter = 0;
static void increment()
// 临界区
{
counter++;
}
static void decrement()
// 临界区
{
counter--;
}
多个线程在临界区内执行,由于代码的执行序列交错而导致结果无法预测,称之为发生了竞态条件
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
注意
虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:
- 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
synchronized
语法
synchronized(对象) // 线程1, 线程2(blocked)
{
临界区
}
解决
package com.tang.Thread;
public class Thread16 {
static int counter = 0;
static final Object obj1 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (obj1) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (obj1) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter);
}
}
0
思考
synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
为了加深理解,请思考下面的问题:
1.放在外面的话是保证一个线程循环5000次并进行读写操作是原子性的 是一个粗粒度的(可能伴随着一次加到5000,一次减到0)
2.放在里面是保证读写操作是一个原子性的 是一个细粒度的(可能伴随着一次加 一次减)
不是一个锁对象,所以可能还会存在线程安全问题。临界区指令可能还会交错执行
对于没加锁,在锁住t1线程时 t2还会获得cpu时间片 此时t2没加锁依然会执行 最后也会导致 临界区指令还会交错执行
package com.tang.Thread;
public class Thread16 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.getCount());
}
}
class Room{
private int counter = 0;
public void increment(){
synchronized (this) {
counter++;
}
}
public void decrement(){
synchronized (this) {
counter--;
}
}
public int getCount(){
synchronized (this) {
return counter;
}
}
}
class Test{
public synchronized void test() {
}
}
等价于
class Test{
public void test() {
synchronized(this) {
}
}
}
class Test{
public synchronized static void test() {
}
}
等价于
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
锁加在非静态方法上锁是this对象
锁在静态方法上锁是类对象
不加 synchronized 的方法
不加 synchronzied 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的)
其实就是考察 synchronized 锁住的是哪个对象
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n1.b(); }).start();
}
12 或 21
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n1.b(); }).start();
}
1s后12,或 2 1s后 1
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n1.b(); }).start();
new Thread(()->{
n1.c(); }).start();
}
3 1s 12 或 23 1s 1 或 32 1s 1
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n2.b(); }).start();
}
2 1s 后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n1.b(); }).start();
}
2 1s 后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n1.b(); }).start();
}
1s 后12, 或 2 1s后 1
@Slf4j(topic = "c.Number")
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n2.b(); }).start();
}
2 1s 后 1
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{
n1.a(); }).start();
new Thread(()->{
n2.b(); }).start();
}
1s 后12, 或 2 1s后 1
成员变量和静态变量是否线程安全?
局部变量是否线程安全?
局部变量线程安全分析
public static void test1() {
int i = 10;
i++;
}
每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享
public static void test1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: bipush 10
2: istore_0
3: iinc 0, 1
6: return
LineNumberTable:
line 10: 0
line 11: 3
line 12: 6
LocalVariableTable:
Start Length Slot Name Signature
3 4 0 i I
package com.tang.Thread;
import java.util.ArrayList;
public class Thread17 {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadUnsafe test = new ThreadUnsafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
}
class ThreadUnsafe {
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
// { 临界区, 会产生竞态条件
method2();
method3();
// } 临界区
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
}
执行
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at com.tang.Thread.ThreadUnsafe.method3(Thread17.java:32)
at com.tang.Thread.ThreadUnsafe.method1(Thread17.java:24)
at com.tang.Thread.Thread17.lambda$main$0(Thread17.java:12)
at java.lang.Thread.run(Thread.java:748)
分析:
public class Thread17 {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadSafe test = new ThreadSafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
}
class ThreadSafe {
public final void method1(int loopNumber) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
private void method2(ArrayList<String> list) {
list.add("1");
}
private void method3(ArrayList<String> list) {
list.remove(0);
}
}
那么就不会有上述问题了
分析:
方法访问修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题?
public class Thread17 {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadSafeSubClass test = new ThreadSafeSubClass();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
}
class ThreadSafe {
public final void method1(int loopNumber) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
public void method2(ArrayList<String> list) {
list.add("1");
}
public void method3(ArrayList<String> list) {
list.remove(0);
}
}
class ThreadSafeSubClass extends ThreadSafe{
@Override
public void method3(ArrayList<String> list) {
new Thread(() -> {
list.remove(0);
}).start();
}
}
Exception in thread "Thread-399" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at com.tang.Thread.ThreadSafeSubClass.lambda$method3$0(Thread17.java:58)
at java.lang.Thread.run(Thread.java:748)
从这个例子可以看出 private 或 final 提供【安全】的意义所在,请体会开闭原则中的【闭】
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为
Hashtable table = new Hashtable();
new Thread(()->{
table.put("key", "value1");
}).start();
new Thread(()->{
table.put("key", "value2");
}).start();
分析
Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
table.put("key", value);
}
不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的。有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安全的呢?
这是因为修改了值在方法上都会创建一个新对象
public class Immutable{
private int value = 0;
public Immutable(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public Immutable add(int v){
return new Immutable(this.value + v);
}
}
package com.tang.Thread;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
public class Thread18 {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
System.out.println("selled count:"+sellCount.stream().mapToInt(c -> c).sum());
System.out.println("remainder count:"+ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
测试结果
selled count:2001
remainder count:0
发生超卖了
解决方案
在临界区加同步代码块 或者在方法上加synchronized
public synchronized int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
package com.tang.Thread;
import java.sql.SQLOutput;
import java.util.Random;
public class Thread20 {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看转账2000次后的总金额
System.out.println("total:" + (a.getMoney() + b.getMoney()));
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~100
public static int randomAmount() {
return random.nextInt(100) + 1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
结果
total:11511
解决
加类锁 让同一时间段内临界区代码只能给一个线程调用
public void transfer(Account target, int amount) {
synchronized (Account.class){
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
Java 对象头
以 32 位虚拟机为例
Monitor 原理
Monitor 被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针
Monitor 结构如下
注意:
- synchronized 必须是进入同一个对象的 monitor 才有上述的效果
- 不加 synchronized 的对象不会关联监视器,不遵从以上规则
synchronized 原理
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
对应的字节码为
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- lock引用 (synchronized开始)
3: dup
4: astore_1 // lock引用 -> slot 1
5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
6: getstatic #3 // <- i
9: iconst_1 // 准备常数 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- lock引用
15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
16: goto 24
19: astore_2 // e -> slot 2
20: aload_1 // <- lock引用
21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
22: aload_2 // <- slot 2 (e)
23: athrow // throw e
24: return
Exception table:
from to target type
6 16 19 any
19 22 19 any
LineNumberTable:
line 8: 0
line 9: 6
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
注意
方法级别的 synchronized 不会在字节码指令中有所体现
1. 轻量级锁
轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}
2. 锁膨胀
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块
}
}
3. 自旋优化
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
4. 偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
例如:
static final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步块 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步块 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
// 同步块 C
}
}
1) 测试延迟特性
引入依赖
<dependency>
<groupId>org.openjdk.jolgroupId>
<artifactId>jol-coreartifactId>
<version>0.10version>
dependency>
package com.tang.threadtest.study;
import org.openjdk.jol.info.ClassLayout;
public class TestBiased {
public static void main(String[] args) throws InterruptedException {
Dog dog = new Dog();
String s = ClassLayout.parseInstance(dog).toPrintable();
System.out.println(s);
Thread.sleep(4000);
String s1 = ClassLayout.parseInstance(new Dog()).toPrintable();
System.out.println(s1);
}
}
class Dog{
}
2) 测试加jvm参数关闭延迟
// 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws IOException {
Dog d = new Dog();
ClassLayout classLayout = ClassLayout.parseInstance(d);
new Thread(() -> {
log.debug("synchronized 前");
System.out.println(classLayout.toPrintableSimple(true));
synchronized (d) {
log.debug("synchronized 中");
System.out.println(classLayout.toPrintableSimple(true));
}
log.debug("synchronized 后");
System.out.println(classLayout.toPrintableSimple(true));
}, "t1").start();
}
输出
11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
11:08:58.121 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
注意
处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
3)测试禁用
在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁
输出
11:13:10.018 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
11:13:10.021 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000
11:13:10.021 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
4)测试 hashCode
正常状态对象一开始是没有 hashCode 的,第一次调用才生成
撤销 - 调用对象 hashCode
调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被
撤销
11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000
11:22:10.393 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
撤销 - 其它线程使用对象
当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
// 如果不用 wait/notify 使用 join 必须打开下面的注释
// 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的
/*try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}*/
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (TestBiased.class) {
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}, "t2");
t2.start();
输出
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
撤销 - 调用 wait/notify
public static void main(String[] args) throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("notify");
d.notify();
}
}, "t2").start();
}
输出
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}
输出
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
可以看到当撤销偏向锁阈值超过 20 次后 是会在给这些对象加锁时重新偏向至
加锁线程
批量撤销
当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的
static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}
参考资料
https://github.com/farmerjohngit/myblog/issues/12
https://www.cnblogs.com/LemonFive/p/11246086.html
https://www.cnblogs.com/LemonFive/p/11248248.html
简单总结
1、批量重偏向和批量撤销是针对类的优化,和对象无关。
2、偏向锁重偏向一次之后不可再次重偏向。
3、当某个类已经触发批量撤销机制后,JVM会默认当前类产生了严重的问题,剥夺了该类的新实例对象使用偏向锁的权利
锁消除
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() throws Exception {
x++;
}
@Benchmark
public void b() throws Exception {
Object o = new Object();
synchronized (o) {
x++;
}
}
}
java -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.542 0.056 ns/op
c.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op
可以看出二者差不多
这是因为jvm 有个即时编译器 发现这个锁是不被共享的 所以底层进行了锁消除
禁止锁消除优化
java -XX:-EliminateLocks -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.507 0.108 ns/op
c.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op
可以看出禁止锁消除之后二者相差不多
小故事
由于条件不满足,小南不能继续进行计算
但小南如果一直占用着锁,其它人就得一直阻塞,效率太低
于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,但这时锁释放开,其它人可以由老王随机安排进屋
直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法
小南于是可以离开休息室,重新进入竞争锁的队列
wait notify 原理
API 介绍
注意:
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁(即在同步代码块中,或者在同步方法中),才能调用这几个方法
案例一:
package com.tang.threadtest.study;
import java.sql.Statement;
public class Study02 {
public static final Object lock = new Object();
public static void main(String[] args) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.tang.threadtest.study.Study02.main(Study02.java:9)
Process finished with exit code 1
package com.tang.threadtest.study;
public class Study03 {
public static final Object lock = new Object();
public static void main(String[] args) {
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
此时不报错
案例二:
package com.tang.threadtest.study;
public class Study04 {
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("线程t1进来了");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"其他代码");
}
},"t1").start();
new Thread(()->{
synchronized (lock){
System.out.println("线程t2进来了");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"其他代码");
}
},"t2").start();
Thread.sleep(2000);
System.out.println("唤醒处在waitSet中的线程");
synchronized (lock){
// lock.notify();
lock.notifyAll();
}
}
}
输出
线程t1进来了
线程t2进来了
唤醒处在waitSet中的线程
t2:其他代码
t1:其他代码
案例三
package com.tang.threadtest.study;
public class Study05 {
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("线程t1进来了");
try {
lock.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"其他代码");
}
},"t1").start();
}
}
输出
线程t1进来了
t1:其他代码
可以看出即使在1秒内没有被notify或者notifyAll也会让线程处于就绪状态 抢到cpu时间片后继续执行wait方法后的代码
package com.tang.threadtest.study;
public class Study06 {
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println(Thread.currentThread().getName()+":"+"获得锁");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
Thread.sleep(1000);
synchronized (lock){
System.out.println(Thread.currentThread().getName()+":"+"获得锁");
}
}
}
十秒之后才会主线程才会打印获得锁 sleep方法不会释放锁 必须等睡眠时间到了 方法执行完了才会释放锁
package com.tang.threadtest.study;
public class Study06 {
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println(Thread.currentThread().getName()+":"+"获得锁");
try {
lock.wait(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
Thread.sleep(1000);
synchronized (lock){
System.out.println(Thread.currentThread().getName()+":"+"获得锁");
}
}
}
一秒之后主线程就会打印获得锁
step 1
看下面一段代码
package com.tang.threadtest.study;
public class Study07 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
System.out.println("有烟没?"+":"+hasCigarette);
if (!hasCigarette) {
System.out.println("没烟,先歇会!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("有烟没?"+":"+hasCigarette);
if (hasCigarette) {
System.out.println("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
System.out.println("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(1000);
new Thread(() -> {
// 这里能不能加 synchronized (room)?
hasCigarette = true;
System.out.println("烟到了噢");
}, "送烟的").start();
}
}
输出
有烟没?:false
没烟,先歇会!
烟到了噢
有烟没?:true
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
思考这种方案好不好?
step 2
思考下面的实现行吗,为什么?
package com.tang.threadtest.study;
public class Study08 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
System.out.println("有烟没?" + ":" + hasCigarette);
if (!hasCigarette) {
System.out.println("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("有烟没?" + ":" + hasCigarette);
if (hasCigarette) {
System.out.println("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
System.out.println("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
System.out.println("烟到了噢");
room.notify();
}
}, "送烟的").start();
}
}
输出
有烟没?:false
没烟,先歇会!
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
烟到了噢
有烟没?:true
可以开始干活了
step 3
package com.tang.threadtest.study;
public class Study09 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
System.out.println("有烟没?" + ":" + hasCigarette);
if (!hasCigarette) {
System.out.println("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("有烟没?" + ":" + hasCigarette);
if (hasCigarette) {
System.out.println("可以开始干活了");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
System.out.println("外卖送到没:"+hasTakeout);
if (!hasTakeout) {
System.out.println("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("外卖送到没:"+hasTakeout);
if (hasTakeout) {
System.out.println("可以开始干活了");
} else {
System.out.println("没干成活...");
}
}
}, "小女").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
System.out.println("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(2000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
System.out.println("外卖到了噢!");
room.notify();
}
}, "送外卖的").start();
}
}
输出
有烟没?:false
没烟,先歇会!
外卖送到没:false
没外卖,先歇会!
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
外卖到了噢!
有烟没?:false
step 4
package com.tang.threadtest.study;
public class Study09 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
System.out.println("有烟没?" + ":" + hasCigarette);
if (!hasCigarette) {
System.out.println("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("有烟没?" + ":" + hasCigarette);
if (hasCigarette) {
System.out.println("可以开始干活了");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
System.out.println("外卖送到没:"+hasTakeout);
if (!hasTakeout) {
System.out.println("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("外卖送到没:"+hasTakeout);
if (hasTakeout) {
System.out.println("可以开始干活了");
} else {
System.out.println("没干成活...");
}
}
}, "小女").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
System.out.println("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(2000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
System.out.println("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
输出
有烟没?:false
没烟,先歇会!
外卖送到没:false
没外卖,先歇会!
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
外卖到了噢!
外卖送到没:true
可以开始干活了
有烟没?:false
step 5
package com.tang.threadtest.study;
public class Study10 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
System.out.println("有烟没?" + ":" + hasCigarette);
while (!hasCigarette) {
System.out.println("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("有烟没?" + ":" + hasCigarette);
if (hasCigarette) {
System.out.println("可以开始干活了");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
System.out.println("外卖送到没:"+hasTakeout);
while (!hasTakeout) {
System.out.println("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("外卖送到没:"+hasTakeout);
if (hasTakeout) {
System.out.println("可以开始干活了");
} else {
System.out.println("没干成活...");
}
}
}, "小女").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
System.out.println("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(2000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
System.out.println("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
输出
有烟没?:false
没烟,先歇会!
外卖送到没:false
没外卖,先歇会!
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
可以开始干活了
外卖到了噢!
外卖送到没:true
可以开始干活了
没烟,先歇会!
至此案例改造完成
正确姿势总结
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}
即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
要点
GuardedObject
class GuardedObject{
//结果
private Object response;
//获取结果
public Object get(long timeout){
synchronized (this){
// 开始时间 15:00:00
long begin = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
while (response == null){
// 这一轮循环应该等待的时间
long waitTime = timeout - passedTime;
// 经历的时间超过了最大等待时间时,退出循环
if (timeout - passedTime <= 0) {
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
}
return response;
}
}
//产生结果
public void complete(Object response){
synchronized (this){
//给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
DownLoader
public class DownLoader {
public static List<String> download() throws InterruptedException {
Thread.sleep(3000);
return Arrays.asList("111","222","333");
}
}
Test1
public class Test1 {
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(()->{
//等待结果
System.out.println(Thread.currentThread().getName()+"等待结果");
List<String> list = (List<String>) guardedObject.get(2000);
System.out.println(Thread.currentThread().getName()+"结果"+list);
},"t1").start();
//产生结果
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行下载");
List<String> download = null;
try {
download = DownLoader.download();
guardedObject.complete(download);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
}
}
上述案例模拟了 两个线程之间的同步 即线程t1 必须要等待t2 并且设定了等待最长时间
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
// isAlive()判断线程是否存货
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可以看出join方法源码利用了保护性暂停模式
图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右侧的 t1,t3,t5 就好比邮递员。如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。
package com.tang.thread;/*
* @author Dram
* @create 2021-04-25 9:07
*/
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Test1 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new People().start();
}
Thread.sleep(1000);
for (Integer id : Mailboxes.getIds()) {
new Postman(id, "内容" + id).start();
}
}
}
class People extends Thread{
@Override
public void run() {
// 收信
GuardedObject guardedObject = Mailboxes.createGuardedObject();
System.out.println(Thread.currentThread().getName()+":"+"开始收信 id:"+guardedObject.getId());
Object mail = guardedObject.get(5000);
System.out.println(Thread.currentThread().getName()+":"+"收到信 id:"+guardedObject.getId()+",内容:"+mail);
}
}
class Postman extends Thread {
private int id;
private String mail;
public Postman(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
System.out.println(Thread.currentThread().getName()+":"+"送信 id:"+id+", 内容:"+mail);
guardedObject.complete(mail);
}
}
class Mailboxes {
private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
private static int id = 1;
// 产生唯一 id
private static synchronized int generateId() {
return id++;
}
public static GuardedObject getGuardedObject(int id) {
return boxes.remove(id);
}
public static GuardedObject createGuardedObject() {
GuardedObject go = new GuardedObject(generateId());
boxes.put(go.getId(), go);
return go;
}
public static Set<Integer> getIds() {
return boxes.keySet();
}
}
class GuardedObject {
// 标识 Guarded Object
private int id;
public GuardedObject(int id) {
this.id = id;
}
public int getId() {
return id;
}
//结果
private Object response;
//获取结果
public Object get(long timeout){
synchronized (this){
// 开始时间 15:00:00
long begin = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
while (response == null){
// 这一轮循环应该等待的时间
long waitTime = timeout - passedTime;
// 经历的时间超过了最大等待时间时,退出循环
if (timeout - passedTime <= 0) {
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
}
return response;
}
}
//产生结果
public void complete(Object response){
synchronized (this){
//给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
运行结果
Thread-1:开始收信 id:2
Thread-2:开始收信 id:3
Thread-0:开始收信 id:1
Thread-3:送信 id:3, 内容:内容3
Thread-5:送信 id:1, 内容:内容1
Thread-4:送信 id:2, 内容:内容2
Thread-2:收到信 id:3,内容:内容3
Thread-1:收到信 id:2,内容:内容2
Thread-0:收到信 id:1,内容:内容1
要点
package com.tang.thread;/*
* @author Dram
* @create 2021-04-25 15:32
*/
import java.util.LinkedList;
public class Test2 {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
int id = i;
new Thread(() -> {
queue.put(new Message(id , "值"+id));
}, "生产者" + i).start();
}
new Thread(() -> {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = queue.take();
}
}, "消费者").start();
}
}
// 消息队列类 , java 线程之间通信
class MessageQueue {
// 消息的队列集合
private LinkedList<Message> list = new LinkedList<>();
// 队列容量
private int capcity;
public MessageQueue(int capcity) {
this.capcity = capcity;
}
// 获取消息
public Message take() {
// 检查队列是否为空
synchronized (list) {
while(list.isEmpty()) {
try {
System.out.println(Thread.currentThread().getName()+":"+"队列为空, 消费者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 从队列头部获取消息并返回
Message message = list.removeFirst();
System.out.println(Thread.currentThread().getName()+":"+"已消费消息:"+message);
list.notifyAll();
return message;
}
}
// 存入消息
public void put(Message message) {
synchronized (list) {
// 检查对象是否已满
while(list.size() == capcity) {
try {
System.out.println(Thread.currentThread().getName()+":"+"队列已满, 生产者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 将消息加入队列尾部
list.addLast(message);
System.out.println(Thread.currentThread().getName()+":"+"已生产消息:"+message);
list.notifyAll();
}
}
}
final class Message {
private int id;
private Object value;
public Message(int id, Object value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value=" + value +
'}';
}
}
生产者0:已生产消息:Message{
id=0, value=值0}
生产者1:已生产消息:Message{
id=1, value=值1}
生产者2:队列已满, 生产者线程等待
消费者:已消费消息:Message{
id=0, value=值0}
生产者2:已生产消息:Message{
id=2, value=值2}
消费者:已消费消息:Message{
id=1, value=值1}
消费者:已消费消息:Message{
id=2, value=值2}
消费者:队列为空, 消费者线程等待
它们是 LockSupport 类中的方法
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
先 park 再 unpark
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(1);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
},"t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);
输出
18:42:52.585 c.TestParkUnpark [t1] - start...
18:42:53.589 c.TestParkUnpark [t1] - park...
18:42:54.583 c.TestParkUnpark [main] - unpark...
18:42:54.583 c.TestParkUnpark [t1] - resume...
先 unpark 再 park
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(2);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);
输出
18:43:50.765 c.TestParkUnpark [t1] - start...
18:43:51.764 c.TestParkUnpark [main] - unpark...
18:43:52.769 c.TestParkUnpark [t1] - park...
18:43:52.769 c.TestParkUnpark [t1] - resume...
与 Object 的 wait & notify 相比
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻
1. 当前线程调用 Unsafe.park() 方法
2. 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁
3. 线程进入 _cond 条件变量阻塞
4. 设置 _counter = 0
1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
2. 唤醒 _cond 条件变量中的 Thread_0
3. Thread_0 恢复运行
4. 设置 _counter 为 0
1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
2. 当前线程调用 Unsafe.park() 方法
3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
4. 设置 _counter 为 0
情况 1 NEW --> RUNNABLE
当调用 t.start() 方法时,由 NEW --> RUNNABLE
情况 2 RUNNABLE <–> WAITING
t 线程用 synchronized(obj) 获取了对象锁后
package com.tang.thread;/*
* @author Dram
* @create 2021-04-26 10:22
*/
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (obj) {
System.out.println("执行....");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其它代码....");// 断点
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
System.out.println("执行....");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其它代码....");// 断点
}
},"t2").start();
Thread.sleep(500);
System.out.println("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notifyAll(); // 唤醒obj上所有等待线程 断点
}
}
}
情况 3 RUNNABLE <–> WAITING
情况 4 RUNNABLE <–> WAITING
情况 5 RUNNABLE <–> TIMED_WAITING
t 线程用 synchronized(obj) 获取了对象锁后
情况 6 RUNNABLE <–> TIMED_WAITING
情况 7 RUNNABLE <–> TIMED_WAITING
情况 8 RUNNABLE <–> TIMED_WAITING
情况 9 RUNNABLE <–> BLOCKED
情况 10 RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁)
代码
package com.tang.thread;/*
* @author Dram
* @create 2021-04-26 14:28
*/
import java.text.SimpleDateFormat;
import java.util.Date;
public class BigRoom {
public static final Object lock1 = new Object();
public static final Object lock2 = new Object();
//吃饭
public void eat() throws InterruptedException {
synchronized (lock1){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date());
System.out.println(date+":"+"吃饭...");
Thread.sleep(5000);
}
}
//睡觉
public void sleep() throws InterruptedException {
synchronized (lock2){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date());
System.out.println(date+":"+"睡觉...");
Thread.sleep(10000);
}
}
}
package com.tang.thread;/*
* @author Dram
* @create 2021-04-26 14:33
*/
public class Test3 {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(()->{
try {
bigRoom.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
bigRoom.eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
控制台
2021-04-26 14:37:48:睡觉...
2021-04-26 14:37:48:吃饭...
将锁的粒度细分
好处,是可以增强并发度
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁。 t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁 例:
package com.tang.thread;/*
* @author Dram
* @create 2021-04-26 14:56
*/
public class DeadLock {
public static final Object lock1 = new Object();
public static final Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock1){
System.out.println("开始1");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("打不出来的...");
}
}
}).start();
new Thread(()->{
synchronized (lock2){
System.out.println("开始2");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("打不出来的...");
}
}
}).start();
}
}
控制台
开始1
开始2
定位死锁
检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如
package com.tang.thread;/*
* @author Dram
* @create 2021-04-26 15:37
*/
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("count:"+count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println("count:"+count);
}
}, "t2").start();
}
}
很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题