public class Process{
public static void main(String[] args){
method1();
}
private static void method1(){
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2(){
Object n = new Object();
return n;
}
}
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 1;i < 5000; i++){
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 1;i < 5000; i++){
count--;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("count的值是{}",count);
}
1.count++操作的字节码
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i
2.count–操作的字节码
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i
3.单线程情况不会出现问题
4.多线程情况下问题的产生(以负数为例)
5.总结
synchronized{
//临界区
}
public class Test {
// 在方法上加上synchronized关键字
public synchronized void test() {
}
// 等价于
public void test() {
synchronized(this) { // 锁住的是对象
}
}
}
public class Test {
// 在静态方法上加上 synchronized 关键字
public synchronized static void test() {
}
//等价于
public void test() {
synchronized(Test.class) { // 锁住的是类,是java.lang.Class类的对象
}
}
}
synchronized实际上使用对象锁保证了临界区内代码的原子性,临界区内的代码是不可分割的,不会被线程切换所打断。
1.线程安全类
2.解释
//组合后线程不安全
Hashtable table = new Hashtable();
if(table.get(key) == null){
table.put(key, value);
}
1.线程安全类
2.解释
1.普通对象
2.数组对象
3.Mark Word结构
1.基本概念
2.结构解析
注意:synchronized必须是进入同一个对象的monitor才有上述的效果。如果不是同一个synchronized的对象,不遵循以上规则;如果不加synchronized的对象,不遵从以上规则。
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args){
synchronized(lock){
counter++;
}
}
Exceprion table中的意思是从from到to如果发生任何异常,都会转到target来继续执行。
monitorenter
和monitorexit
指令,其中,monitorenter
指令指向同步代码块开始的位置,monitorexit
指向同步代码块的结束位置;当执行monitorenter
指令时,判断monitor中的计数器是否为0,如果为0,则会将monitor中的计数器加1表示加锁成功,如果不为0,则判断当前线程是否为monitor中Owner的线程,是则加1(可重入锁)表示加锁成功,否则阻塞;当执行monitorexit
指令时,会将monitor中的计数器值减1,如果当前计数器值减1后为0,表示释放锁。monitorenter
和monitorexit
指令,取而代之的是ACC_SYNCHRONIZED
标识,该标识指明了该方法是否是一个同步方法,JVM通过访问该标识来辨别一个方法是否为同步方法,从而执行相应的同步调用。1.轻量级锁的使用场景
2.轻量级锁的加锁过程
//以下代码,method1()和method()2加锁的时间就是错开的,即不会竞争锁
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}
每当代码执行到synchronized代码块时,都会创建一个锁记录(Lock Record)对象,线程的每个栈帧中都存放着一个锁记录对象,锁记录内部包含两个部分,起始时一部分存放了锁记录对象的地址和00标志位
(00标志代表轻量级锁),另一部分为空(全为0)。
让锁记录中的Object reference指向要加锁的Java对象的存储地址,并尝试用CAS方式来交换lock record地址和00标志位
和要加锁的Java对象的Mark Word
。
如果交换成功,则表示加上了轻量级锁。此时Java对象的对象头中存储了锁记录对象的地址和00标志位
。
如果交换失败,则存在两种情况:
情况一:自己执行了synchronized锁的重入,此时仍然需要添加一个锁记录对象,然后进行CAS交换并指向Java对象的引用,但此时CAS交换失败,新增加的锁记录对象中锁记录对象的地址和00标志位
的位置为null且Object reference部分指向Java对象的锁记录。此时的锁记录对象作为重入的计数。
情况二:其他线程已经持有了该Java对象的轻量级锁,表明存在竞争,进入锁的膨胀过程。
当解锁时,分为三种情况考虑
情况一:正常情况,直接使用CAS将Mark Word的值恢复给Java对象头时,取出的值不为null,且交换成功即表明解锁成功。
情况二:存在重入锁,如果进行CAS交换时,发现取值为null,则表示有重入锁,此时删除该锁记录对象,表示重入计数减一。
情况三:锁已膨胀,如果进行CAS交换时,发现取值不null,且交换失败,此时说明轻量级锁已膨胀为重量级锁,则进入重量级锁解锁流程。
1.基本概念
2.流程
当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁,这时Thread-1加轻量级锁失败,进入锁膨胀流程。
首先,为对象申请Monitor锁,让Object指向重量级锁地址,随后,然后自己进入Monitor的EntryList变成BLOCKED状态。
当Thread-0退出synchronized同步块时,使用CAS将Mark Word的值恢复给对象头,对象的对象头指向Monitor,那么会进入重量级锁的解锁过程,即按照Monitor的地址找到Monitor对象,将Owner设置为null ,唤醒EntryList中的Thread-1线程。
重量级锁竞争的时候,还可以使用自旋来进行优化。
自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。在Java6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。Java7之后不能控制是否开启自旋功能。
1.基本概念
2.轻量级锁和偏向锁的对比
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
}
}
3.偏向状态
-XX:BiasedLockingStartupDelay=0
来禁用延迟。-xx:-UseBiasedLocking
来禁用偏向锁,UseBiasedLocking前的-
就代表禁用,如果是+
代表启用。4.偏向锁的撤销
5.批量重偏向
6.批量撤销
7.锁消除
-XX:-Eliminatelocks
来禁止锁的消除。前面,在介绍Moinitor和synchronized时,曾对wait()和notify()方法有所提及,接下来,我们进行详细的解析。
方法 | 作用 |
---|---|
wait() | 会使当前线程陷入等待状态 |
wait(long timeout) | 在wait()的基础上,额外设置了等待时间,单位为ms,如果超过等待时间还未被唤醒,则当前线程会自动苏醒,并开始竞争CPU资源 |
wait(long timeout, int nanos) | 在wait(long timeout)的基础上,企图进行ns级的时间控制,但实际上也只能在ms级别进行设置,后面的nanos只要设置了大于0,就会增加1ms,并不能将时间精度控制在ns级 |
notify() | 随机唤醒一个在该锁的WaitSet中的线程 |
notifyAll() | 唤醒所有在该锁的WaitSet中的线程 |
注意:调用了wait()和notify()、notifyAll()方法的锁,一定会升级为重量级锁。
sleep() | wait() |
---|---|
Thread的方法 | Object的方法 |
可以在任何地方使用 | 必须和synchronized配合使用 |
不会释放对象锁 | 会释放对象锁 |
每个线程都有一个属于自己的Parker对象,该Parker对象是用C语言实现的,由_counter、_cond、_mutex三个部分组成。
方法 | 作用 |
---|---|
park() | 阻塞当前线程 |
unpark() | 解锁当前线程 |
注意:如果先调用了unpark(),再调用park()则,线程不会被阻塞。
public class TestLiveLock{
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args){
//期望减到0退出循环
new Thread(() -> {
while(count > 0){
sleep(1);
count--;
}
}).start();
//期望加到20退出循环
new Thread(() -> {
while(count < 20){
sleep(1);
count++;
}
}).start();
}
}
ReentrantLcok | synchronized |
---|---|
可打断,获取不到锁可被其他线程打断等待 | 不可打断,获取不到锁会一直等待 |
可设置超时时间,指定时间内未获取锁则放弃 | 不可设置超时时间 |
默认为非公平锁,但可以设置为公平锁 | 一定为非公平锁 |
支持多个条件变量,WAITING状态线程可根据条件在不同的WaitSet内等待 | 仅支持单个条件变量,所有WAITING状态线程都在一个WaitSet内等待 |
注意:两者都是可重入锁。
//reentrantLock是ReetrantLock的实例
reentrantLock.lock();//放在try的代码块内外都可以
try{
//临界区
}finally{
//临界区
retrantLock.unLock();
}
lock()
方法,需要用lockInterruptibly()
方法。此时如果没有竞争,当前线程就会获取锁。如果存在竞争,当前线程就会进入阻塞队列,此时,当前线程可以被其他线程用interrput()方法打断阻塞。public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
//没有竞争,则获取锁
//有竞争,则进入队列阻塞,可被其他线程打断
lock.lockInterruptibly();
} catch (InterruptedException e) {
//被打断
e.printStackTrace();
//返回,不再向下执行
return;
}finally {
// 释放锁
lock.unlock();
}
});
//主线程获取锁
lock.lock();
try {
//t1线程企图获取锁
t1.start();
Thread.sleep(1000);
//主线程打断t1线程
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
tryLock()
,表明尝试获得锁,该方法会返回一个布尔值,如果获取成功,则返回true,如果获取失败,则不等待直接返回,并返回false。public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
// 未设置等待时间,一旦获取失败,直接返回false
if(!lock.tryLock()) {
// 获取失败,不再向下执行,返回
return;
}
lock.unlock();
});
lock.lock();
try{
t1.start();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
tryLock(long timeout, TimeUint)
,该方法的两个参数分别为等待时间和时间单位,如果当前线程在指定时间内未能成功获取锁,则不再等待,直接返回。注意,此时的tryLock的等待状态,也可以被其他线程中的interrupt()方法打断。public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
// 判断获取锁是否成功,最多等待1秒
if(!lock.tryLock(1, TimeUnit.SECONDS)) {
// 获取失败,不再向下执行,直接返回
return;
}
} catch (InterruptedException e) {
// 被打断
e.printStackTrace();
//不再向下执行,直接返回
return;
}
// 释放锁
lock.unlock();
});
lock.lock();
try{
t1.start();
// 打断等待
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}