线程安全是并发编程中的重要关注点。
形成线程安全成绩的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。
为了处置这个成绩,我们能够需求这样一个方案,当存在多个线程操作共享数据时,需求保证同一时辰有且只要一个线程在操作共享数据,其他线程必须等到该线程处置完数据后再停止。
在 Java 中,关键字 Synchronized可以保证在同一个时辰,只要一个线程可以执行某个办法或许某个代码块(主要是对办法或许代码块中存在共享数据的操作)。
下面来一同探求Synchronized的基本运用、完成机制。
面试题来自:社招一年半面经分享(含阿里美团头条京东滴滴)
用法 两类锁:对象锁:包括办法锁(默许锁对象为this以后实例对象)和同步代码块锁(本人指定锁对象)。
类锁:指Synchronized修饰静态的办法或指定锁为Class对象。
当一个线程试图拜访同步代码块时,它首先必须失掉锁,而参加或抛出异常时必须释放锁。
给普通办法加锁时,上锁的对象是 this;
给静态办法加锁时,锁的是 class 对象;
给代码块加锁,可以指定一个详细的对象作为锁。
代码示例如下:
public class SynchronizedTest {
/**
* 修饰静态办法, 同等于下面注释的办法
*/
public synchronized static void test1() {
System.out.println("月伴飞鱼");
}
// public static void test1() {
// synchronized (SynchronizedTest.class){
// System.out.println("月伴飞鱼");
// }
// }
/**
* 修饰实例办法, 同等于下面注释的办法
*/
public synchronized void test2(){
System.out.println("月伴飞鱼");
}
// public void test2(){
// synchronized (this){
// System.out.println("月伴飞鱼");
// }
// }
/**
* 修饰代码块
*/
public void test3(){
synchronized (this){
System.out.println("月伴飞鱼");
}
}
}
多线程拜访同步办法的几种状况:两个线程同时拜访一个对象的同步办法。
由于同步办法锁运用的是this对象锁,同一个对象的this锁只要一把,两个线程同一时间只能有一个线程持有该锁,所以该办法将会串行运转。
两个线程拜访的是两个对象的同步办法。
由于两个对象的this锁互不影响,Synchronized将不会起作用,所以该办法将会并行运转。
两个线程拜访的是Synchronized的静态办法。
Synchronized修饰的静态办法获取的是以后类模板对象的锁,该锁只要一把,无论拜访多少个该类对象的办法,都将串行执行。
同时拜访同步办法与非同步办法
非同步办法不受影响。
拜访同一个对象的不同的普通同步办法。
由于this对象锁只要一个,不同线程拜访多个普通同步办法将串行运转。
同时拜访静态Synchronized和非静态Synchronized办法
静态Synchronized办法的锁为class对象的锁,非静态Synchronized办法锁为this的锁,它们不是同一个锁,所以它们将并行运转。
运用优化大家在运用synchronized关键字的时分,能够常常会这么写:
synchronized (this) {
...
}
它的作用域是以后对象,锁的就是以后对象,谁拿到这个锁谁就可以运转它所控制的代码。
当有一个明白的对象作为锁时,就可以这么写,但是当没有一个明白的对象作为锁,只想让一段代码同步时,可以创立一个特殊的变量(对象)来充任锁:
public class Demo {
private final Object lock = new Object();
public void methonA() {
synchronized (lock) {
...
}
}
}
这样写没成绩。但是用new Object()作为锁对象能否是一个最佳选择呢?
我在StackOverFlow看到这么一篇文章:object-vs-byte0-as-lock
大意就是用new byte[0]作为锁对象更好,会增加字节码操作的次数。
public class Demo {
private final byte[] lock = new byte[0];
}
详细细节大家,可以看看这篇文章,算提供一种思绪吧!
完成原理由于Synchronized锁的是对象,在解说原理之前先引见下对象结构相关知识。
HotSpot虚拟机中,对象在内存中存储的规划可以分为三块区域:对象头、实例数据和对齐填充。
对象头对象头包括两部分信息:运转时数据Mark Word和类型指针
假设对象是数组对象,那么对象头占用3个字宽(Word)(需求记载数组长度),假设对象是非数组对象,那么对象头占用2个字宽(1word = 2Byte = 16bit)
对象头的类型指针指向该对象的类元数据,虚拟机经过这个指针可以确定该对象是哪个类的实例
Mark Word用于存储对象本身的运转时数据, 如哈希码(HashCode)、GC分代年龄、锁形状标志、线程持有的锁、倾向线程ID、倾向时间戳等等,它是完成轻量级锁和倾向锁的关键。
这部分数据的长度在32位和64位的虚拟机(暂不思索开启紧缩指针的场景)中辨别为32个和64个Bits。
Synchronized锁对象就存储在MarkWord中,下面是MarkWord的规划:
32位虚拟机 (责任编辑:admin)