1. 程式人生 > >Java多線程簡析——Synchronized(同步鎖)、Lock以及線程池

Java多線程簡析——Synchronized(同步鎖)、Lock以及線程池

ati auto bsp lock eas 根據 引入 封裝 util

Java多線程

Java中,可運行的程序都是有一個或多個進程組成。進程則是由多個線程組成的。
最簡單的一個進程,會包括mian線程以及GC線程。

線程的狀態

線程狀態由以下一張網上圖片來說明:

技術分享

在圖中,紅框標識的部分方法,可以認為已過時,不再使用。
(1)wait、notify、notifyAll是線程中通信可以使用的方法。線程中調用了wait方法,則進入阻塞狀態,只有等另一個線程調用與wait同一個對象的notify方法。
這裏有個特殊的地方,調用wait或者notify,前提是需要獲取鎖,也就是說,需要在同步塊中做以上操作。
(2)join方法。該方法主要作用是在該線程中的run方法結束後,才往下執行。如以下代碼:

public class ThreadJoin {  

    public static void main(String[] args) {  
  
        Thread thread= new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.err.println("線程"+Thread.currentThread().getId()+" 打印信息");  
            }  
        });  
        thread.start();  
          
        
try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.err.println("主線程打印信息"); } }

該方法顯示的信息是:
線程8 打印信息
主線程打印信息

如果去掉其中的join方法,則顯示如下:

主線程打印信息
線程8 打印信息
(3)yield方法。這個是線程本身的調度方法,使用時你可以在run方法執行完畢時,調用該方法,告知你已可以出讓內存資源。
其他的線程方法,基本都會在日常中用到,如start、run、sleep,這裏就不再介紹。

Synchronized(同步鎖)

在Java中使用多線程,你就不能繞過同步鎖這個概念。這在多線程中是十分重要的。
在Java多線程的使用中,你必然會遇到一個問題:多個線程共享一個或者一組資源,這資源包括內存、文件等。
很常見的一個例子是,張三在銀行賬戶存有9999元,經過多次的取100,存100後,賬戶還有多少錢?
看代碼:
以下表示賬戶信息:

import java.sql.Time;  
import java.util.concurrent.TimeUnit;  
  
public class Account {  
  
    private String name;  
    private float amt;  
    public Account(String name,float amt) {  
        this.name=name;  
        this.amt=amt;  
    }  
  
    public  void  increaseAmt(float increaseAmt){  
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        amt+=increaseAmt;  
    }  
      
    public  void decreaseAmt(float decreaseAmt){  
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        amt-=decreaseAmt;  
    }  
      
    public void printMsg(){  
        System.out.println(name+"賬戶現有金額為:"+amt);  
    }  
}  

以下是我們操作賬戶的方法:

final int NUM=100;  

Thread[] threads=new Thread[NUM];  
for(int i=0;i<NUM;i++){  
    if(threads[i]==null){  
        threads[i]=new Thread(new Runnable() {  
              
            @Override  
            public void run() {  
                account.increaseAmt(100f);  
                account.decreaseAmt(100f);  
            }  
        });  
        threads[i].start();  
    }  
}  
  
for(int i=0;i<NUM;i++){  
    try {  
        threads[i].join();  
    } catch (InterruptedException e) {  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
    }  
}  
  
account.printMsg();  

你會發現,每次打印出來的賬戶余額都不一定是一樣的。這就是同步鎖的必要性。
java中,提供了多種使用同步鎖的方式。

(1)對動態方法的修飾。
作用的是調用該方法的對象(或者說對象引用)。

public synchronized void doSomething(){}  

(2)對代碼塊的修飾。
作用的是調用該方法的對象(或者說對象引用)。

public void increaseAmt(float increaseAmt){  
          
    try {  
        TimeUnit.SECONDS.sleep(1);  
    } catch (InterruptedException e) {  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
    }  
    synchronized (this) {  
        System.out.println(this);  
        amt+=increaseAmt;  
    } 
    
}

(3)對靜態方法的修飾。
作用的是靜態方法所在類的所有對象(或者說對象引用)。

public synchronized static void increaseAmt(float increaseAmt){  
    try {  
        TimeUnit.SECONDS.sleep(1);  
    } catch (InterruptedException e) {  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
    }  
    amt+=increaseAmt;  
}

(4)對類的修飾。
作用的是靜態方法所在類的所有對象(或者說對象引用)。

synchronized (AccountSynchronizedClass.class) {  
    amt-=decreaseAmt;  
}

以修飾代碼塊的方式為例,我們重新運行以上代碼後,得到了正確的結果。代碼如下:

import java.util.concurrent.TimeUnit;  
/**  
 * Synchronized 代碼塊  
 * @author 戰國  
 *  
 */  
public class AccountSynchronizedBlock {  
  
    private String name;  
    private float amt;  
    public AccountSynchronizedBlock(String name,float amt) {  
        this.name=name;  
        this.amt=amt;  
    }  
  
    public  void  increaseAmt(float increaseAmt){  
          
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        synchronized (this) {  
            System.out.println(this);  
            amt+=increaseAmt;  
        }  
    }  
      
    public  void decreaseAmt(float decreaseAmt){  
        try {  
            TimeUnit.SECONDS.sleep(1);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        synchronized (this) {  
            System.out.println(this);  
            amt-=decreaseAmt;  
        }  
          
    }  
      
    public void printMsg(){  
        System.out.println(name+"賬戶現有金額為:"+amt);  
    }  
}  
//多線程synchronized修飾代碼塊 ,每次計算的值都一樣  
final AccountSynchronizedBlock account=new AccountSynchronizedBlock("張三", 9999.0f);  
final int NUM=50;  

Thread[] threads=new Thread[NUM];  
for(int i=0;i<NUM;i++){  
    if(threads[i]==null){  
        threads[i]=new Thread(new Runnable() {  
              
            @Override  
            public void run() {  
                account.increaseAmt(100f);  
                account.decreaseAmt(100f);  
            }  
        });  
        threads[i].start();  
    }  
}  

for(int i=0;i<NUM;i++){  
    try {  
        threads[i].join();  
    } catch (InterruptedException e) {  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
    }  
}  
account.printMsg();  

以上是同步鎖的簡單說明。
在JDK5中,Java又引入了一個相似的概念Lock,也就是鎖。功能與synchronized是類似的。

Lock

Lock對比synchronized有高手總結的差異如下:
總結來說,Lock和synchronized有以下幾點不同:

  1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;
  2)synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;
  3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;
  4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
  5)Lock可以提高多個線程進行讀操作的效率。

  在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

(參考http://www.cnblogs.com/dolphin0520/p/3923167.html)。
Lock的操作與synchronized相比,靈活性更高,而且Lock提供多種方式獲取鎖,有Lock、ReadWriteLock接口,以及實現這兩個接口的ReentrantLock類、ReentrantReadWriteLock類。
對Lock的簡單操作代碼如下:

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReadWriteLock;  
import java.util.concurrent.locks.ReentrantLock;  
import java.util.concurrent.locks.ReentrantReadWriteLock;  
  
public class LockImp {  
  
      
    private Lock lock=new ReentrantLock();  
    private ReadWriteLock rwLock=new ReentrantReadWriteLock();  
      
    private List<Integer> list=new ArrayList<Integer>();  
      
    public void doReentrantLock(Thread thread){  
        lock.lock();  
        System.out.println(thread.getName()+"獲取鎖");  
        try {  
              for(int i=0;i<10;i++){  
                    list.add(i);  
                }  
        } catch (Exception e) {  
              
        }finally{  
            lock.unlock();  
            System.out.println(thread.getName()+"釋放鎖");  
        }  
          
    }  
    public void doReentrantReadLock(Thread thread){  
        rwLock.readLock().lock();  
        System.out.println(thread.getName()+"獲取讀鎖");  
        try {  
            for(int i=0;i<10;i++){  
                list.add(i);  
            }  
        } catch (Exception e) {  
              
        }finally{  
            rwLock.readLock().unlock();  
            System.out.println(thread.getName()+"釋放讀鎖");  
        }  
          
    }  
    public void doReentrantWriteLock(Thread thread){  
        rwLock.writeLock().lock();  
        System.out.println(thread.getName()+"獲取寫鎖");  
        try {  
            for(int i=0;i<10;i++){  
                list.add(i);  
            }  
        } catch (Exception e) {  
              
        }finally{  
            rwLock.writeLock().unlock();  
            System.out.println(thread.getName()+"釋放寫鎖");  
        }  
          
    }  
      
      
      
    /**  
     * @param args  
     */  
    public static void main(String[] args) {  
  
        final LockImp lockImp=new LockImp();  
          
        final Thread thread1=new Thread();  
        final Thread thread2=new Thread();  
        final Thread thread3=new Thread();  
          
        new Thread(new Runnable() {  
              
            @Override  
            public void run() {  
                lockImp.doReentrantLock(thread1);  
            }  
        }).start();  
          
        new Thread(new Runnable() {  
                      
                    @Override  
                    public void run() {  
                        lockImp.doReentrantLock(thread2);  
                    }  
                }).start();  
          
        new Thread(new Runnable() {  
              
            @Override  
            public void run() {  
                lockImp.doReentrantLock(thread3);  
            }  
        }).start();  
      
          
        lockImp.doReentrantReadLock(thread1);  
        lockImp.doReentrantReadLock(thread2);  
        lockImp.doReentrantReadLock(thread3);  
          
        lockImp.doReentrantWriteLock(thread1);  
        lockImp.doReentrantWriteLock(thread2);  
        lockImp.doReentrantWriteLock(thread3);  
    }  
  
}  

Lock的使用中,務必需要lock、unlock同時使用,避免死鎖。

線程池的使用

為什麽使用線程池?
因為使用它有好處:(1)在界面上,簡化了寫法,代碼更簡潔(2)對程序中的線程可以進行適度的管理(3)有效較低了多個線程的內存占有率等。
這是一篇講述線程池非常好的文章:http://www.cnblogs.com/dolphin0520/p/3932921.html
如果對線程池有不了解的同學,可以參考鏈接中的文章,講的深入淺出。
在這裏只是簡單的封裝一個線程池的工具類,僅供參考:

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
  
public class ThreadPoolUtil {  
  
     private volatile static ThreadPoolUtil instance;  
     private ThreadPoolUtil(){}  
     private static ExecutorService threadPool;  
       
       
     public static ThreadPoolUtil getInstance(){  
         if(instance==null){  
             synchronized (ThreadPoolUtil.class) {  
                  instance=new ThreadPoolUtil();  
                 threadPool=Executors.newCachedThreadPool();  
            }  
         }  
         return instance;  
     }  
       
    public void excute(Runnable runnable){  
        threadPool.execute(runnable);  
    }  
      
    public void shutdown(){  
        threadPool.shutdown();  
    }  
      
    public boolean isActive(){  
        if(threadPool.isTerminated()){  
            return false;  
        }  
        return true;  
    }  
}  

轉載至 http://blog.csdn.net/yangzhaomuma/article/details/51236976

Java多線程簡析——Synchronized(同步鎖)、Lock以及線程池