Java 多線程同步和異步詳解

 

1)多線程併發時,多個線程同時請求同一個資源,必然致使此資源的數據不安全,A線程修改了B線
程的處理的數據,而B線程又修改了A線程處理的數理。顯然這是因爲全局資源形成的,有時爲了解
決此問題,優先考慮使用局部變量,退而求其次使用同步代碼塊,出於這樣的安全考慮就必須犧牲
系統處理性能,加在多線程併發時資源掙奪最激烈的地方,這就實現了線程的同步機制
同步:A線程要請求某個資源,可是此資源正在被B線程使用中,由於同步機制存在,A線程請求
不到,怎麼辦,A線程只能等待下去
異步:A線程要請求某個資源,可是此資源正在被B線程使用中,由於沒有同步機制存在,A線程
仍然請求的到,A線程無需等待
java

java中實現多線程
1)繼承Thread,重寫裏面的run方法
2)實現runnable接口web

比較推薦後者,第一,java沒有單繼承的限制第二,還能夠隔離代碼數組

2)線程池
要知道在計算機中任何資源的建立,包括線程,都須要消耗系統資源的。在WEB服務中,對於web服
務器的響應速度必需要儘量的快,這就容不得每次在用戶提交請求按鈕後,再建立線程提供服務
。爲了減小用戶的等待時間,線程必須預先建立,放在線程池中,線程池能夠用HashTable這種數
據結構來實現,看了Apach HTTP服務器的線程池的源代碼,用是就是HashTable,KEY用線程對象,
value 用ControlRunnable,ControlRunnable是線程池中惟一能幹活的線程,是它指派線程池中的
線程對外提供服務。出於安全考慮,Apach HTTP服務器的線程池它是同步的。安全

3)Java同步機制有4種實現方式:服務器

1.synchronized 多線程

同步方法 
    即有synchronized關鍵字修飾的方法。 
    因爲java的每一個對象都有一個內置鎖,當用此關鍵字修飾方法時, 
    內置鎖會保護整個方法。在調用該方法前,須要得到內置鎖,不然就處於阻塞狀態。
    代碼如: 
    public synchronized void save(){}併發

同步代碼塊 
    即有synchronized關鍵字修飾的語句塊。 
    被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
    代碼如: 
    synchronized(object){ 
    }
   注: synchronized關鍵字也能夠修飾靜態方法,此時若是調用該靜態方法,將會鎖住整個類dom

2.ThreadLocal
    ThreadLocal 保證不一樣線程擁有不一樣實例,相同線程必定擁有相同的實例,即爲每個使用該
變量的線程提供一個該變量值的副本,每個線程均可以獨立改變本身的副本,而不是與其它線程
的副本衝突。
優點:提供了線程安全的共享對象
與其它同步機制的區別:同步機制是爲了同步多個線程對相同資源的併發訪問,是爲了多個線程之
間進行通訊;而 ThreadLocal 是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源
,這樣固然不須要多個線程進行同步了。
3.volatile
     volatile 修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。
並且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。
    優點:這樣在任什麼時候刻,兩個不一樣的線程老是看到某個成員變量的同一個值。
    原因:Java 語言規範中指出,爲了得到最佳速度,容許線程保存共享成員變量的私有拷貝,而
且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。這樣當多個線程同時與某
個對象交互時,就必需要注意到要讓線程及時的獲得共享成員變量的變化。而 volatile 關鍵字就
是提示 VM :對於這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。
     使用技巧:在兩個或者更多的線程訪問的成員變量上使用 volatile 。當要訪問的變量已在
synchronized 代碼塊中,或者爲常量時,沒必要使用。
        線程爲了提升效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的
是B。只在某些動做時才進行A和B的同步,所以存在A和B不一致的狀況。volatile就是用來避免這種
狀況的。 volatile告訴jvm,它所修飾的變量不保留拷貝,直接訪問主內存中的(讀操做多時使用
較好;線程間須要通訊,本條作不到)
Volatile 變量具備 synchronized 的可見性特性,可是不具有原子特性。這就是說線程可以自
動發現 volatile 變量的最新值。Volatile 變量可用於提供線程安全,可是隻能應用於很是有限的
一組用例:多個變量之間或者某個變量的當前值與修改後值之間沒有約束。
您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理
想的線程安全,必須同時知足下面兩個條件:
對變量的寫操做不依賴於當前值;該變量沒有包含在具備其餘變量的不變式中。異步

//只給出要修改的代碼,其他代碼與上同
        class Bank {
            //須要同步的變量加上volatile
            private volatile int account = 100;

            public int getAccount() {
                return account;
            }
            //這裏再也不須要synchronized 
            public void save(int money) {
                account += money;
            }
        }

4.使用重入鎖實現線程同步 ReenreantLockjvm

ReentrantLock感受上是synchronized的加強版,synchronized的特色是使用簡單,一切交給JVM去處理,可是功能上是比較薄弱的。在JDK1.5以前,ReentrantLock的性能要好於synchronized,因爲對JVM進行了優化,如今的JDK版本中,二者性能是不相上下的。若是是簡單的實現,不要刻意去使用ReentrantLock。

相比於synchronized,ReentrantLock在功能上更加豐富,它具備可重入、可中斷、可限時、公平鎖等特色。

ReenreantLock類的經常使用方法有:

ReentrantLock() : 建立一個ReentrantLock實例 

lock() : 得到鎖 

unlock() : 釋放鎖 

package test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Bank {
    private int account = 100;
    //須要聲明這個鎖
    private Lock lock = new ReentrantLock();
    public int getAccount() {
        return account;
    }

    //這裏再也不須要synchronized 
    public void save(int money) {
        lock.lock();
        try{
            account += money;
        }finally{
            lock.unlock();
        }
    }
}

lockInterruptibly():得到響應中斷的鎖

interrupt():中斷

可中斷:在java中,線程的中斷(interrupt)只是改變了線程的中斷狀態。對於執行通常邏輯的線程,若是調用它的interrupt()方法,那麼對這個線程沒有任何影響,線程會繼續正常地執行下去;對於wait中等待notify/notifyAll喚醒的線程(包括sleep,join),其實這個線程已經"暫停"執行,由於它正在某一對象的休息室中,這時若是它的中斷狀態被改變,那麼它就會拋出異常。那麼它與被notify/All喚醒的線程的區別是,正常喚醒的線程會繼續執行wait下面的語句,而在wait中被中斷的線程則將控制權交給了catch語句,一些正常的邏輯要被放到catch中來運行。

由於普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()可以響應中斷。因此利用這一點來處理線程的死鎖問題。案例代碼以下:

package test;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable
{
	public static ReentrantLock lock1 = new ReentrantLock();
	public static ReentrantLock lock2 = new ReentrantLock();

	int lock;

	public Test(int lock)
	{
		this.lock = lock;
	}

	@Override
	public void run()
	{
		try
		{
			if (lock == 1)
			{
				lock1.lockInterruptibly();
				try
				{
					Thread.sleep(500);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
				lock2.lockInterruptibly();
			}
			else
			{
				lock2.lockInterruptibly();
				try
				{
					Thread.sleep(500);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
				lock1.lockInterruptibly();
			}
		}
		catch (Exception e)
		{
			// TODO: handle exception
			e.printStackTrace();
		}
		finally
		{
            //isHeldByCurrentThread()查詢當前線程是否保持此鎖
			if (lock1.isHeldByCurrentThread())
			{
				lock1.unlock();
			}
			if (lock2.isHeldByCurrentThread())
			{
				lock2.unlock();
			}
			System.out.println(Thread.currentThread().getId() + ":線程退出");
		}
	}

	public static void main(String[] args) throws InterruptedException
	{
		Test t1 = new Test(1);
		Test t2 = new Test(2);
		Thread thread1 = new Thread(t1);
		Thread thread2 = new Thread(t2);
		thread1.start();
		thread2.start();
		Thread.sleep(1000);
		DeadlockChecker.check();
	}

	static class DeadlockChecker
	{
		//ThreadMXBean是Java 虛擬機線程系統的管理接口。實現此接口的實例是一個 MXBean,能夠經過調用 ManagementFactory.getThreadMXBean() 方法或從平臺 MBeanServer 方法得到它。
		private final static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
		final static Runnable deadlockChecker = new Runnable()
		{
			@Override
			public void run()
			{
				while (true)
				{
					//線程 ID 是一個經過調用線程的 Thread.getId() 方法返回的 long 型正值。線程 ID 在其生存期間是惟一的。線程終止時,該線程 ID 能夠被從新使用。
					//findDeadlockedThreads()線程的死鎖檢測方法,返回裝有死鎖線程ID的long型數組
					long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
					if (deadlockedThreadIds != null)
					{
						//getThreadInfo(long[], boolean, boolean)用來獲取線程堆棧的跟蹤(各類狀態信息的複製),這裏的目的是爲了獲取全部處於活動狀態線程的線程信息
						ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
						//兩層循環爲了確保被中斷的線程是處於活動狀態中的線程
						for (Thread t : Thread.getAllStackTraces().keySet())
						{
							for (int i = 0; i < threadInfos.length; i++)
							{
								if(t.getId() == threadInfos[i].getThreadId())
								{
									t.interrupt();
								}
							}
						}
					}
					try
					{
						Thread.sleep(5000);
					}
					catch (Exception e)
					{
						// TODO: handle exception
					}
				}

			}
		};
		
		public static void check()
		{
			Thread t = new Thread(deadlockChecker);
			//設置爲守護線程,做用:當守護線程是jvm中僅剩的線程時它會自動離開。垃圾回收GC就是一個典型的守護線程。
			//注:當全部的常規線程運行完畢之後,守護線程無論運行到哪裏,java虛擬機都會退出運行,因此守護線程中最好不要寫一些會影響程序的業務。
			t.setDaemon(true);
			t.start();
		}
	}

}

可限時:超時不能得到鎖,就返回false,不會永久等待構成死鎖

使用lock.tryLock(long timeout, TimeUnit unit)來實現可限時鎖,參數爲時間和單位。

舉個例子來講明下可限時:

package test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable
{
	public static ReentrantLock lock = new ReentrantLock();

	@Override
	public void run()
	{
		try
		{
			if (lock.tryLock(5, TimeUnit.SECONDS))
			{
				Thread.sleep(6000);
			}
			else
			{
				System.out.println("get lock failed");
			}
		}
		catch (Exception e)
		{
		}
		finally
		{
			if (lock.isHeldByCurrentThread())
			{
				lock.unlock();
			}
		}
	}
	
	public static void main(String[] args)
	{
		Test t = new Test();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}

}

使用兩個線程來爭奪一把鎖,當某個線程得到鎖後,sleep6秒,每一個線程都只嘗試5秒去得到鎖。

因此一定有一個線程沒法得到鎖。沒法得到後就直接退出了。

輸出:

get lock failed

公平鎖

public ReentrantLock(boolean fair) 

public static ReentrantLock fairLock = new ReentrantLock(true);

通常意義上的鎖是不公平的,不必定先來的線程能先獲得鎖,後來的線程就後獲得鎖。不公平的鎖可能會產生飢餓現象。

公平鎖的意思就是,這個鎖能保證線程是先來的先獲得鎖。雖然公平鎖不會產生飢餓現象,可是公平鎖的性能會比非公平鎖差不少。

5.使用阻塞隊列實現線程同步

使用javaSE5.0版本中新增的java.util.concurrent包將有助於簡化開發。 
本小節主要是使用LinkedBlockingQueue<E>來實現線程的同步 
LinkedBlockingQueue<E>是一個基於已鏈接節點的,範圍任意的blocking queue。 
隊列是先進先出的順序(FIFO)

LinkedBlockingQueue 類經常使用方法 
    LinkedBlockingQueue() : 建立一個容量爲Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在隊尾添加一個元素,若是隊列滿則阻塞 
    size() : 返回隊列中的元素個數 
    take() : 移除並返回隊頭元素,若是隊列空則阻塞 

package test;

import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 用阻塞隊列實現線程同步 LinkedBlockingQueue的使用
 * 當隊列中沒有元素時take會被阻塞
 * 當隊列慢了時put會被阻塞
 */
public class BlockingSynchronizedThread {
    /**
     * 定義一個阻塞隊列用來存儲生產出來的商品
     */
    private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(10);
    /**
     * 定義生產商品個數
     */
    private static final int size = 20;
    /**
     * 定義啓動線程的標誌,爲0時,啓動生產商品的線程;爲1時,啓動消費商品的線程
     */
    private int flag = 0;

    private class LinkBlockThread implements Runnable {
        @Override
        public void run() {
            int new_flag = flag++;
            System.out.println("啓動線程 " + new_flag);
            if (new_flag == 0) {
                for (int i = 0; i < size; i++) {
                    int b = new Random().nextInt(255);
                    System.out.println("生產商品:" + b + "號");
                    try {
                        queue.put(b);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("倉庫中還有商品:" + queue.size() + "個");
                    try {
                        Thread.sleep(100);//生產速度大於消費速度
                        //Thread.sleep(5000);消費速度大於生產速度
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } else {
                for (int i = 0; i < size / 2; i++) {
                    try {
                        int n = queue.take();
                        System.out.println("消費者買去了" + n + "號商品");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("倉庫中還有商品:" + queue.size() + "個");
                    try {
                        Thread.sleep(5000);
                        //Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
        LinkBlockThread lbt = bst.new LinkBlockThread();
        Thread thread1 = new Thread(lbt);
        Thread thread2 = new Thread(lbt);
        thread1.start();
        thread2.start();

    }

}

注:BlockingQueue<E>定義了阻塞隊列的經常使用方法,尤爲是三種添加元素的方法,咱們要多加註意,當隊列滿時:

  add()方法會拋出異常

  offer()方法返回false

  put()方法會阻塞

6.AtomicInteger

AtomicInteger,一個提供原子操做的Integer的類。在Java語言中,++i和i++操做並非線程安全的,在使用的時候,不可避免的會用到synchronized關鍵字。而AtomicInteger則經過一種線程安全的加減操做接口。

AtomicInteger提供的接口。

//獲取當前的值

public final int get()

//取當前的值,並設置新的值

 public final int getAndSet(int newValue)

//獲取當前的值,並自增

 public final int getAndIncrement()

//獲取當前的值,並自減

public final int getAndDecrement()

//獲取當前的值,並加上預期的值

public final int getAndAdd(int delta)

synchronized的方法和一個AtomicInteger的方法來進行測試,直觀的感覺下性能上的差別

package test;

import java.util.concurrent.atomic.AtomicInteger;  
public class AtomicIntegerCompareTest {  
    private int value;  
      
    public AtomicIntegerCompareTest(int value){  
        this.value = value;  
    }  
      
    public synchronized int increase(){  
        return value++;  
    }  
      
    public static void main(String args[]){  
        long start = System.currentTimeMillis();  
          
        AtomicIntegerCompareTest test = new AtomicIntegerCompareTest(0);  
        for( int i=0;i< 1000000;i++){  
            test.increase();  
        }  
        long end = System.currentTimeMillis();  
        System.out.println("time elapse:"+(end -start));  
          
        long start1 = System.currentTimeMillis();  
          
        AtomicInteger atomic = new AtomicInteger(0);  
          
        for( int i=0;i< 1000000;i++){  
            atomic.incrementAndGet();  
        }  
        long end1 = System.currentTimeMillis();  
        System.out.println("time elapse:"+(end1 -start1) );  
          
          /*運行結果:time elapse:19
            time elapse:9
  AtomicInteger擁有更高效的性能
*/
    }  
}

4)一旦一個線程進入一個實例的任何同步方法,別的線程將不能
進入該同一實例的其它同步方法,可是該實例的非同步方法仍然可以被調用

Demo1:

package test;

public class SynTest {
	//非同步
	static void method(Thread thread){
		System.out.println("begin "+thread.getName());
		try{
			Thread.sleep(2000);
		}catch(Exception ex){
			ex.printStackTrace();
		}
		System.out.println("end "+thread.getName());
	}
	 
	//同步方式一:同步方法
	synchronized static void method1(Thread thread){//這個方法是同步的方法,每次只有一個線程能夠進來
		System.out.println("begin "+thread.getName());
		try{
			Thread.sleep(2000);
		}catch(Exception ex){
			ex.printStackTrace();
		}
		System.out.println("end "+thread.getName());
	}
	 
	//同步方式二:同步代碼塊
	static void method2(Thread thread){
		synchronized(SynTest.class) {
			System.out.println("begin "+thread.getName());
			try{
				Thread.sleep(2000);
			}catch(Exception ex){
				ex.printStackTrace();
			}
			System.out.println("end "+thread.getName());
		}
	}
	 
	 
	//同步方式三:使用同步對象鎖
	private static Object _lock1=new Object();
		private static byte _lock2[]={};//聽說,此鎖更可提升性能。源於:鎖的對象越小越好
		static void method3(Thread thread){
			synchronized(_lock1) {
				System.out.println("begin "+thread.getName());
				try{
					Thread.sleep(2000);
				}catch(Exception ex){
					ex.printStackTrace();
				}
			System.out.println("end "+thread.getName());
		}
	}
	 
	public static void main(String[] args){
		//啓動3個線程,這裏用了匿名類
		for(int i=0;i<3;i++){
			new Thread(){
				public void run(){
					//method(this);
					method1(this);
					method2(this);
					method3(this);
				}
			}.start();
		}
	}
}

 Demo2:

package test;

public class SynTest2 {
	public static void main(String[] args){
		Callme target=new Callme();
		Caller ob1=new Caller(target,"Hello");
		Caller ob2=new Caller(target,"Synchronized");
		Caller ob3=new Caller(target,"World");
	}
}
package test;

public class Callme {
	synchronized void test(){
		System.out.println("測試是不是:一旦一個線程進入一個實例的任何同步方法,別的線程將不能進入該同一實例的其它同步方法,可是該實例的非同步方法仍然可以被調用");
	}
	 
		void nonsynCall(String msg){
			System.out.println("["+msg);
			System.out.println("]");
		}
	 
		synchronized void synCall(String msg){
			System.out.println("["+msg);
			System.out.println("]");
		}
}
	 
class Caller implements Runnable{
	String msg;
	Callme target;
	Thread t;
	 
	Caller(Callme target,String msg){
		this.target=target;
		this.msg=msg;
		t=new Thread(this);
		t.start();
	}
	 
	public void run() {
		// TODO Auto-generated method stub
		//target.nonsynCall(msg);
		target.synCall(msg);
		target.test();
	}
}
相關文章
相關標籤/搜索