一、概念
1、多个线程之间访问同一资源,进行协调的过程是线程同步。例如两个人同时操作同一银行账户。解决方法:加锁
2、Java种引入对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。关键字synchronized来与对象的互斥锁联系。
当synchronized修饰方法时,是指当前调用该方法的对象被锁定。当synchronized(*)修饰代码块时,*指定的对象被锁定。如果*为this,则与synchronized修饰方法达到的效果一致。
public class Test extends Thread{ public static void main(String args[]){ Test t = new Test(); Test t2 = new Test(); t.start(); t.m2(); } public synchronized void m1(){ System.out.println("m1"); try{ Thread.sleep(10000); }catch(Exception e){ } } public void m2(){ System.out.println("m2"); try{ Thread.sleep(10000); }catch(Exception e){ } } public void run(){ m1(); } }
输出:m2m1
public class Test extends Thread{ public static void main(String args[]){ Test t = new Test(); Test t2 = new Test(); t.start(); t.m2(); } public synchronized void m1(){ System.out.println("m1"); try{ Thread.sleep(10000); }catch(Exception e){ } } public synchronized void m2(){ System.out.println("m2"); try{ Thread.sleep(10000); }catch(Exception e){ } } public void run(){ m1(); } }
输出:m2 m1
public class Test extends Thread{ public static void main(String args[]){ Test t = new Test(); Test t2 = new Test(); t.start(); t2.start(); t.m2(); t2.m2(); } public synchronized void m1(){ System.out.println("m1"); try{ Thread.sleep(10000); }catch(Exception e){ } } public synchronized void m2(){ System.out.println("m2"); try{ Thread.sleep(10000); }catch(Exception e){ } } public void run(){ m1(); } }
输出:m1m1 m2 m2
public class Test extends Thread{ static Object lock=new Object(); public static void main(String args[]){ Test t = new Test(); Test t2 = new Test(); t.start(); t2.start(); t.m2(); t2.m2(); } public void m1(){ synchronized(lock){ System.out.println("m1"); try{ Thread.sleep(10000); }catch(Exception e){ } } } public void m2(){ synchronized(lock){ System.out.println("m2"); try{ Thread.sleep(10000); }catch(Exception e){ } } } public void run(){ m1(); } }
输出:m1 m1 m2 m2
二、举例
t1,t2两个线程同时访问同一个对象test。
1 public class TestSync implements Runnable{ 2 Timer timer = new Timer(); 3 public static void main(String args[]){ 4 TestSync test = new TestSync(); 5 Thread t1 = new Thread(test); 6 Thread t2 = new Thread(test); 7 t1.setName("t1"); 8 t2.setName("t2"); 9 t1.start(); 10 t2.start(); 11 } 12 public void run(){ 13 timer.add(Thread.currentThread().getName()); 14 } 15 } 16 17 class Timer{ 18 private static int num = 0 ; 19 public void add(String name){ 20 synchronized(this){ 21 num++; 22 try{ 23 Thread.sleep(1);//改成 wait(100);不行,因为wait释放锁 24 }catch(InterruptedException e){ 25 } 26 System.out.println(name+",你是第"+num+"个使用timer的线程"); 27 } 28 } 29 }
输出:
t1,你是第1个使用timer的线程
t2,你是第2个使用timer的线程
如果去掉synchronized则输出错误:
t2,你是第2个使用timer的线程
t1,你是第2个使用timer的线程
错误原因:一个线程的执行过程中,被另一个线程打断了。
解决方法一:在20行加入synchoronized互斥锁(上面的代码)。因为synchoronized锁定的是当前对象,而
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
所以能够达到同步效果,因为t1,t2是同一个test对象,对应的timer也是同一个。如果
Thread t1 = new Thread(test1);
Thread t2 = new Thread(test2);
则不能达到同步效果,因为t1,t2不是同一对象,对应的timer不是同一个。
package test2; public class TestSync implements Runnable{ static Timer timer = new Timer(); public static void main(String args[]){ TestSync test = new TestSync(); TestSync test2 = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test2); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } public void run(){ timer.add(Thread.currentThread().getName()); } } class Timer{ private static int num = 0 ; public synchronized void add(String name){ num++; try{ Thread.sleep(3000);//改成 wait(100);不行,因为wait释放锁 }catch(InterruptedException e){ } System.out.println(name+",你是第"+num+"个使用timer的线程"); } }
输出正确,因为timer是同一个对象。
解决方法二:或者在19行加入同步方法。如下:
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); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } public void run(){ timer.add(Thread.currentThread().getName()); } } class Timer{ private static int num = 0 ; public synchronized void add(String name){ //synchronized(this){ num++; try{ Thread.sleep(1); }catch(InterruptedException e){ } System.out.println(name+",你是第"+num+"个使用timer的线程"); //} } }
注意调用sleep的线程不释放锁,但调用wait的线程释放锁。
三、死锁的模拟
public class TestDeadLock implements Runnable{ public int flag = 1 ; static Object o1 = new Object(); static Object o2 = new Object(); public void run(){ System.out.println("flag="+flag); if(flag==1){ synchronized(o1){ try{ Thread.sleep(500); }catch(Exception e){ e.printStackTrace(); } synchronized(o2){ System.out.println("1"); } } } if(flag==0){ synchronized(o2){ try{ Thread.sleep(500); }catch(Exception e){ e.printStackTrace(); } synchronized(o1){ System.out.println("0"); } } } } public static void main(String[] args){ TestDeadLock t1 = new TestDeadLock(); TestDeadLock t2 = new TestDeadLock(); t1.flag = 1 ; t2.flag = 0 ; Thread t11 = new Thread(t1); Thread t22 = new Thread(t2); t11.start(); t22.start(); } }
死锁的条件(四个同时满足):互斥、部分锁定、循环等待、不可剥夺
解决方法:锁的粒度加粗(一次性锁定全部需要的资源,破坏部分锁定)
规定顺序(破坏循环等待)
可以剥夺(破坏不可剥夺)
四、synchronized面试题
(1)
问:上面程序中一个线程调用m1方法时,另一个线程可以同时调用m2方法吗?
答案:可以同时执行。main()函数运行输出1000,b=1000,说明可以同时执行两个方法。(如果输出100,b=1000则说明不可以)。如果将m2方法改为:
结果又是什么呢?答案:可以同时执行。输出2000,b=2000。
执行过程:m1修改b为1000,之后睡眠,m2修改b为2000,main中打印2000,m1睡眠结束打印b=2000。
注:那个方法加了synchronized只是说明该方法同时可以被一个线程调用,但是其他线程仍然可以自由调用非同步的方法(可能对同步方法产生影响,例如涉及到该同步方法中的变量)。
(2)正确做法:m1,m2都加同步
所以说,b相当于一个资源,如何控制该资源能够被正确访问呢?这就需要把涉及到该资源b的所有方法都考虑到!哪些方法是不是要设置为synchronized,所以本题中需要把m1,m2方法都加锁,即用synchronized修饰为同步方法。如下:
结果又是什么?答案:输出1000,b=1000
执行过程:m1,m2不能同时执行。运行m2方法,b的值改为2000,之后运行m1方法输出b改为1000,m1睡眠,main主线程输出1000,m1方法输出b=1000。
5、两个方法一个读该对象,另一个修改该对象的值。一般的情况下改的方法加锁(同步方法),因为不允许多个线程同时改;读的方法不加锁(非同步方法),因为可以同时读。所以是加只读锁。