1.java的内存模型
一,对象头
-
Mark Word
-
指向类的指针
-
数组长度(只有数组才有)
二,实例数据
三,对齐填充字节
2.Mark Word
Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。
Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的:
锁状态 | 25bit | 4bit | 1bit(偏向锁标记位) | 2bit( 锁标志位) |
---|---|---|---|---|
无锁 | hashcode | 分代年龄 | 0 | 01 |
偏向锁 | 线程ID和 Epoch | 分代年龄 | 1 | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 | ||
重量级锁 | 指向重量级锁的指针 | 10 | ||
GC标记 | 空 | 11 |
JVM一般是这样使用锁和Mark Word的:
-
当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。
-
当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是偏向锁标记位那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。
-
当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,偏向锁标记位是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。
-
当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。
-
偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。
-
轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。
-
自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。
3. wait()和notify()/notifyAll()
这3个方法都是Object对象的方法,只有当一个对象在某一线程中作为锁对象时,才能调用这3个方法,
4. park()/unpark()
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
LockSupport.park();
System.out.println(Thread.currentThread().getName());
}
};
Thread t1 = new Thread(runnable,"t1");
Thread t2 = new Thread(runnable, "t2");
t1.start();
t2.start();
try {
Thread.sleep(2000);
System.out.println("unpark");
t2.interrupt();
LockSupport.unpark(t1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- LockSupport.park() 暂停当前线程
- LockSupport.unpark(Thread thread); 恢复当前线程,可以在park之前使用
- Thread 对象的interrupt()方法也可以使park暂停的线程恢复
5 死锁和活锁
- 死锁 : 2个线程互相持有对方的锁,导致都不能继续向下运行,进入阻塞
- 活锁: 2个线程互相改变了对方的结束条件,导致2个线程一直运行,结束不了
6.ReentrantLock
具备以下特点:
- 线程在等待锁的过程中可以被中断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
lock.lockInterruptibly();
} catch (InterruptedException e) {
//在等待锁的阻塞过程中被打断
System.out.println("t1 interrupt");
e.printStackTrace();
return;
}
try {
} finally {
lock.unlock();
}
}, "t1");
t1.start();
try {
lock.lockInterruptibly();
System.out.println("t2 get lock");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("start interrupt");
t1.interrupt();
lock.unlock();
}
- 可设置超时时间
- 可设置为公平锁
- 支持多个条件变量
ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
new Thread(() -> {
try {
lock.lock();
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("condition1 notify t0");
lock.unlock();
}
}, "t0").start();
new Thread(() -> {
try {
lock.lock();
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("condition1 notify t1");
lock.unlock();
}
}, "t1").start();
try {
Thread.sleep(1000);
lock.lock();
System.out.println("condition1 signal t2");
condition1.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
另外一些方法
boolean tryLock()
/boolean tryLock(long, TimeUnit)
仅在调用时锁未被另一个线程保持的情况下才获取锁。
ReentrantReadWriteLock
- 多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。
- 支持锁降级
private static void test2 () {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
new Thread(() -> {
writeLock.lock();
System.out.println("t1 writeLock");
Thread.sleep(1000);
readLock.lock();
System.out.println("t1 readLock");
System.out.println("t1 unlock");
writeLock.unlock();
Thread.sleep(1000);
System.out.println("t1 unlock");
readLock.unlock();
}, "t1").start();
new Thread(() -> {
readLock.lock();
System.out.println("t2 readLock");
System.out.println("t2 unlock");
readLock.unlock();
}, "t2").start();
}
StampedLock
- StampedLock控制锁有三种模式(写,读,乐观读)
- 在乐观读的时候如果发生了写,则应当重读而不是在读的时候直接阻塞写
static int a = 1;
private static void test3 () {
StampedLock lock = new StampedLock();
new Thread(()->{
long stamp = lock.tryOptimisticRead();
System.out.println("Optimistic read a:" + a);
Thread.sleep(1000);
if ( ! lock.validate(stamp)) {
stamp = lock.readLock();
System.out.println(" read a:" + a);
lock.unlockRead(stamp);
}
}).start();
new Thread(()->{
long stamp = lock.writeLock();
Thread.sleep(500);
a ++;
lock.unlockWrite(stamp);
}).start();
}
CountDownLatch
- countDown() 将计数减一
- await() 阻塞,等待计数为0时向下运行
public static void test4() {
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("start");
Thread.sleep(900);
countDownLatch.countDown();
}).start();
try {
boolean await = countDownLatch.await(1, TimeUnit.SECONDS);
System.out.println(await);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(() -> {
try {
cyclicBarrier.await();
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
Thread.sleep(1000);
cyclicBarrier.await();
System.out.println(11);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();