java内置锁是一个互斥锁,这就是意味着最多只有一个线程可以得到该锁,当线程A尝试去得到线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,若是B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,可是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。咱们知道,类的对象实例能够有不少个,可是每一个类只有一个class对象,因此不一样对象实例的对象锁是互不干扰的,可是每一个类只有一个类锁。可是有一点必须注意的是,其实类锁只是一个概念上的东西,并非真实存在的,它只是用来帮助咱们理解锁定实例方法和静态方法的区别的
上面已经对锁的一些概念有了一点了解,下面探讨synchronized关键字的用法。
synchronized的用法:synchronized修饰方法和synchronized修饰代码块。
下面分别分析这两种用法在对象锁和类锁上的效果。
对象锁的synchronized修饰方法和代码块:
html
public class TestSynchronized { public void test1() { synchronized(this) { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public synchronized void test2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" ); Thread test2 = new Thread( new Runnable() { public void run() { myt2.test2(); } }, "test2" ); test1.start();; test2.start(); // TestRunnable tr=new TestRunnable(); // Thread test3=new Thread(tr); // test3.start(); } } test2 : 4 test2 : 3 test2 : 2 test2 : 1 test2 : 0 test1 : 4 test1 : 3 test1 : 2 test1 : 1 test1 : 0
上述的代码,第一个方法时用了同步代码块的方式进行同步,传入的对象实例是this,代表是当前对象,固然,若是须要同步其余对象实例,也不可传入其余对象的实例;第二个方法是修饰方法的方式进行同步。由于第一个同步代码块传入的this,因此两个同步代码所须要得到的对象锁都是同一个对象锁,下面main方法时分别开启两个线程,分别调用test1和test2方法,那么两个线程都须要得到该对象锁,另外一个线程必须等待。上面也给出了运行的结果能够看到:直到test2线程执行完毕,释放掉锁,test1线程才开始执行。(可能这个结果有人会有疑问,代码里面明明是先开启test1线程,为何先执行的是test2呢?这是由于java编译器在编译成字节码的时候,会对代码进行一个重排序,也就是说,编译器会根据实际状况对代码进行一个合理的排序,编译前代码写在前面,在编译后的字节码不必定排在前面,因此这种运行结果是正常的, 这里是题外话,最主要是检验synchronized的用法的正确性) 若是咱们把test2方法的synchronized关键字去掉,执行结果会如何呢? test1 : 4 test2 : 4 test2 : 3 test1 : 3 test1 : 2 test2 : 2 test2 : 1 test1 : 1 test2 : 0 test1 : 0 上面是执行结果,咱们能够看到,结果输出是交替着进行输出的,这是由于,某个线程获得了对象锁,可是另外一个线程仍是能够访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,获得了对象锁,其余线程仍是能够访问那些没有同步的方法(普通方法)。这里涉及到内置锁的一个概念(此概念出自java并发编程实战第二章):对象的内置锁和对象的状态之间是没有内在的关联的,虽然大多数类都将内置锁用作一种有效的加锁机制,但对象的域并不必定经过内置锁来保护。当获取到与对象关联的内置锁时,并不能阻止其余线程访问该对象,当某个线程得到对象的锁以后,只能阻止其余线程得到同一个锁。之因此每一个对象都有一个内置锁,是为了免去显式地建立锁对象。 因此synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就代表要得到该内置锁才能执行,并不能阻止其余线程访问不须要得到该内置锁的方法。 类锁的修饰(静态)方法和代码块:
public class TestSynchronized { public void test1() { synchronized(TestSynchronized.class) { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public static synchronized void test2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" ); Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" ); test1.start(); test2.start(); // TestRunnable tr=new TestRunnable(); // Thread test3=new Thread(tr); // test3.start(); } } test1 : 4 test1 : 3 test1 : 2 test1 : 1 test1 : 0 test2 : 4 test2 : 3 test2 : 2 test2 : 1 test2 : 0
其实,类锁修饰方法和代码块的效果和对象锁是同样的,由于类锁只是一个抽象出来的概念,只是为了区别静态方法的特色,由于静态方法是全部对象实例共用的,因此对应着synchronized修饰的静态方法的锁也是惟一的,因此抽象出来个类锁。其实这里的重点在下面这块代码,synchronized同时修饰静态和非静态方法
public class TestSynchronized { public synchronized void test1() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static synchronized void test2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" ); Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" ); test1.start(); test2.start(); // TestRunnable tr=new TestRunnable(); // Thread test3=new Thread(tr); // test3.start(); } } test1 : 4 test2 : 4 test1 : 3 test2 : 3 test2 : 2 test1 : 2 test2 : 1 test1 : 1 test1 : 0 test2 : 0
上面代码synchronized同时修饰静态方法和实例方法,可是运行结果是交替进行的,这证实了类锁和对象锁是两个不同的锁,控制着不一样的区域,它们是互不干扰的。一样,线程得到对象锁的同时,也能够得到该类锁,即同时得到两个锁,这是容许的。
到这里,对synchronized的用法已经有了必定的了解。这时有一个疑问,既然有了synchronized修饰方法的同步方式,为何还须要synchronized修饰同步代码块的方式呢?而这个问题也是synchronized的缺陷所在
synchronized的缺陷:当某个线程进入同步方法得到对象锁,那么其余线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易致使系统的崩溃。若是某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其余线程就要永远的等待。这是一个致命的问题。
固然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种状况下要优于同步方法的方面。例如在某个类的方法里面:这个类里面声明了一个对象实例,SynObject so=new SynObject();在某个方法里面调用了这个实例的方法so.testsy();可是调用这个方法须要进行同步,不能同时有多个线程同时执行调用这个方法。 这时若是直接用synchronized修饰调用了so.testsy();代码的方法,那么当某个线程进入了这个方法以后,这个对象其余同步方法都不能给其余线程访问了。假如这个方法须要执行的时间很长,那么其余线程会一直阻塞,影响到系统的性能。 若是这时用synchronized来修饰代码块:synchronized(so){so.testsy();},那么这个方法加锁的对象是so这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其余同步方法时没有影响的,由于他们持有的锁都彻底不同。
不过这里还有一种特例,就是上面演示的第一个例子,对象锁synchronized同时修饰方法和代码块,这时也能够体现到同步代码块的优越性,若是test1方法同步代码块后面有很是多没有同步的代码,并且有一个100000的循环,这致使test1方法会执行时间很是长,那么若是直接用synchronized修饰方法,那么在方法没执行完以前,其余线程是不能够访问test2方法的,可是若是用了同步代码块,那么当退出代码块时就已经释放了对象锁,当线程还在执行test1的那个100000的循环时,其余线程就已经能够访问test2方法了。这就让阻塞的机会或者线程更少。让系统的性能更优越。 一个类的对象锁和另外一个类的对象锁是没有关联的,当一个线程得到A类的对象锁时,它同时也能够得到B类的对象锁。 可能上面只有理论和代码,对刚接触的人比较难理解,下面举一个例子, 打个比方:一个object就像一个大房子,大门永远打开。房子里有 不少房间(也就是方法)。
这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙能够打开全部上锁的房间。