【J2SE快速进阶】——多线程之synchronized

来源:互联网 发布:醉玲珑小说网络点击量 编辑:程序博客网 时间:2024/05/29 09:29

         

        我和老婆去银行取钱          

        有一天,和老婆打了个赌,现在我的银行账号里共有5000块钱,我们去银行同时取钱,看我俩能不能同时取出5000来。。。。(PS:打赌的代价是:如果都能取出5000,那这10000块都给她买吃的!如果只能取5000,嘿嘿,那就只用着5000块给她买吃的~~~~怎么觉得这条件有点怪怪的nie?)

        心动不如行动!她拿着存折去柜台取,我拿着银行卡去ATM机取,找了个合适的时机,我在输入好金额时,一直盯着那个teller的手,在一切准备就绪后敲回车的同时,我以迅雷不及掩耳之势按下了确定。结果是,我的ATM机唰唰唰吐出50张毛爷爷,老婆那边也得意洋洋的拿着5000块钱过来了。。。。
        “喂~醒醒了!”

        原来是在做梦~~不行,这可能行得通,我得试试!别不相信,我还用刚学的线程的知识验证了一下呢!      

public class TestDrawMoney implements Runnable {//说明:此例的背景是【多人】【同时】在【同一账户上】取款//在统一账户danny的账户上取款Depositor danny = new Depositor();public static void main(String[] args) {TestDrawMoney test=new TestDrawMoney();//我取款的操作是一个线程draw1,我老婆取款的操作也是一个线程draw2Thread draw1 = new Thread(test);Thread draw2 = new Thread(test);draw1.setName("我");draw2.setName("我老婆");//启动线程draw1.start();draw2.start();}    //重写run方法public void run() {danny.DrawMoney(5000);}}//储户类class Depositor {//余额private static int deposit = 5000;//取钱方法public void  DrawMoney(int money) {if (money <= deposit) {// 模拟银行吐钱过程System.out.println(Thread.currentThread().getName() + "取款时:成功取款!" + money + "元!");// 模拟账户扣款过程deposit = deposit - money;}else{System.out.println(Thread.currentThread().getName()+"取款时:余额不足!");}}}

        如果银行取款的程序跟上面相似的话,那么其中一个线程(比如我老婆取钱)运行到“账户扣款”之前时,资源可能被另一个线程(比如我取钱)抢过来运行,这时,第一个线程因为已经判断账户余额充足,而且已经把钱给了我老婆,但是银行扣款运行之前,我取钱这个线程抢先执行,银行还没有扣款,所以在我取钱这个线程中,判断余额也是充足的。
       您猜测试结果怎么着?我俩还真都取出了5000!

       

          

       可惜学习了的线程中锁的机制后,这个发财梦被彻底打破了。。。。

public class TestDrawMoney implements Runnable {// 说明:此例的背景是【多人】【同时】在【同一账户上】取款// 在统一账户danny的账户上取款Depositor danny = new Depositor();public static void main(String[] args) {TestDrawMoney test = new TestDrawMoney();// 我取款的操作是一个线程draw1,我老婆取款的操作也是一个线程draw2Thread draw1 = new Thread(test);Thread draw2 = new Thread(test);draw1.setName("我");draw2.setName("我老婆");// 启动线程draw1.start();draw2.start();}// 重写run方法public void run() {danny.DrawMoney(5000);}}// 储户类class Depositor {// 余额private static int deposit = 5000;// 取钱方法public void DrawMoney(int money) {synchronized (this) {//这里为整个线程的操作加上了锁if (money <= deposit) {// 模拟银行吐钱过程System.out.println(Thread.currentThread().getName() + "取款时:成功取款!" + money + "元!");// 模拟账户扣款过程deposit = deposit - money;} else {System.out.println(Thread.currentThread().getName+"取款时:余额不足!");}}}}

          跟第一个例子唯一不同之处,就是在取钱方法DrawMoney()中加了锁——synchronized,这样,当一个线程运行此方法时,这个对象就会被锁定,其他线程就无法执行此方法中被锁定的代码。

       运行结果是,只能有一个人能取出钱来:

          或    (这两个线程,先执行的能够取出钱,但谁先谁后并不一定,所以会有两种结果。)


       synchronized介绍

       当多个线程可能在同时访问相同的资源时,就要考虑是否用synchronized。synchronized用来给对象、方法或者代码块加锁。同一时刻只能有一个线程可以执行被锁代码,其他任何线程都不会打断它,必须等到当前正在执行被锁代码的线程执行完毕,其他线程才可以执行。

       sychronized的实质是。在执行被锁方法或代码的过程中,锁定当前对象(上例中既然锁定了当前对象“danny”,那么肯定也锁定了当前对象中的静态变量“deposit”)。

       

       synchronized的用法

       1、用synchronized修饰方法,如上例中可以直接用synchronized修饰DrawMoney()的方法:    

public synchronized void DrawMoney(int money) {if (money <= deposit) {// 模拟银行吐钱过程System.out.println(Thread.currentThread().getName() + "取款时:成功取款" + money + "元!");// 模拟账户扣款过程deposit = deposit - money;} else {System.out.println(Thread.currentThread().getName() + "取款时:余额不足!");}}
            如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。

       如果两个线程想同时访问这个方法,那么需要在两个实例对象中访问。也就是说,不同对象实例中synchronized修饰的方法时互不干扰的。


         2、用synchronized锁住代码

       正如上文第二个例子,在方法中,用synchronized(this){ //代码片段}锁住同一时刻只允许一个线程访问的代码。同一时刻,其他线程不允许访问此对象的被锁代码段。即执行到此代码段时,此当前对象会被锁住,其他线程不允许访问,待当前线程执行完毕,其他线程才可以访问。


        synchronized修饰成员方法和synchronized(this)两种用法的含义是一样的。


        synchronized可能会带来的问题

        当把程序“锁”住时,还会带来其他问题——死锁。

        比如现在Danny和Maria两个人只有一双筷子,想吃大餐的他们只有抢到两只筷子才可以开吃:       

public class TestDeadLock2 implements Runnable {public String name;      //姓名static Object chopsticks1 = new Object();      //第一只筷子static Object chopsticks2 = new Object();      //第二只筷子public static void main(String[] args) {System.out.println("抢筷子开始!");TestDeadLock2 test1=new TestDeadLock2();TestDeadLock2 test2=new TestDeadLock2();test1.name="Danny";test2.name="Maria";Thread t1=new Thread(test1);Thread t2=new Thread(test2);t1.start();t2.start();}public void run() {if (name.equals("Danny")) {synchronized (chopsticks1) {try {Thread.sleep(500);//这里为了放大效果,让Danny抢到筷子1时停顿一下} catch (InterruptedException e) {e.printStackTrace();}synchronized (chopsticks2) {System.out.println("Danny成功抢到两只筷子,他要开吃啦!");}}} else if (name.equals("Maria")) {synchronized (chopsticks2) {try {Thread.sleep(500);//这里为了放大效果,让Maria抢到筷子2时停顿一下} catch (InterruptedException e) {e.printStackTrace();}synchronized (chopsticks1) {System.out.println("Maria成功抢到两只筷子,她要开吃啦!");}}}}}
        例子中,Danny和Maria抢到每一只筷子时都会把筷子握在手里,当Danny抢到筷子1,Maria抢到筷子2时,他们都在等对方让出筷子,互不相让,无限等待下去,这就发生了死锁。


        所以,用锁需谨慎,涉及到同步时,一定要考虑这个线程是不是应该同步,加了同步,效率变低,不加同步,可能产生类似上例中数据不一致的现象。所以,在保证数据安全的情况下,同步区域(即被锁区域)越小越好。


3 0