synchronized详解

synchronized关键字主要用于对普通方法,静态方法和代码块java

synchronized用于代码块

package Synchronized;

public class Test02 {
	public void method1(){
		System.out.println("method 1 start");
		try{
			synchronized(this){
				System.out.println("method 1 execute");
				Thread.sleep(3000);
			}
			
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 1 end");
	}
	public void method2(){
		System.out.println("method 2 start");
		try{
			synchronized(this){
				System.out.println("method 2 execute");
				Thread.sleep(1000);
			}
			
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 2 end");
	}
	public static void main(String[] args){
		Test02 test = new Test02();
		new Thread(new Runnable(){
			public void run(){
				test.method1();
			}
		}).start();;
		new Thread(new Runnable(){
			public void run(){
				test.method2();
			}
		}).start();;
	}
}
输出:
method 1 start
method 1 execute
method 2 start
method 2 execute
method 1 end
method 2 end

虽然线程1和线程2都进入了对应的方法开始执行,可是线程2在进入同步块以前,须要等待线程1中同步块执行完成。多线程

对如下代码反编译并发

public class T {
	//同步代码块,使用monitor监视器
	public void method3() {
		synchronized(this){
			System.out.println("Hello World!");
		}
	}
}

获得高并发

Compiled from "T.java"
public class Synchronized.T extends java.lang.Object{
public Synchronized.T();
  Code:
   0:	aload_0
   1:	invokespecial	#8; //Method java/lang/Object."<init>":()V
   4:	return

public void method3();
  Code:
   0:	aload_0
   1:	dup
   2:	astore_1
   3:	monitorenter
   4:	getstatic	#15; //Field java/lang/System.out:Ljava/io/PrintStream;
   7:	ldc	#21; //String Hello World!
   9:	invokevirtual	#23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   12:	aload_1
   13:	monitorexit
   14:	goto	20
   17:	aload_1
   18:	monitorexit
   19:	athrow
   20:	return
  Exception table:
   from   to  target type
     4    14    17   any
    17    19    17   any

}
 

每一个对象有一个监视器锁(monitor)。synchronized同步代码块时,线程便会执行monitorenter指令时尝试获取monitor的全部权,过程以下:性能

一、若是monitor的进入数为0,则该线程进入monitor,而后将进入数设置为1,该线程即为monitor的全部者。this

二、若是线程已经占有该monitor,只是从新进入,则进入monitor的进入数加1.spa

3.若是其余线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor的全部权。   线程

4.执行monitorexit的线程必须是objectref所对应的monitor的全部者。指令执行时,monitor的进入数减1,若是减1后进入数为0,那线程退出monitor,再也不是这个monitor的全部者。其余被这个monitor阻塞的线程能够尝试去获取这个 monitor 的全部权。 对象

synchronized用于普通方法

//对普通方法同步
public class Test01 {
	public synchronized void method1(){
		System.out.println("method 1 start");
		try{
			System.out.println("method 1 execute");
			Thread.sleep(100);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 1 end");
	}
	public synchronized void method2(){
		System.out.println("method 2 start");
		try{
			System.out.println("method 2 execute");
			Thread.sleep(300);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 2 end");
	}
	public static void main(String[] args){
		Test01 test = new Test01();
		new Thread(new Runnable(){
			public void run(){
				test.method1();
			}
		}).start();;
		new Thread(new Runnable(){
			public void run(){
				test.method2();
			}
		}).start();;
	}
}
输出:
method 1 start
method 1 execute
method 1 end
method 2 start
method 2 execute
method 2 end

 可见,线程2对test对象的method2方法的执行须要等待线程1对test对象的method1方法的执行结束后才能执行。blog

方法的同步并无经过指令monitorenter和monitorexit来完成,不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其余任何线程都没法再得到同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需经过字节码来完成。

synchronized用于静态方法

package Synchronized;

public class Test03 {
	public synchronized static void method1(){
		System.out.println("method 1 start");
		try{
			System.out.println("method 1 execute");
			Thread.sleep(100);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 1 end");
	}
	public synchronized static void method2(){
		System.out.println("method 2 start");
		try{
			System.out.println("method 2 execute");
			Thread.sleep(300);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 2 end");
	}
	public static  void main(String[] args){
		Test03 test1 = new Test03();
		Test03 test2 = new Test03();
		new Thread(new Runnable(){
			public void run(){
				test1.method1();
			}
		}).start();;
		new Thread(new Runnable(){
			public void run(){
				test2.method2();
			}
		}).start();;
	}
}
输出:
method 1 start
method 1 execute
method 1 end
method 2 start
method 2 execute
method 2 end

执行结果以下,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),因此即便test和test2属于不一样的对象,可是它们都属于SynchronizedTest类的实例,因此也只能顺序的执行method1和method2,不能并发执行。

总结

synchronized是经过软件(JVM)实现的,简单易用,即便在JDK5以后有了Lock,仍然被普遍地使用。

synchronized其实是非公平的,新来的线程有可能当即得到监视器,而在等待区中等候已久的线程可能再次等待,不过这种抢占的方式能够预防饥饿。

synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。

多线程竞争一个锁时,其他未获得锁的线程只能不停的尝试得到锁,而不能中断。高并发的状况下会致使性能降低。ReentrantLock的lockInterruptibly()方法能够优先考虑响应中断。 一个线程等待时间过长,它能够中断本身,而后ReentrantLock响应这个中断,再也不让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。

相关文章
相关标签/搜索