java 多线程(4) 线程同步之锁(synchronized) / 死锁 / 两个锁定对象期间访问(修改)其变量的面试

来源:互联网 发布:mac cocos2dx android 编辑:程序博客网 时间:2024/05/20 19:32

一. 锁的定义

锁就是synchronized 关键字,记住synchronized(this )是锁定当前对象。在函数m1()里面写synchronized( this ),这个和public synchronized void m1() 等价。

但是他只锁定当前对象的synchronized 大括号内的话,其他,这个synchronized 不去锁定。这个对象的其他方法 / 变量( 被synchronized 语句块改过后的) 还能被其他线程调用。锁定了这个对象,但是不是说完全锁定了,针对这个方法的锁定期间,其他方法照样可以修改变量值。  所以,如果你想把类变量 ( 银行账户值 ) 值控制的完全,不会被错误修改,要把所有控制这个变量的方法全部考虑到,该synchronized 要synchronized 。

二. 锁的粒度

如果是对象变量,锁this,即只对操作这个对象的众多线程有限制作用,对其他类对象无关,如果是类变量,锁class。锁谁,就是拿到谁的锁,那么其他需要这个锁的语句块,就得排队,但是不需要这个锁的语句块,直接执行不受阻碍。所以就有了锁obja,锁objb这种有多把锁的情况。而不是简单的只锁this 。也就是说m1() 要等着锁的到来才能执行,而m2()不需要等着锁就能执行( synchronized )。

三. 锁的代码


锁代码1

package test.java.Thread;public class TestSync implements Runnable{Timer timer = new Timer();public static void main(String[] args) {TestSync test = new TestSync();Thread t1 = new Thread(test);Thread t2 = new Thread(test);//注意这种用法,Runnable() 参数不为空,则调用t1.start() 时候,会调用test 的run()方法t1.setName("t1");t2.setName("t2");t1.start();t2.start();}@Overridepublic void run() {timer.add(Thread.currentThread().getName());}}class Timer {private int num =0;public void add(String name){synchronized (this) { //很明显不加这一句,打印结果是错的,解决办法就是在执行这7句话的过程中,请你把我当前的对象锁住 //这里加this 叫锁定当前对象num++;            //锁定当前对象的意思是在执行这个大括号里面的语句之中,一个线程执行的过程之中,不会被另外一个线程打断,一个线程已经进入到try{  //我这个锁定的区域里面了,你放心,不可能有另外一个线程也在这里面,这叫互斥锁。Thread.sleep(1);}catch(InterruptedException e){}System.out.println(name+" 你是第"+num+"个使用timer 的线程");//t1 你是第2个使用timer 的线程     t2 你是第2个使用timer 的线程 }//end synchronized}//public synchronized void add(String name){//这个是上面的简便写法,意思是在执行这个函数的过程之中,锁定当前对象//.//.//.//}}

死锁代码2

package test.java.Thread;/** *  * @author jalo *这是两个线程死锁,哲学家问题是多个线程转着圈的死锁 *  解决死锁的办法之一,把锁的粒度加粗一些,你锁定一个对象不就行了?非要锁定里面的两个对象(o1,o2), *  当然解决死锁还有很多其他办法。如果不写系统级的程序,很难碰到死锁的问题。因为死锁都被中间件厂商给解决了。 *  如果有机会写系统级的程序,比如自己实现一个数据库的连接池,就会自己实现各种锁,就会去考虑控制死锁。 * */public class TestDeadLock implements Runnable{  int flag ;static Object o1 = new Object();static Object o2 = new Object();@Overridepublic void run() {if(flag==0){synchronized (o1) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2) {System.out.println("flag==0");}}}else if (flag==1){synchronized (o2) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o1) {System.out.println("flag==1");}}}}public static void main(String[] args) {TestDeadLock td1 = new TestDeadLock();TestDeadLock td2 = new TestDeadLock();td1.flag = 0;td2.flag = 1;Thread t1 = new Thread(td1);Thread t2 = new Thread(td2);t1.start();t2.start();}}

锁定对象期间访问其对象的面试题,代码3

package test.java.Thread;/** *  * @author jalo * 这是个面试题,一个线程在调用m1()期间,另一个线程可以调用m2()吗?如果可以,那么m2()调用的结果是b=0还是b=1000? * 可以调用,b=1000 *  synchronized(this)是锁定当前对象,但是他只锁定当前对象的synchronized 大括号内的话, *  这个对象的其他方法 / 变量,这个synchronized 不去锁定。这个对象的其他方法 / 变量( 被synchronized 语句块改过后的) 还能被其他线程调用。 */public class ThreadLockQuestion {int b = 0;public synchronized void m1() throws Exception{b = 1000;Thread.sleep(1000000000);System.out.println("b= "+b);}public void m2(){System.out.println(b);}public static void main(String[] args) {ThreadLockQuestion tlq = new ThreadLockQuestion();ThreadLock tl1 = new ThreadLock(tlq);ThreadLock tl2 = new ThreadLock(tlq);tl1.flag = 1;tl2.flag = 2;Thread t1 = new Thread(tl1);Thread t2 = new Thread(tl2);t1.start();t2.start();}}class ThreadLock implements Runnable{ThreadLockQuestion tlq ;int flag;ThreadLock(ThreadLockQuestion tlq){this.tlq = tlq;}@Overridepublic void run() {try {if(flag==1){tlq.m1();System.out.println("flag==1");}if(flag==2){Thread.sleep(1000);tlq.m2();  //这里m2()看到的是1000 , System.out.println("flag==2");}} catch (Exception e) {e.printStackTrace();}}}
结果:

1000        //说明是m2()的调用结果,而且b 已经被锁语句块给改成了1000,并且锁住对象期间,还能访问这个对象的变量,访问结果是最新值
flag==2

............   //一大堆时间过后

b=1000

flag==1


锁定对象期间修改其变量的面试题,代码4

package test.java.Thread;/** *  * @author jalo * 这是个面试题,一个线程在调用m1()期间,另一个线程可以调用m2()吗?如果可以,那么m2()调用的结果是b=0还是b=1000? * 可以调用,b=1000 *  synchronized(this)是锁定当前对象,但是他只锁定当前对象的synchronized 大括号内的话,即m1()方法中的语句 *  锁定了这个对象,但是不是说完全锁定了这个对象,针对这个方法m1()的锁定期间,m2()方法照样可以修改变量b 的值。 *   */public class ThreadLockQuestion {int b = 0;public synchronized void m1() throws Exception{b = 1000;Thread.sleep(5000);System.out.println("b= "+b);  //这个是b=2000,意思是虽然m1()被锁定期间,但是m2()照样能改b 的值}public void m2() throws Exception{Thread.sleep(2500);b=2000;System.out.println(b);}public static void main(String[] args) {ThreadLockQuestion tlq = new ThreadLockQuestion();ThreadLock tl1 = new ThreadLock(tlq);ThreadLock tl2 = new ThreadLock(tlq);tl1.flag = 1;tl2.flag = 2;Thread t1 = new Thread(tl1);Thread t2 = new Thread(tl2);t1.start();t2.start();}}class ThreadLock implements Runnable{ThreadLockQuestion tlq ;int flag;ThreadLock(ThreadLockQuestion tlq){this.tlq = tlq;}@Overridepublic void run() {try {if(flag==1){tlq.m1();System.out.println("flag==1");}if(flag==2){Thread.sleep(1000);tlq.m2();  //这里m2()看到的是1000 , System.out.println("flag==2");}} catch (Exception e) {e.printStackTrace();}}}

结果:

2000   //以下是m2()调用结果    

flag==2      
b= 2000    // 以下是m1()调用结果,可见m1()锁定期间,m2()照样能改b 的值。使用同步要非常小心,你m1()同步了,但是其他线程可以自由访问任意未同步的方法m2(),其访问可以对你同步的方法m1()产生影响。所以要仔细考虑函数加不加同步,加了同步,导致效率变低,不加同步,有可能导致数据不一致的情况。
flag==1     //


代码4改进,代码5

如果你想把类变量 ( 银行账户值 ) 值控制的完全,不会被错误修改,要把所有控制这个变量的方法全部考虑到,该synchronized 要synchronized。这里把m2()也改成synchronized 即可。

改了这一句,加了synchronized,public synchronized void m2() throws Exception{

package test.java.Thread;/** *  * @author jalo * 这是个面试题,一个线程在调用m1()期间,另一个线程可以调用m2()吗?如果可以,那么m2()调用的结果是b=0还是b=1000? * 可以调用,b=1000 *  synchronized(this)是锁定当前对象,但是他只锁定当前对象的synchronized 大括号内的话,即m1()方法中的语句 *  锁定了这个对象,但是不是说完全锁定了这个对象,针对这个方法m1()的锁定期间,m2()方法照样可以修改变量b 的值。 *   */public class ThreadLockQuestion {int b = 0;public synchronized void m1() throws Exception{b = 1000;Thread.sleep(5000);System.out.println("b= "+b);  //这个是b=2000,意思是虽然m1()被锁定期间,但是m2()照样能改b 的值}public synchronized void m2() throws Exception{Thread.sleep(2500);b=2000;System.out.println(b);}public static void main(String[] args) {ThreadLockQuestion tlq = new ThreadLockQuestion();ThreadLock tl1 = new ThreadLock(tlq);ThreadLock tl2 = new ThreadLock(tlq);tl1.flag = 1;tl2.flag = 2;Thread t1 = new Thread(tl1);Thread t2 = new Thread(tl2);t1.start();t2.start();}}class ThreadLock implements Runnable{ThreadLockQuestion tlq ;int flag;ThreadLock(ThreadLockQuestion tlq){this.tlq = tlq;}@Overridepublic void run() {try {if(flag==1){tlq.m1();System.out.println("flag==1");}if(flag==2){Thread.sleep(1000);tlq.m2();  //这里m2()看到的是1000 , System.out.println("flag==2");}} catch (Exception e) {e.printStackTrace();}}}


结果:

b= 1000
flag==1       //由此可见,本来应该m2()调用先出结果,但是却死等m1()结束,说明synchronized m2() 起了作用。
2000
flag==2       //线程1,在调用 m1() 的执行的过程中,线程2要调用其他的 synchronized 的函数也会卡死,直到线程1执行完,排队的线程2 的函数再执行。

                     //于是,m1(),m2() 这种都是synchronized 的函数,又一样了,又有顺序了,又不能打乱了,无论几个线程执行他们,都是按照顺序。谁先调用(谁 

                      先拿到this 锁,这里是t1 先拿到),谁就执行,执行完,再执行后排队的,一个个调用着走。这其实就是第一行说的和synchronized( this ) 等价,故

                      t1 调用 m1() 期间拿到this的锁,直到 t1 结束,t2 调用m2() 才能拿到this 的锁去执行m2()。这叫从原理上理解。













0 0
原创粉丝点击