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以後提供了
包含了常用的原子變量AtomicXXX(原子變量:volatile的特性,它裡面封裝的變量有volatile修飾,故而記憶體可見性;CAS算法 compare-and-swap保證了資料的原子性。)java.util.concurrent.atomic
CAS算法
- CAS算法 是硬體對于并發操作共享資料的共享支援
- 包含了三個操作數 記憶體值 V 預估值 A 更新值 B
- 特點:當且僅當V==A時,才V=B;否則,不做任何操作。
- 每一次都先從記憶體值值取資料,(預估值與記憶體值進行比較,如果相同 才将更新的值 指派非記憶體值,否則不做任何操作)
3. 同步容器類 ConcurrentHashMap
- 在java5.0以後 在
包下提供了許多種并發的容器來改進同步容器的性能java.util.concurrent
-
:是線程安全的 鎖的分段機制 預設有16個級别的ConcurrentHashMap
段segment
每一個段都有一個獨立的鎖,支援同時多個線程同時通路,效率比hashtable高(增加了一個線程安全的hash表,分段鎖代替了hashtable的獨占鎖,進而提高了性能)concurrentLevel
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
- 用于解決多線程安全的方式
- 同步代碼塊 關鍵字 synchronized(隐示鎖)
- 同步方法 關鍵字synchronized (隐示鎖)
- jdk1.5以後 同步鎖Lock 是顯示鎖,(需要通過
方式進行上鎖,通過lock()
來釋放鎖)更加靈活,但是要注意調用unlock()釋放鎖,建lock執行個體,因為lock是接口,是以要使用來的實作類unlock()
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();
}
}
}