谈谈java中锁

来源:互联网 发布:win10 sql sever 编辑:程序博客网 时间:2024/06/07 06:08

首先我们得明白锁的作用是什么,锁无非就是为了解决多个线程访问同一个资源时候,为了解决数据可能发生不一致性而产生的。如果不存在这种情况,是不需要锁的。锁只是解决的一种途径,我们可以通过安全的并发容器,不可变对象,threadlocal,线程安全类,不共享数据等途径去解决。

其次我们要明白锁的范围,对象锁,类锁,内部锁之间的区别,下面我依次举例分析,

1.首先比较下同步对象方法,和同步对象块的区别。

     

public class SynTest {//private Object readObj=new Object();//private Object  wirteOjb=new Object();public  synchronized void Test1(){System.out.println("开始 test1");print("Test1");}public void Test2(){System.out.println("开始 test2");synchronized(this){print("Test2");}}private void print(String name){for(int i=1;i<5;i++){System.out.println(name+": "+i+" 次调用");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String [] args){final SynTest a=new SynTest();new Thread(new Runnable(){public void run(){   a.Test1();}}).start();new Thread(new Runnable(){public void run(){   a.Test2();}}).start();;}}

结果:

开始 test1
Test1: 1 次调用
开始 test2
Test1: 2 次调用
Test1: 3 次调用
Test1: 4 次调用
Test2: 1 次调用
Test2: 2 次调用
Test2: 3 次调用
Test2: 4 次调用


     由此我们可以分析得出,同步对象方法,和同步对象块都是对同一个对象加锁,那么谁先取得锁,那么谁就获得了优先执行权。其他线程必须等待锁释放后才能够执行。同步对象方法和同步对象块的区别,就是范围问题。方法块在进入方法时候就开始加锁,加锁的粗度比较大,而同步块在进入块的时候才加锁,粒度更细。到底是粗粒度好,还是细粒度好,这根据实际应用去分析。细粒度固然可以减少持有锁的时间,但如果方法中,反复多次持有锁,增加线程切换,细粒度未必就好了。

    大家仔细看结果,发现在执行方法1后,调用一次后,方法2  打印了 开始test2,这就是因为这里没有进入同步块,相当于一个执行一个普通方法的代码,在执行 开始打印test2后,进去同步块,应为有其它线程持有了锁,所以它必须等待。由此我们可以分析,多线程下,同步方法,和普通方法不会有影响,这里我就不举例了。

     同理,同步静态方法和同步静态类块也是类似的,大家可以自行试试


2.同步对象方法和同步对象块作用不同对象

public static void main(String [] args){final SynTest a=new SynTest();final SynTest b=new SynTest();new Thread(new Runnable(){public void run(){   a.Test1();}}).start();new Thread(new Runnable(){public void run(){   b.Test2();}}).start();;}
结果:

开始 test2
Test2: 1 次调用
开始 test1
Test1: 1 次调用
Test2: 2 次调用
Test1: 2 次调用
Test2: 3 次调用
Test1: 3 次调用
Test2: 4 次调用
Test1: 4 次调用

 把上面的main方法更改下,主要是两个线程作用不同对象,可以看到结果,方法是交替执行的,这是因为两个线程持有的是两个不同对象的锁,虽然属于同一个类,但是,不是同一个对象。持有锁不同,所以他们不存在互斥的问题。这就是类锁和对象锁的区别。类锁只有一个,不同线程持有同一个类锁,他们之间是互斥的。


3.根据上面的结论,我们可以推出,类锁和对象锁的区别。因为对象锁,类锁不是属于同一个锁,多个线程下也不会互斥,

package learn.thread;public class SynTest {//private Object readObj=new Object();//private Object  wirteOjb=new Object();public  synchronized void Test1(){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("开始 test1");print("Test1");}public void Test2(){System.out.println("开始 test2");synchronized(SynTest.class){print("Test2");}}private void print(String name){for(int i=1;i<5;i++){System.out.println(name+": "+i+" 次调用");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String [] args){final SynTest a=new SynTest();//final SynTest b=new SynTest();new Thread(new Runnable(){public void run(){   a.Test1();}}).start();new Thread(new Runnable(){public void run(){   a.Test2();}}).start();;}}
结果

开始 test2
Test2: 1 次调用
开始 test1
Test1: 1 次调用
Test2: 2 次调用
Test1: 2 次调用
Test2: 3 次调用
Test1: 3 次调用
Test2: 4 次调用
Test1: 4 次调用

   上面结果论证了 类锁和对象锁是不会相互影响的。


4.内部锁

     内存锁其实跟对象锁,类锁原理差不多,内部锁就是在对象内部定义了一个或多个对象,通过对这个内部对象加锁,来实现互斥。这样的好处就是减少了锁的粒度。我们常用的就是读写锁,实现锁分离。大家记住的原则就是,是否互斥,关键看是否持有的是同一个锁,理解了就明白各种锁区别在哪里。


package learn.thread;public class SynTest {private Object readObj=new Object();private Object  wirteOjb=new Object();public  synchronized void Test1(){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("开始 test1");print("Test1");}public void Test2(){System.out.println("开始 test2");synchronized(readObj){print("Test2");}}public void Test3(){System.out.println("开始 test3");synchronized(readObj){print("Test3");}}private void print(String name){for(int i=1;i<5;i++){System.out.println(name+": "+i+" 次调用");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String [] args){final SynTest a=new SynTest();//final SynTest b=new SynTest();new Thread(new Runnable(){public void run(){   a.Test1();}}).start();new Thread(new Runnable(){public void run(){   a.Test2();}}).start();new Thread(new Runnable(){public void run(){   a.Test3();}}).start();;}}
结果

开始 test2
Test2: 1 次调用
开始 test3
开始 test1
Test1: 1 次调用
Test2: 2 次调用
Test1: 2 次调用
Test2: 3 次调用
Test1: 3 次调用
Test2: 4 次调用
Test3: 1 次调用
Test1: 4 次调用
Test3: 2 次调用
Test3: 3 次调用
Test3: 4 次调用

这里启动了三个线程去跑。方法1 和(方法2,方法3)不是同一个锁,方法2和方法3是属于同一个锁。方法2和方法3谁先持有锁谁先执行。方法1跟 方法2,3获胜者交替执行。当然可以存在多个内部对象锁,原理一样。

5.可重入锁

package learn.thread;public class SynTest {private Object readObj=new Object();public  synchronized void Test1(){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("开始 test1");print("Test1");}public void Test2(){System.out.println("开始 test2");synchronized(readObj){print("Test2");}}public void Test3(){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("开始 test3");synchronized(this){print("Test3");Test1();}}private void print(String name){for(int i=1;i<5;i++){System.out.println(Thread.currentThread().getName()+":"+name+": "+i+" 次调用");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String [] args){final SynTest a=new SynTest();//final SynTest b=new SynTest();new Thread(new Runnable(){public void run(){   a.Test3();}},"thread 1").start();//new Thread(new Runnable(){//public void run(){//   a.Test3();//}//},"threa 2").start();}}
结果

开始 test3
thread 1:Test3: 1 次调用
thread 1:Test3: 2 次调用
thread 1:Test3: 3 次调用
thread 1:Test3: 4 次调用
开始 test1
thread 1:Test1: 1 次调用
thread 1:Test1: 2 次调用
thread 1:Test1: 3 次调用
thread 1:Test1: 4 次调用


分析:上面是一个线程多次加锁。首先我们进入test3方法 获取对象锁,打印执行语句后,又执行方法2再次获取当前对象锁,应为之前方法3的锁要在同步块结束之后才会释放,如果当前锁是不可重入的,那么当前方法2获取锁,就会失败。不会继续执行。这个时候java的可重入锁机制,同一个线程可以多次加锁,会用个计数器累加,方法执行完毕后,依次减少。当计数器为0的时候,就代表释放了锁。

6.  组合锁

package learn.thread;public class SynTest {private Object readObj=new Object();public  synchronized void Test1(){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("开始 test1");print("Test1");}public void Test2(){System.out.println("开始 test2");synchronized(readObj){print("Test2");}}public void Test3(){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("开始 test3");synchronized(readObj){print("Test3");Test1();}}private void print(String name){for(int i=1;i<5;i++){System.out.println(Thread.currentThread().getName()+":"+name+": "+i+" 次调用");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String [] args){final SynTest a=new SynTest();//final SynTest b=new SynTest();new Thread(new Runnable(){public void run(){   a.Test3();}},"thread 1").start();try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}new Thread(new Runnable(){public void run(){   a.Test1();}},"threa 2").start();}}
结果:

开始 test3
thread 1:Test3: 1 次调用
thread 1:Test3: 2 次调用
开始 test1
threa 2:Test1: 1 次调用
thread 1:Test3: 3 次调用
thread 1:Test3: 4 次调用
threa 2:Test1: 2 次调用
threa 2:Test1: 3 次调用
threa 2:Test1: 4 次调用
开始 test1
thread 1:Test1: 1 次调用
thread 1:Test1: 2 次调用
thread 1:Test1: 3 次调用
thread 1:Test1: 4 次调用

分析:线程1首先执行方法3,获取到内部锁,应为线程1启动后,主线程等待原因。防止线程1立马获取到对象锁,线程2获取到对象锁执行,线程1在打印方法后,执行方法1,这个时候因为线程2已经获取到了对象锁,等待线程2释放对象锁,然后去获取对象锁,释放对象锁,然后释放内部锁。如果线程2 在获取到对象锁后,嵌套又获取内部锁。

而线程1是先获取内部锁,然后再获取对象锁,彼此持有对方的锁不释放,就会产生死锁。清楚了加锁范围,和加锁对象。就容易理解了。


个人理解,若理解有误可提出。转发请注明作者。

0 0
原创粉丝点击