43、多线程同步及synchronized关键字

为什么要引入同步机制
      在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
      解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

[java] view plain copy print ?
  1. public class FetchMoney 
  2.     public static void main(String[] args) 
  3.     { 
  4.         Bank bank = new Bank(); 
  5.         Thread t1 = new MoneyThread(bank);//柜台 
  6.         Thread t2 = new MoneyThread(bank);//取款机 
  7.      
  8.         t1.start(); 
  9.         t2.start(); 
  10.     } 
  11.      
  12. class Bank 
  13.     private int money = 1000
  14.      
  15. //  public synchronized int getMoney(int number) 
  16.     public  int getMoney(int number) 
  17.     { 
  18.         if(0>number) 
  19.         { 
  20.             return -1
  21.         } 
  22.         else if(money < number) 
  23.         { 
  24.             return -2
  25.         } 
  26.         else if(money < 0
  27.         { 
  28.             return -3
  29.         } 
  30.          
  31.         else 
  32.         { 
  33.             try 
  34.             { 
  35.                 Thread.sleep(1000); 
  36.             } catch (InterruptedException e) 
  37.             { 
  38.                 e.printStackTrace(); 
  39.             } 
  40.             money -= number; 
  41.             System.out.println("left money:"+ money); 
  42.             return number; 
  43.         } 
  44.     } 
  45. class MoneyThread extends Thread 
  46.     private Bank bank; 
  47.      
  48.     public MoneyThread(Bank bank) 
  49.     { 
  50.         this.bank = bank; 
  51.     } 
  52.     @Override 
  53.     public void run() 
  54.     { 
  55.         System.out.println(bank.getMoney(800)); 
  56.     } 


这个程序运行,对于银行一个账户1000元,有可能两次都取出800,剩余-600,也可能两次都取800,剩余200,结果明显不对,这是因为对getMoney(int money)这个方法的线程级访问无法控制造成的。如果方法的声明中加上synchronized关键字,就可以得到解决

1、synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。     

      java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示该对象上锁,,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法

2、

[java] view plain copy print ?
  1. public class ThreadTest4 
  2.     public static void main(String[] args) 
  3.     { 
  4.         Example example = new Example(); 
  5.         Thread t1 = new TheThread(example); 
  6.         Thread t2 = new TheThread2(example); 
  7.          
  8.         t1.start(); 
  9.         t2.start(); 
  10.     } 
  11. class Example 
  12.     public synchronized void excute() 
  13.     { 
  14.         for(int i = 0;i<20;i++) 
  15.         { 
  16.             try 
  17.             { 
  18.                 Thread.sleep(200); 
  19.             } catch (InterruptedException e) 
  20.             { 
  21.                 e.printStackTrace(); 
  22.             } 
  23.             System.out.println("example1:"+i); 
  24.         } 
  25.     } 
  26.      
  27.     public synchronized void excute2() 
  28.     { 
  29.         for(int i = 0;i<20;i++) 
  30.         { 
  31.             try 
  32.             { 
  33.                 Thread.sleep(500); 
  34.             } catch (InterruptedException e) 
  35.             { 
  36.                 e.printStackTrace(); 
  37.             } 
  38.             System.out.println("example2:"+i); 
  39.         } 
  40.     } 
  41. class TheThread extends Thread 
  42.     private Example example; 
  43.     public TheThread(Example example) 
  44.     { 
  45.         this.example = example; 
  46.     } 
  47.     public void run() 
  48.     { 
  49.         this.example.excute(); 
  50.     } 
  51. class TheThread2 extends Thread 
  52.     private Example example; 
  53.     public TheThread2(Example example) 
  54.     { 
  55.         this.example = example; 
  56.     } 
  57.     public void run() 
  58.     { 
  59.         this.example.excute2(); 
  60.     } 


将顺序打印,先excute1:0-19,然后excute2:0-19

如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。

如果将main()主方法改为:

[java] view plain copy print ?
  1. public static void main(String[] args) 
  2.     { 
  3.         Example example = new Example(); 
  4.         Thread t1 = new TheThread(example); 
  5.         example = new Example(); 
  6.         Thread t2 = new TheThread2(example); 
  7.          
  8.         t1.start(); 
  9.         t2.start(); 
  10.     } 


将乱序。

如果将Example的两个方法都加上static关键字,这样定义

public synchronized static void excute()

public synchronized static void excute2()

则将顺序打印

如果第一个的static去掉,即一个是非静态的,一个是静态的,则乱序

      如果某个synchronized方法是static的,那么当线程访问该方法时,他锁定的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为java中无论一个类有多少个对象,这些对象会对应唯一一个class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。

3、synchronized的代码块

[java] view plain copy print ?
  1. class Example 
  2.     public  void excute() 
  3.     { 
  4.         Object obj = new Object(); 
  5.         synchronized (obj){          //synchronized代码块定义 
  6.         for(int i = 0;i<20;i++) 
  7.         { 
  8.             try 
  9.             { 
  10.                 Thread.sleep(200); 
  11.             } catch (InterruptedException e) 
  12.             { 
  13.                 e.printStackTrace(); 
  14.             } 
  15.             System.out.println("example1:"+i); 
  16.         } 
  17.         } 
  18.     } 
  19.      
  20.     public synchronized static void excute2() 
  21.     { 
  22.         for(int i = 0;i<20;i++) 
  23.         { 
  24.             try 
  25.             { 
  26.                 Thread.sleep(500); 
  27.             } catch (InterruptedException e) 
  28.             { 
  29.                 e.printStackTrace(); 
  30.             } 
  31.             System.out.println("example2:"+i); 
  32.         } 
  33.     } 


synchronized块,写法:

synchronized(object)

{

}

表示线程在执行的时候会对obj对象上锁。

这相当于定义了一个标志,用这个标志是否上锁来控制代码块的执行,如果将obj换为this,就是对Example对象的锁定,相当于在方法上的synchronized声明。

synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。

当synchronized方法执行完或发生异常时,会自动释放锁。被synchronized保护的数据应该是私有(private)的。

4、怎样实现同步:线程间的相互作用:

死锁(dead lock)

Object类的wait 和 notify方法:

wait方法将释放对象锁,然后等待另一个线程调用这个对象的notify()或notifyAll()方法来唤醒方法,唤醒后不是立即继续执行,而是先获得对象锁,获得后在执行。

具有wait()和notify()的线程状态图:

43、多线程同步及synchronized关键字_第1张图片

5、举例:对一个对象Sample中的成员变量进行增减操作,使结果为0和1交互出现。

[java] view plain copy print ?
  1. public class Sample 
  2.     private int number; 
  3.      
  4.     public synchronized void inCrease() 
  5.     { 
  6.         if(0 != number) 
  7.         { 
  8.             try 
  9.             { 
  10.                 wait(); 
  11.             } catch (InterruptedException e) 
  12.             { 
  13.                 e.printStackTrace(); 
  14.             } 
  15.         } 
  16.          
  17.         number++; 
  18.         System.out.println(number); 
  19.         notify(); 
  20.     } 
  21.      
  22.     public synchronized void deCrease() 
  23.     { 
  24.         if( 0 == number) 
  25.         { 
  26.             try 
  27.             { 
  28.                 wait(); 
  29.             } catch (InterruptedException e) 
  30.             { 
  31.                 e.printStackTrace(); 
  32.             } 
  33.         } 
  34.          
  35.         number--; 
  36.         System.out.println(number); 
  37.         notify(); 
  38.     } 
  39.  
  40.  
  41. public class IncreaseThread extends Thread 
  42.     private Sample sample; 
  43.      
  44.     public IncreaseThread(Sample sample) 
  45.     { 
  46.         this.sample = sample; 
  47.     } 
  48.      
  49.     @Override 
  50.     public void run() 
  51.     { 
  52.         for(int i = 0; i < 10; i++) 
  53.         { 
  54.             try 
  55.             { 
  56.                 Thread.sleep((long)(Math.random() * 1000)); 
  57.             } catch (InterruptedException e) 
  58.             { 
  59.                 e.printStackTrace(); 
  60.             } 
  61.             sample.inCrease(); 
  62.         }        
  63.     } 
  64.  
  65.  
  66. public class DecreaseThread extends Thread 
  67.     private Sample sample; 
  68.      
  69.     public DecreaseThread(Sample sample) 
  70.     { 
  71.         this.sample = sample; 
  72.     } 
  73.      
  74.     @Override 
  75.     public void run() 
  76.     { 
  77.         for(int i = 0; i < 10; i++) 
  78.         { 
  79.             try 
  80.             { 
  81.                 Thread.sleep((long)(Math.random() * 1000)); 
  82.             } catch (InterruptedException e) 
  83.             { 
  84.                 e.printStackTrace(); 
  85.             } 
  86.             sample.deCrease(); 
  87.         }        
  88.     } 
  89.  
  90.  
  91. public class MainTest 
  92.     public static void main(String[] args) 
  93.     { 
  94.         Sample sample = new Sample(); 
  95.          
  96.         Thread t1 = new IncreaseThread(sample); 
  97.         Thread t2 = new DecreaseThread(sample); 
  98.          
  99.         t1.start(); 
  100.         t2.start(); 
  101.     } 


对于两个线程,像上面的程序,结果正确,如果再增加几个线程,如:

[java] view plain copy print ?
  1. public class MainTest 
  2.     public static void main(String[] args) 
  3.     { 
  4.         Sample sample = new Sample(); 
  5.          
  6.         Thread t1 = new IncreaseThread(sample); 
  7.         Thread t2 = new DecreaseThread(sample); 
  8.         Thread t3 = new IncreaseThread(sample); 
  9.         Thread t4 = new DecreaseThread(sample); 
  10.         t1.start(); 
  11.         t2.start(); 
  12.         t3.start(); 
  13.         t4.start(); 
  14.     } 


结果错误,出现的原因一种可能是因为当number为0时,进入inCrease方法,当判断number为0,要执行number++时,执行了线程切换,切换到另一个线程,这个线程也执行inCrease方法,因为这时number为0,所以执行了number++,这时number为1,线程执行完毕,调用notify(),结果又唤醒了原来的inCrease线程,原线程接着wait()之后执行,执行number++操作,导致number变为2,出现错误,具体解决方法就是在线程唤醒后,应该再次检查number的值进行判断,所以将if判断改为while判断:

[java] view plain copy print ?
  1. public class Sample 
  2.     private int number; 
  3.      
  4.     public synchronized void inCrease() 
  5.     { 
  6.         while(0 != number)        //改为while 
  7.         { 
  8.             try 
  9.             { 
  10.                 wait(); 
  11.             } catch (InterruptedException e) 
  12.             { 
  13.                 e.printStackTrace(); 
  14.             } 
  15.         } 
  16.          
  17.         number++; 
  18.         System.out.println(number); 
  19.         notify(); 
  20.     } 
  21.      
  22.     public synchronized void deCrease() 
  23.     { 
  24.         while( 0 == number)        //改为while 
  25.         { 
  26.             try 
  27.             { 
  28.                 wait(); 
  29.             } catch (InterruptedException e) 
  30.             { 
  31.                 e.printStackTrace(); 
  32.             } 
  33.         } 
  34.          
  35.         number--; 
  36.         System.out.println(number); 
  37.         notify(); 
  38.     } 

      wait与notify方法都是定义在Object类中,而且是final的,因此会被所有的java类所继承并且无法重写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或块当中。当线程执行了wait方法时,它会释放掉对象的锁。

6、Thread类的sleep方法:另一个会导致线程暂停的方法就是Thread类的sleep方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。

你可能感兴趣的:(多线程,java开发工具)