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

一. 锁的定义

锁就是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();
	}

	@Override
	public 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();
	
	@Override
	public 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;
	}
	
	@Override
	public 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;
	}
	
	@Override
	public 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;
	}
	
	@Override
	public 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()。这叫从原理上理解。

    原文作者:java锁
    原文地址: https://blog.csdn.net/nx188/article/details/50510737
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞