java并发编程这个领域中synchronized关键字一直都是元老级的角色,在java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,java的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。在JDK1.6之后java官方对从JVM层面对synchronized 较大优化,所以现在的synchronized锁效率也优化得很不错。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
synchronized实现原理
通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的,切换到类的对应目录执行javac SynchronizedTest.java命令生成编译后的.class文件,然后执行javap -v SynchronizedTest.class。
1)synchronized同步代码块
public class SynchronizedTest {
public static void main(String[] args) {
new SynchronizedTest().method();
}
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
2)synchronized修饰方法
public class SynchronizedTest {
public static void main(String[] args) {
new SynchronizedTest().method();
}
public synchronized void method() {
System.out.println("Hello World!");
}
}
synchronized 修饰的方法的同步并没有 monitorenter 指令和 monitorexit 指令完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
小结:对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方法,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一个时刻只能有一个线程获取到由synchronized所保护对象的监视器。
synchronized的应用
synchronized实现同步的基础是:Java中每个对象都可以作为锁,具体表现为以下三种形式:
1.对于普通同步方法,锁是当前实例对象;
2.对于静态同步方法,锁是当前类的class对象;
3.对于同步方法块,锁是synchronized括号里配置的对象
1)多个线程访问的是多个对象
public class HasSelfPrivateNum {
private int num = 0;
public static void main(String[] args) {
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
ThreadTestA thread1 = new ThreadTestA(numRef1);
thread1.start();
ThreadTestB thread2 = new ThreadTestB(numRef2);
thread2.start();
}
synchronized public void add(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static public class ThreadTestA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadTestA(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
numRef.add("a");
}
}
static public class ThreadTestB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadTestB(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
numRef.add("b");
}
}
}
两个线程ThreadTestA和ThreadTestB分别访问同一个类的不同实例的相同名称的同步方法,但是效果确实异步执行,因为synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。所以在上面的实例中,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态。当前创建了两个HasSelfPrivateNum类对象,所以就产生了两个锁。当ThreadTestA的引用执行到add方法run中的Thread.sleep(2000)语句时,ThreadB就会“乘机执行”。
2)多个线程访问的是同一个对象
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadTestA thread1 = new ThreadTestA(numRef);
thread1.start();
ThreadTestB thread2 = new ThreadTestB(numRef);
thread2.start();
}
多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。
3)脏读
发生脏读的情况实在读取实例变量时,此值已经被其他线程更改过。
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(3000);
this.password = password;
System.out.println("setValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//该方法前加上synchronized关键字就同步了
public void getValue() {
System.out.println("getValue method thread name="
+ Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadC thread = new ThreadC(publicVarRef);
thread.start();
Thread.sleep(500);//打印结果受此值大小影响
publicVarRef.getValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ThreadC extends Thread {
private PublicVar publicVar;
public ThreadC(PublicVar publicVar) {
this.publicVar = publicVar;
}
@Override
public void run() {
publicVar.setValue("B", "BB");
}
}
}
4)静态同步synchronized方法与synchronized(class)代码块
synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。
public class SynchronizedTest2 {
public static void printA() {
synchronized (SynchronizedTest2.class) {
try {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized public static void printB() {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
}
synchronized public void printC() {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");
}
static class ThreadA extends Thread {
private SynchronizedTest2 test;
public ThreadA(SynchronizedTest2 test) {
this.test = test;
}
@Override
public void run() {
test.printA();
}
}
static class ThreadB extends Thread {
private SynchronizedTest2 test;
public ThreadB(SynchronizedTest2 test) {
this.test = test;
}
@Override
public void run() {
test.printB();
}
}
static class ThreadC extends Thread {
private SynchronizedTest2 test;
public ThreadC(SynchronizedTest2 test) {
this.test = test;
}
@Override
public void run() {
test.printC();
}
}
public static void main(String[] args) {
SynchronizedTest2 test = new SynchronizedTest2();
ThreadA a = new ThreadA(test);
a.setName("A");
a.start();
ThreadB b = new ThreadB(test);
b.setName("B");
b.start();
ThreadC c = new ThreadC(test);
c.setName("C");
c.start();
}
}
静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。synchronized关键字加到非static静态方法上持有的是对象锁。线程A,B和线程C持有的锁不一样,所以A和B运行同步,但是和C运行不同步。
参考文献:
Java并发编程的艺术
https://blog.csdn.net/qq_3433...