实现Java线程同步的五种方法

线程同步概念
Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不明确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
一、 同步方法
使用synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用关键字修饰此方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则该线程就处于阻塞状态。
代码如:public synchronized void save(){}
注:synchronized关键字也可以静态方法,此时如果调用该静态方法,将会锁住整个类。

二、同步代码块
即有synchronized关键字修饰的语句块。被关键字修饰的的语句块会自动加上内置锁,从而实现同步。
代码如:synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要去同步整个方法,使用关键字synchronized修饰关键代码即可。

实例:

public class SynchronizedThread {
      public static void main(String[] args) {
            SynchronizedThread sThread=new SynchronizedThread();
            sThread.useThread();

      }
   public void useThread(){
           Bank bank=new Bank();
           SaveBankThread savebankThread=new SaveBankThread(bank);

            Thread thread1=new Thread(savebankThread);
            thread1.start();

            RemoveBankThread removeBankThread=new RemoveBankThread(bank);

            Thread thread2=new Thread(removeBankThread);
            thread2.start();
   }
}   
class Bank{
      private int count=0;

      public void getCount(){
            System.out.println("账户余额为:"+count);
      }
      /**
       * 同步方法实现存钱
       * @param money
       */
      public synchronized void save(int money){
            count+=money;
            System.out.println(System.currentTimeMillis()+"存进:"+money);
      }
      /**
       * 同步代码块实现取钱
       * @param money
       */
      public synchronized void remove(int money){
            if (count-money<0) {
                  System.err.println("余额不足。");
                  return;
            }
            /*synchronized(Bank.class){*/
                  count-=money;
                  System.err.println(System.currentTimeMillis()+"取出:"+money);
            //}
      }
}
class SaveBankThread implements Runnable{
      private Bank bank;

      public SaveBankThread(Bank bank){
            this.bank=bank;
      }

      public void run() {
               while(true){
                  try {
                        Thread.sleep(1000);
                  } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                  }
                  bank.save(100);
                  bank.getCount();
                  System.out.println("\n");
               }
      }
 }
class RemoveBankThread implements Runnable{
      private Bank bank;

      public RemoveBankThread(Bank bank){
            this.bank=bank;
      }
      public void run() {

             while(true){

                  bank.remove(100);
                  bank.getCount();
                  System.out.println("\n");
                  try {
                        Thread.sleep(1000);
                  } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                  }
            }
      }
}

三、使用特殊域变量修饰符volatile实现线程同步
(1)、volatile关键字为域变量的访问提供了一种免锁机制
(2)、使用volatile修饰域变量相当于告诉虚拟机可能会被其他线程更新,因此每次使用该域变量都要同步到内存,从内存中读取,而不是直接使用寄存器中的值。
(3)、volatile不会提供原子操作,他不能用来修饰final类型的变量。
(4)、volatile只对域变量起作用,并不能保证线程安全。
上述实例:只需将count设为volatile修饰,就可以实现同步:

class Bank{
      /**
       * volatile实现
       */
      private volatile int count=0;

      public void getCount(){
            System.out.println("账户余额为:"+count);
      }
      /**
       * 同步方法实现存钱
       * @param money
       */
      public  void save(int money){
            count+=money;
            System.out.println(System.currentTimeMillis()+"存进:"+money);
      }
      /**
       * 同步代码块实现取钱
       * @param money
       */
      public  void remove(int money){
            if (count-money<0) {
                  System.err.println("余额不足。");
                  return;
            }
            /*synchronized(Bank.class){*/
                  count-=money;
                  System.err.println(System.currentTimeMillis()+"取出:"+money);
            //}
      }
}

注:多线程中的非同步问题主要出现在对域的都写上,如果域自身避免这个问题,那么就不需要修改操作该域的方法。用final域,有锁保护的域可以避免非同步的问题。

四、使用重入锁实现线程同步
在jdk1.5以后新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁。它与synchronized修饰的方法具有相同的基本行为与语义,并且扩展了其能力。
ReentrantLock类的常用方法有:
(1)、ReentrantLock():创建一个ReentrantLock实例。
(2)、lock():获得锁
(3)、unlock():释放锁

上述代码可以修改为:

class Bank{
      /**
       * volatile实现
       */
      private  int count=0;
      /**
       * 使用可重入锁
       */
      private Lock lock=new ReentrantLock();

      public void getCount(){
            System.out.println("账户余额为:"+count);
      }
      /**
       * 同步方法实现存钱
       * @param money
       */
      public void save(int money){
            lock.lock();
            try {
                  count+=money;
                  System.out.println(System.currentTimeMillis()+"存进:"+money);
            } catch (Exception e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }finally {
                  lock.unlock();//释放锁
            }
      }
      /**
       * 同步代码块实现取钱
       * @param money
       */
      public  void remove(int money){
            if (count-money<0) {
                  System.err.println("余额不足。");
                  return;
            }
                lock.lock();
                  try {
                        count-=money;
                        System.err.println(System.currentTimeMillis()+"取出:"+money);
                  } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                  }finally {
                        lock.unlock();
                  }

      }
}

注:
1、ReentrantLock()还可以通过public ReentrantLock(boolean fair)构造方法创建公平锁,即,优先运行等待时间最长的线程,这样大幅度降低程序运行效率。
2、关于Lock对象和synchronized关键字的选择:
(1)、最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。
(2)、如果synchronized关键字能够满足用户的需求,就用synchronized,他能简化代码。
(3)、如果需要使用更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally中释放锁。

五、使用ThreadLocal管理局部变量实现线程同步
ThreadLocal管理变量,则每一个使用该变量的线程都获得一个该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的副本,而不会对其他线程产生影响。
ThreadLocal类常用的方法:
1、get():返回该线程局部变量的当前线程副本中的值。
2、initialValue():返回此线程局部变量的当前线程的”初始值“。
3、remove():移除此线程局部变量当前线程的值。
4、set(T value):将此线程局部便利啊ing的当前线程副本中的值设置为指定值value。
上述代码修改为:

class Bank{

      private static ThreadLocal count=new ThreadLocal(){
            @Override
            protected Integer initialValue() {
                  // TODO Auto-generated method stub
                  return 0;
            }

      };


      public void getCount(){
            System.out.println("账户余额为:"+count.get());
      }
      /**
       * 局部变量实现存钱
       * @param money
       */
      public void save(int money){
            count.set(count.get()+money);
            System.out.println(System.currentTimeMillis()+"存进出:"+money);
      }
      /**
       * 局部变量实现取钱
       * @param money
       */
      public  void remove(int money){
            if (count.get()-money<0) {
                  System.err.println("余额不足。");
                  return;
            }
            count.set(count.get()-money);
            System.err.println(System.currentTimeMillis()+"取出:"+money);
      }
}

注:ThreadLocal与同步机制:
(1)、ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
(2)、前者采用以”空间换时间”的方法,后者采用”时间换空间”

你可能感兴趣的:(java)