天天看點

JUC多線程1. volatile 關鍵字 記憶體可見性2. 原子變量與CAS算法3. 同步容器類 ConcurrentHashMap4.CountDownLatch閉鎖5.實作Callable接口6.同步鎖 Lock

JUC多線程

  • 1. volatile 關鍵字 記憶體可見性
  • 2. 原子變量與CAS算法
    • 原子變量
    • CAS算法
  • 3. 同步容器類 ConcurrentHashMap
  • 4.CountDownLatch閉鎖
  • 5.實作Callable接口
  • 6.同步鎖 Lock

1. volatile 關鍵字 記憶體可見性

  • 記憶體可見性問題:當多線程操作共享資料時,彼此不可見。(解決方法1:使用synchronize 同步 鎖讓每一次讀取資料都從記憶體中去讀 保證了資料的及時的更新,但是效率還是比較低;2:使用volatile關鍵字)
  • volatile作用:當多個線程操作共享資料時,可以保證記憶體中的資料是可見的。(相教于synchronized 是一種較為輕量級的同步政策)
  • volatile與synchronized不同點:1. volatile不具備“互斥性”(互斥性目前資源有且隻能一個線程通路;2. volatile 不能保證變量的“原子性”)

2. 原子變量與CAS算法

原子變量

i++

的原子性問題:i++ 的操作實際包含了三個步驟“讀 該 寫”

  • 原子變量:在jdk1.5以後提供了

    java.util.concurrent.atomic

    包含了常用的原子變量AtomicXXX(原子變量:volatile的特性,它裡面封裝的變量有volatile修飾,故而記憶體可見性;CAS算法 compare-and-swap保證了資料的原子性。)

CAS算法

  • CAS算法 是硬體對于并發操作共享資料的共享支援
  • 包含了三個操作數 記憶體值 V 預估值 A 更新值 B
  • 特點:當且僅當V==A時,才V=B;否則,不做任何操作。
  • 每一次都先從記憶體值值取資料,(預估值與記憶體值進行比較,如果相同 才将更新的值 指派非記憶體值,否則不做任何操作)

3. 同步容器類 ConcurrentHashMap

  • 在java5.0以後 在

    java.util.concurrent

    包下提供了許多種并發的容器來改進同步容器的性能
  • ConcurrentHashMap

    :是線程安全的 鎖的分段機制 預設有16個級别的

    segment

    concurrentLevel

    每一個段都有一個獨立的鎖,支援同時多個線程同時通路,效率比hashtable高(增加了一個線程安全的hash表,分段鎖代替了hashtable的獨占鎖,進而提高了性能)
    JUC多線程1. volatile 關鍵字 記憶體可見性2. 原子變量與CAS算法3. 同步容器類 ConcurrentHashMap4.CountDownLatch閉鎖5.實作Callable接口6.同步鎖 Lock

4.CountDownLatch閉鎖

  • 在java 5.0在java.util.concurrent包中,提供了多種并發容器類來改進同步容器的性能
  • CountDownLatch一個同步的輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待
  • 閉鎖 可以延遲線程的進度知道到達終止狀态,可以確定某些活動到其他的活動都完成才繼續執行

計算10個線程的操作時間:

/**
 * 閉鎖:先完成某些運算時,隻有其他所有線程的運算全部完成時,目前的原酸才能繼續執行
 * 
 */
public class TestCountDownLatch {
	public static void main(String[] args) {
		// 目的:計算10個線程的總時間

		// 錯誤示範1:下面的方式沒有辦法計算時間,一個主線程 10個分線程,隻有等10個分線程都執行完了才計算他們的時間差,才能達到目的
		/*
		 * final CountDownLatch latch=new CountDownLatch(5); LacthDemo lacthDemo=new
		 * LacthDemo(latch); //開啟多線程 計算10個線程的時間 long start=System.currentTimeMillis();
		 * for(int i=0;i<10;i++) { new Thread(lacthDemo).start();//開啟10個線程 } long
		 * end=System.currentTimeMillis(); System.out.println("消耗的時間:"+(end-start));
		 */

		final CountDownLatch latch = new CountDownLatch(10);// 底層有一個count來控制,每此都要減少1,當為0的時候,就繼續執行
		LacthDemo lacthDemo = new LacthDemo(latch);
		// 開啟多線程 計算10個線程的時間
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10; i++) {
			new Thread(lacthDemo).start();// 開啟10個線程
		}
		try {
			latch.await();// 要它等待,等他為0就繼續執行
		} catch (InterruptedException e) {// 用于中斷的
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("消耗的時間:" + (end - start));
	}
}

class LacthDemo implements Runnable {
	private CountDownLatch latch;
	public LacthDemo(CountDownLatch latch) {
		this.latch = latch;
	}
	@Override
	public void run() {
		// 但是多線程可能存線上程安全問題,故而加一個synchronize
		synchronized (this) {
			try {
				for (int i = 0; i < 5000; i++) {
					if (i % 2 == 0) {
						System.out.println(i);
					}
				}
			} finally {
				latch.countDown();// 買次執行完就減1,當為0的時候,就繼續執行,且必須執行
			}
		}
	}
}
           

5.實作Callable接口

建立線程的有4種方式:

1. 繼承Thread類,并複寫run方法,建立該類對象,調用start方法開啟線程。此方式沒有傳回值。

2. 實作Runnable接口,複寫run方法,建立Thread類對象,将Runnable子類對象傳遞給Thread類對象。調用start方法開啟線程。此方法2較之方法1好,将線程對象和線程任務對象分離開。降低了耦合性,利于維護。此方式沒有傳回值。

3. 建立FutureTask對象,建立Callable子類對象,複寫call(相當于run)方法,将其傳遞給FutureTask對象(相當于一個Runnable)。 建立Thread類對象,将FutureTask對象傳遞給Thread對象。調用start方法開啟線程。這種方式可以獲得線程執行完之後的傳回值。該方法使用Runnable功能更加強大的一個子類.這個子類是具有傳回值類型的任務方法。

4. 線程池
           

Callable建立線程:

1.相較于Runnable:Callable可以有傳回值,并且可以抛出異常

2.執行Callable的方式,需要FutureTask實作類的支援,可以用于接收運算的結果,FutureTask是Future的實作類

//建立線程 
public class TestCallable {
	public static void main(String[] args) {
		ThreadDemo treadDemo=new ThreadDemo();
		//因為有傳回值,執行Callable的方式,需要FutureTask實作類的支援,可以用于接收運算的結果
		FutureTask<Integer> res=new FutureTask<>(treadDemo);
		new Thread(res).start();//啟動線程
		
		try {
			//接受線程運算後的結果
			Integer sum=res.get();//當線上程的運作過程中,并不會執行,當new Thread(res).start()運作完成後才會執行
			//故而:FutureTask也用于閉鎖的操作
			System.out.println(sum);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}	
	}
}

//class TreadDemo implements Runnable {
//	@Override
//	public void run() {
//		// TODO Auto-generated method stub
//	}
//}
/*
 * 1.相較于Runnable:Callable可以有傳回值,并且可以抛出異常
 * 2.執行Callable的方式,需要FutureTask實作類的支援,可以用于接收運算的結果,FutureTask是Future的實作類
 * 
 */
class ThreadDemo implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {//重寫call()方法
		int sum=0;
		for (int i = 0; i<Integer.MAX_VALUE; i++) {
			System.out.println(i);
			sum+=i;
		}
		return sum;
	}	
}
           
  • 所在的包:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
           

6.同步鎖 Lock

  • 用于解決多線程安全的方式
  1. 同步代碼塊 關鍵字 synchronized(隐示鎖)
  2. 同步方法 關鍵字synchronized (隐示鎖)
  3. jdk1.5以後 同步鎖Lock 是顯示鎖,(需要通過

    lock()

    方式進行上鎖,通過

    unlock()

    來釋放鎖)更加靈活,但是要注意調用unlock()釋放鎖,建lock執行個體,因為lock是接口,是以要使用來的實作類

    ReentrantLock

public class TestLock {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		new Thread(ticket, "1号視窗").start();
		new Thread(ticket, "2号視窗").start();
		new Thread(ticket, "3号視窗").start();
	}
}

class Ticket implements Runnable {
	private int tick = 100;
	private Lock lock = new ReentrantLock();// 建立lock執行個體,因為lock是接口,是以要使用來的實作類ReentrantLock

	@Override
	public void run() {
		while (true) {
			lock.lock();// 上鎖
			try {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
				}
				if (tick > 0) {
					System.out.println(Thread.currentThread().getName() + ": 完成售票,餘票為" + --tick);
				}
			} finally {
				lock.unlock();// 釋放鎖 必須有最好寫到finally
			}
		}
	}
}
           
  • 類所在的包:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
           
  • 利用wait(),notify()等待喚醒機制解決生産消費者問題
//生産者 消費者案例
//生産者 消費者案例
public class TestProductAndConsume2 {
	public static void main(String[] args) {
		clerk clerk=new clerk();
		Productor productor=new Productor(clerk);
		Consumer consumer=new Consumer(clerk);
		//如果生産者消費者不使用等待喚醒機制 可能就會一段時間内一直消費 或生産 沒有進行生産消費的過程
		new Thread(productor,"生産者A").start();
		new Thread(productor,"生産者C").start();
		new Thread(consumer,"消費者B").start();
		new Thread(consumer,"消費者D").start();
	}
}

//店員
class clerk{
	private int product=0;
	//進貨
	public synchronized void get() {
		//假設最多可以存放30個商品
		while(product>=30) {//解決虛假喚醒  wait應該總是使用在while中
			System.out.println("産品滿。。。");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+":"+ ++product);//進貨
		this.notifyAll();
		
	}
	//出售
	public synchronized void sale() {
		while(product<=0) {
			System.out.println("缺貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+":"+ --product);//進貨
		this.notifyAll();
	
	}
}

//生産者
class Productor implements Runnable {//生産者可能有多個
	private clerk clerk;
	public Productor(clerk clerk) {
		this.clerk=clerk;
	}
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}

//消費者
class Consumer implements Runnable{
	private clerk clerk;
	public Consumer(clerk clerk) {
		this.clerk=clerk;
	}
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			clerk.sale();
		}
	}
}
           
  • 通過lock來完成等待喚醒機制,生産者消費者模型
  • Condition

    :可以控制通信,與

    wait,notify,notifyAll

    對應的分别是

    await,signal,singalAll

    具體代碼:
//生産者 消費者案例
public class TestProductAndConsumeLock {
	public static void main(String[] args) {
		clerk clerk = new clerk();
		Productor productor = new Productor(clerk);
		Consumer consumer = new Consumer(clerk);
		// 如果生産者消費者不使用等待喚醒機制 可能就會一段時間内一直消費 或生産 沒有進行生産消費的過程
		new Thread(productor, "生産者A").start();
		new Thread(productor, "生産者C").start();
		new Thread(consumer, "消費者B").start();
		new Thread(consumer, "消費者D").start();
	}
}

//店員
class clerk {
	private int product = 0;
	// 使用同步鎖來解決
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();// 通過Condition來進行通信
	// 進貨
	public void get() {
		lock.lock();
		try {
			// 假設最多可以存放30個商品
			while (product >= 30) {// 解決虛假喚醒
				System.out.println("産品滿。。。");
//				try {
//					this.wait();
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				try {
					condition.await();// 等待
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + ":" + ++product);// 進貨
			condition.signalAll();// 喚醒

		} finally {
			lock.unlock();
		}
	}

	// 出售
	public void sale() {
		lock.lock();
		try {
			while (product <= 0) {
				System.out.println("缺貨。。。");
//				try {
//					this.wait();lock也有自己的等待喚醒
//					
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + ":" + --product);// 進貨
			condition.signalAll();
		} finally {
			lock.unlock();
		}

	}
}
//生産者
class Productor implements Runnable {// 生産者可能有多個
	private clerk clerk;

	public Productor(clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}
//消費者
class Consumer implements Runnable {
	private clerk clerk;

	public Consumer(clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			clerk.sale();
		}
	}
}
           

線程按序交替

1.A B C交替列印 2.假設A B C線程将自己的ID列印出來個10次,然後按照一定的順序顯示 3.A--列印5次 B列印15次 C--列印20次 并且按序交替

實作代碼:

//A B C交替列印
//假設A B C線程将自己的ID列印出來個10次,然後按照一定的順序顯示
//A--列印5次   B列印15次 C--列印20次  并且按序交替
public class TestABCAlternate {
	public static void main(String[] args) {
		AlternateDemo alternateDemo=new AlternateDemo();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {
					alternateDemo.loopA(i);
				}
			}
		},"A").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i <=20; i++) {
					alternateDemo.loopB(i);
				}
			}
		},"B").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {
					alternateDemo.loopC(i);
					System.out.println("------------------");
				}
			}
		},"C").start();
	}
}
class AlternateDemo{
	private int number=1;//表示目前正在執行線程的标記
	private Lock lock=new ReentrantLock();//要實作按序交替 需要有鎖
	//還需要線程之間進行通信
	private Condition conditionA=lock.newCondition();
	private Condition conditionB=lock.newCondition();
	private Condition conditionC=lock.newCondition();
	public void loopA(int totallLoop) {//列印的第幾輪
		lock.lock();
		try {
			//1.判斷線程是否應該是1,1為A列印  2--B,3--C
			if(number!=1) {
				//如果不該A列印 則自己就進行等待的操作
				try {
					conditionA.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//2. number==1 列印
			for (int i = 1; i <=5; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
			}
			//3.喚醒
			number=2;
			conditionB.signal();//喚醒2列印
		} finally {
			lock.unlock();
		}
	}
	public void loopB(int totallLoop) {//列印的第幾輪
		lock.lock();
		try {
			//1.判斷線程是否應該是1,1為A列印  2--B,3--C
			if(number!=2) {
				//如果不該A列印 則自己就進行等待的操作
				try {
					conditionB.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//2. number==1 列印
			for (int i = 1; i <=15; i++) {
			System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
			}
			//3.喚醒
			number=3;
			conditionC.signal();//喚醒2列印
		} finally {
			lock.unlock();
		}
	}
	public void loopC(int totallLoop) {//列印的第幾輪
		lock.lock();
		try {
			//1.判斷線程是否應該是1,1為A列印  2--B,3--C
			if(number!=3) {
				//如果不該A列印 則自己就進行等待的操作
				try {
					conditionC.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//2. number==1 列印
			for (int i = 1; i <=20; i++) {
			System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
			}
			//3.喚醒
			number=1;
			conditionA.signal();//喚醒2列印
		} finally {
			lock.unlock();
		}
	}
}
           

繼續閱讀