在多线程程序中,难免会多个线程操纵一个共享变量,以模拟多个窗口共同售票为例:
public class Demo { private int num=10; //未加synchronized修饰的sell方法 public synchronized void sell(){ if(num==0){ return; } num--; System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+num+"张"); } class window implements Runnable{ @Override public void run() { // TODO Auto-generated method stub while(true){ sell(); try { Thread.sleep(1000*2); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) { Demo d = new Demo(); window w = d.new window(); new Thread(w,"窗口1").start(); new Thread(w,"窗口2").start(); new Thread(w,"窗口3").start(); new Thread(w,"窗口4").start(); }
输出结果:
窗口1卖出一张票,剩余6张
窗口4卖出一张票,剩余7张
窗口3卖出一张票,剩余8张
窗口2卖出一张票,剩余5张
窗口4卖出一张票,剩余3张
窗口3卖出一张票,剩余3张
窗口1卖出一张票,剩余4张
窗口4卖出一张票,剩余1张
窗口1卖出一张票,剩余0张
窗口2卖出一张票,剩余1张
窗口3卖出一张票,剩余1张
很明显这不是我们想要的结果 导致这种结果的原因就是没有对sell方法加锁造成的
举个例子,int i=10; 开启两个进程 进程1和进程2 同时执行i=i-1操作并输出,
假设进程1先开始运行
进程1运行过程中,首先会先把i读取到内存当中,然后cpu从内存中读取i,执行i-1,最后将结果i=9刷新到内存中. 在整个过程中,某一时间段(cpu进行计算后,将结果返回内存前)存在两个i值,即内存中i=10 cpu中i=9;
这个时候若进程2读取i的值,读取的是内存中i的值 10,并不是进程1操作了i的结果值 9,因为此时进程1还未将计算结果刷新到内存;这便导致了明明执行了两次i-1操作结果却i=9;
为了避免这种情况,我们要对sell方法加锁(synchronized)
打个比方,一群人(多线程)去参观一座庄园(一个实体类的对象),里面有很多房子(方法),有的房子有锁(加了synchronized关键字),有的无锁(未加synchronized关键字),无锁的房子可随意由大家参观,而有锁的房子在门口放了把钥匙,当有人(进程)想进入房间(执行该方法),他首先会用钥匙打开房门然后带着钥匙进入房间,这样便不会有人进去打扰他参观,当他参观完后
走出房间将钥匙放在外面留给下一个想要参观的人(需要执行该方法的其他进程).
如此便避免了多个线程对共享资源操作发生冲突
让我们看一下synchronized修饰了方法之后的运行结果
//加了synchronized修饰的sell方法 public synchronized void sell(){ if(num==0){ return; } num--; System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+num+"张"); }
运行结果:
窗口1卖出一张票,剩余9张
窗口4卖出一张票,剩余8张
窗口3卖出一张票,剩余7张
窗口2卖出一张票,剩余6张
窗口3卖出一张票,剩余5张
窗口2卖出一张票,剩余4张
窗口4卖出一张票,剩余3张
窗口1卖出一张票,剩余2张
窗口4卖出一张票,剩余1张
窗口2卖出一张票,剩余0张
这才是我们想要的结果;
synchronized如果用来修饰方法,作用范围则是调用这个方法的对象 若按照下述方法开启进程 则依然是错误的结果 <pre name="code" class="java">public static void main(String[] args) { Demo d = new Demo(); new Thread(d.new window(),"窗口1").start(); new Thread(d.new window(),"窗口2").start(); new Thread(d.new window(),"窗口3").start(); new Thread(d.new window(),"窗口4").start(); }
原因便是在开启线程时 每一次都new了一个window对象,这相当于是四个对象的四个进程,有四把锁,所以这样开启线程加不加synchronized修饰方法都是一样的