美文网首页
Java编程-多线程同步

Java编程-多线程同步

作者: Sammy_ao | 来源:发表于2017-09-23 17:40 被阅读0次

    同步的需求

    例如你写了一个金融类程序,使用取钱/存钱这一对操作来表示金融交易。在这个程序里,一个线程执行取钱操作,另一个线程负责存钱操作。每一个线程操作着一对代表着金融交易的名字和金额的共享变量、类和实例域变量。对于一个合法的交易,每一个线程都必须在下一个线程操作之前完成对变量name和mount的分配。下面的例子展示了为什么需要同步。

    NeedForSynchronizationDemo.java

    // NeedForSynchronizationDemo.java
    public class NeedForSynchronizationDemo
    {
        public static void main(String[] args)
        {
            FinTrans ft = new FinTrans();
            FinTransThread depositThread = new FinTransThread(ft, "Deposit Thread");
            FinTransThread withdrawalThread = new FinTransThread(ft, "Withdrawal Thread");
            depositThread.start();
            withdrawalThread.start();
        }
    }
    
    class FinTrans
    {
        public static String transName;
        public static int transAmount;
    }
    
    class FinTransThread extends Thread
    {
        private FinTrans ft;
    
        public FinTransThread(FinTrans ft, String threadName)
        {
            super(threadName);
            this.ft = ft;
        }
    
        @Override 
        public void run()
        {
            if (getName().equals("Deposit Thread"))
            {
                ft.transName = "Deposit";
                try
                {
                    Thread.sleep((int)(Math.random() * 1000));
                }
                catch (InterruptedException e)
                {
    
                }
                ft.transAmount = 2000;
                System.out.println(ft.transName + " " + ft.transAmount);
            }
            else
            {
                ft.transName = "Withdrawal";
                try
                {
                    Thread.sleep((int)(Math.random() * 1000));
                }
                catch (InterruptedException e)
                {
    
                }
                ft.transAmount = 250;
                System.out.println(ft.transName + " " + ft.transAmount);
            }
        }
    }
    

    我们可能期望输出结果为:

    Deposit 2000
    Withdrawal 250 
    

    但是结果可能是以下的几种组合

    Withdrawal 250.0
    Withdrawal 2000.0
    Deposit 2000.0
    Deposit 250.0
    

    Java同步机制

    Java的同步机制可以避免多于一个线程在同一时间执行同一段关键代码。Java的同步机制基于监听(monitor)和锁(lock)的概念。将monitor想象成一个保护装置,它保护着一段关键代码,而lock是通过保护装置的一个途径。意思是:当一个线程想要访问受保护装置保护的代码时,这个线程必须取得一个与这个monitor相关联的锁(每一个对象都有它自己的锁)。如果其他线程持有这个锁,那么JVM强制让请求的线程等待直到锁被释放。JVM提供了monitorentermonitorexit指令,但是我们不必使用这种低等级的方法。我们可以使用synchronized关键字和对应的synchronized语句。

    Synchronized语句

    synchronized语句以synchronized关键字开始。sync object可以看做锁,而下面的代码块可以看做monitor保护的关键代码。

    synchronized ("sync object")
    {
       // Access shared variables and other shared resources
    }
    

    SynchronizationDemo.java

    // SynchronizationDemo.java
    public class SynchronizationDemo
    {
        public static void main(String[] args)
        {
            FinTrans ft = new FinTrans();
            FinTransThread depositThread = new FinTransThread(ft, "Deposit Thread");
            FinTransThread withdrawalThread = new FinTransThread(ft, "Withdrawal Thread");
            depositThread.start();
            withdrawalThread.start();
        }
    }
    
    class FinTrans
    {
        public static String transName;
        public static int transAmount;
    }
    
    class FinTransThread extends Thread
    {
        private FinTrans ft;
    
        public FinTransThread(FinTrans ft, String threadName)
        {
            super(threadName);
            this.ft = ft;
        }
    
        @Override 
        public void run()
        {
            for (int i = 0; i < 100; i++)
            {
                if (getName().equals("Deposit Thread"))
                {
                    synchronized(ft)
                    {
                        ft.transName = "Deposit";
                        try
                        {
                            Thread.sleep((int)(Math.random() * 1000));
                        }
                        catch (InterruptedException e)
                        {
    
                        }
                        ft.transAmount = 2000;
                        System.out.println(ft.transName + " " + ft.transAmount);    
                    }
    
                }
                else
                {
                    synchronized(ft)
                    {
                        ft.transName = "Withdrawal";
                        try
                        {
                            Thread.sleep((int)(Math.random() * 1000));
                        }
                        catch (InterruptedException e)
                        {
    
                        }
                        ft.transAmount = 250;
                        System.out.println(ft.transName + " " + ft.transAmount);
                    }
    
                }
            }
    
        }
    }
    

    Tips:如果想要知道线程是否获得了给定对象的锁,可以调用Thread类的holdsLock(Object o)方法。

    让方法同步

    过度的使用synchronized会导致代码运行的极为低效。例如,你的程序的一个方法里面存在连续的两个synchronized语段,每个synchronized代码段都尝试获取同一个对象的关联锁。由于获取和释放资源都需要消耗时间,重复地调用这个方法会降低程序的性能。
    当一个实例或类的方法被冠以synchronized关键字时,这个方法称为同步的方法。例如:synchronized void print(String s)。当你对一个实例的方法进行同步时,每次调用这个方法都需要获得与该实例相关联的锁。

    SynchronizationDemo2.java

    //SynchronizationDemo2.java
    public class SynchronizationDemo2
    {
        public static void main(String[] args)
        {
            FinTrans ft = new FinTrans();
            FinTransThread depositThread = new FinTransThread(ft, "Deposit Thread");
            FinTransThread withdrawalThread = new FinTransThread(ft, "Withdrawal Thread");
            depositThread.start();
            withdrawalThread.start();
        }
    }
    
    class FinTrans
    {
        public static String transName;
        public static int transAmount;
    
        synchronized public void update(String transName, int transAmount)
        {
            this.transName = transName;
            this.transAmount = transAmount;
            System.out.println(transName + " " + transAmount);
        }
    }
    
    class FinTransThread extends Thread
    {
        private FinTrans ft;
    
        public FinTransThread(FinTrans ft, String threadName)
        {
            super(threadName);
            this.ft = ft;
        }
    
        @Override 
        public void run()
        {
            for (int i = 0; i < 100; i++)
            {
                if (getName().equals("Deposit Thread"))
                {
                    ft.update("Deposit Thread", 2000);
                }
                else
                {
                    ft.update("Withdrawal Thread", 250);
                }
            }
    
        }
    }
    

    类的方法也能被同步,一些程序混淆了同步的实例方法和类方法。以下两点需要注意:

    1. 对象锁和类锁之间并不关联。他们是不同的实体。获取和释放每个锁都是相互独立的。一个同步的实例方法调用一个同步的类方法两种锁都需要。首先,同步的实例方法需要对应实例的锁,同时,实例方法需要类方法的锁。
    2. 同步的类方法可以调用一个对象的同步方法。同步的类方法调用一个对象的同步方法也需要两个锁。

    LockTypes.java

    // LockTypes.java
    class LockTypes
    {
       // Object lock acquired just before execution passes into instanceMethod()
       synchronized void instanceMethod ()
       {
          // Object lock released as thread exits instanceMethod()
       }
       // Class lock acquired just before execution passes into classMethod()
       synchronized static void classMethod (LockTypes lt)
       {
          lt.instanceMethod ();
          // Object lock acquired just before critical code section executes
     
          synchronized (lt)
          {
             // Critical code section
             // Object lock released as thread exits critical code section
          }
          // Class lock released as thread exits classMethod() 
       }
    }
    

    同步失效

    当一个线程自愿或非自愿地离开了临界代码,它会释放锁以便其他线程能获得。假设两个线程想要进入同一段临界区。为了避免线程同时进入同一个临界区,每一个线程必须尝试去取得同一个锁。如果每一个线程尝试去获得不同的锁而且成功了,两个线程都会进入临界区,一个线程无须等待另一个线程结束,因为他们拥有的是两个不同的锁。

    NoSynchronizationDemo.java

    // NoSynchronizationDemo.java
    class NoSynchronizationDemo
    {
       public static void main (String [] args)
       {
          FinTrans ft = new FinTrans ();
          TransThread tt1 = new TransThread (ft, "Deposit Thread");
          TransThread tt2 = new TransThread (ft, "Withdrawal Thread");
          tt1.start ();
          tt2.start ();
       }
    }
    class FinTrans
    {
       public static String transName;
       public static double amount;
    }
    class TransThread extends Thread
    {
       private FinTrans ft;
       TransThread (FinTrans ft, String name)
       {
          super (name); // Save thread's name
          this.ft = ft; // Save reference to financial transaction object
       }
       public void run ()
       {
          for (int i = 0; i < 100; i++)
          {
               if (getName ().equals ("Deposit Thread"))
               {
                   synchronized (this)
                   {
                      ft.transName = "Deposit";
                      try
                      {
                         Thread.sleep ((int) (Math.random () * 1000));
                      }
                      catch (InterruptedException e)
                      {
                      }
                      ft.amount = 2000.0;
                      System.out.println (ft.transName + " " + ft.amount);
                   }
               }
               else
               {
                   synchronized (this)
                   {
                      ft.transName = "Withdrawal";
                      try
                      {
                         Thread.sleep ((int) (Math.random () * 1000));
                      }
                      catch (InterruptedException e)
                      {
                      }
                      ft.amount = 250.0;
                      System.out.println (ft.transName + " " + ft.amount);
                   }
               }
          }
       }
    }
    

    由于this是对当前线程对象的引用,所以上述代码会导致同步失效。

    死锁

    在一些程序里面,下面的场景可能会发生。线程A获得了一个锁,线程B也需要这个锁来进入自己的临界区。相同的,线程B也获得一个锁,线程A需要这个锁来进入自己的临界区。由于没有一个线程获得它们需要的锁,每个线程必须等待以获得锁,此外,由于没有线程能够继续执行以释放对方所需要的锁,程序就会进去死锁状态。

    DeadlockDemo.java

    // DeadlockDemo.java
    class DeadlockDemo
    {
       public static void main (String [] args)
       {
          FinTrans ft = new FinTrans ();
          TransThread tt1 = new TransThread (ft, "Deposit Thread");
          TransThread tt2 = new TransThread (ft, "Withdrawal Thread");
          tt1.start ();
          tt2.start ();
       }
    }
    class FinTrans
    {
       public static String transName;
       public static double amount;
    }
    class TransThread extends Thread
    {
       private FinTrans ft;
       private static String anotherSharedLock = "";
       TransThread (FinTrans ft, String name)
       {
          super (name); // Save thread's name
          this.ft = ft; // Save reference to financial transaction object
       }
       public void run ()
       {
          for (int i = 0; i < 100; i++)
          {
               if (getName ().equals ("Deposit Thread"))
               {
                   synchronized (ft)
                   {
                      synchronized (anotherSharedLock)
                      {
                         ft.transName = "Deposit";
                         try
                         {
                            Thread.sleep ((int) (Math.random () * 1000));
                         }
                         catch (InterruptedException e)
                         {
                         }
                         ft.amount = 2000.0;
                         System.out.println (ft.transName + " " + ft.amount);
                      }
                   }
               }
               else
               {
                   synchronized (anotherSharedLock)
                   {
                      synchronized (ft)
                      {
                         ft.transName = "Withdrawal";
                         try
                         {
                            Thread.sleep ((int) (Math.random () * 1000));
                         }
                         catch (InterruptedException e)
                         {
                         }
                         ft.amount = 250.0;
                         System.out.println (ft.transName + " " + ft.amount);
                      }
                   }
               }
          }
       }
    }
    

    Tip:为了避免死锁,我们必须仔细分析代码当中是否存在线程之间的锁依赖问题。

    相关文章

      网友评论

          本文标题:Java编程-多线程同步

          本文链接:https://www.haomeiwen.com/subject/dipcextx.html