java 支持多個線程同時訪問一個對象或者是對象的成員變量,關鍵字 synchroninzed
可以修飾方法或者以同步塊的形式來使用习勤,他主要確保多個線程在同一時刻,只能有一個線程處于處于方法或者是 同步塊中报亩,他保證了線程對變量訪問的可見性和排他性卷员,又稱為內(nèi)置鎖機制。
對象鎖和類鎖
對象鎖適用于對象實例方法居砖,或者一個對象上的虹脯,類鎖是用于類的靜態(tài)方法或者一個類的Class 對象上的。我們知道類的對象實例可以有多個奏候,但是每個類只有一個Class對象循集,所以不同對象的實例的對象鎖是互不干擾的,但是每個類只有一個類鎖蔗草。
有一點要注意:類鎖是一個概念上的東西咒彤,并不是真實存在的疆柔,類鎖其實鎖的是每個類的對應的class 對象。類鎖和對象鎖其實是互不干擾的镶柱。
線程間的協(xié)作
等待 / 通知機制
只能用在synchronized 包裹的塊中
等待:wait() ; 通知:notifyAll();
概念:一個線程A調(diào)用了B的wait()方法進入等待狀態(tài)旷档,而另一個線程C調(diào)用了B 的notify()或者是notifyAll()方法,線程A 收到了通知后從B的wait()返回歇拆,進而執(zhí)行后續(xù)的操作鞋屈。上述兩個線程通過對象B 來完成交互,而對象上的wait()方法和notify()方法的關系就如同開關一樣故觅,用來完成等待方和通知方之間的交互工作厂庇。
等待和通知的標準范式:
等待方遵循的條件
- 獲取對象鎖
- 如果條件不滿足,那么久就調(diào)用對象的wait()方法输吏,被通知后仍要檢查條件权旷。
-
條件滿足執(zhí)行對應的邏輯
等待方
通知方遵循的條件:
- 獲取對象鎖
- 改變對象的條件
-
通知所有等待在對象上的線程
通知方
在通知的時候盡可能的去使用notifyAll()
代碼模塊:
等待方
/**
* 快遞
*/
public class Express {
public static final String CITY= "BeiJing";
private int km; //運輸公里數(shù)
private String site; //到達地點
public Express() {
}
public Express(int km, String site) {
this.km = km;
this.site = site;
}
/**
* 公里數(shù)變化了
*/
public synchronized void changeKm(){
this.km =110;
notifyAll();
}
/**
* 到達地點變化了
*/
public synchronized void changeSite(){
this.site = "ShangHai";
notifyAll();
}
public synchronized void waitKm(){
while (km<100){
try {
wait();
System.out.println("check Km thread"+Thread.currentThread().getName()+"is be notifyed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("this is km "+km);
}
public synchronized void waitSite(){
while (CITY.equals(site)){
try {
wait();
System.out.println("check site thread"+Thread.currentThread().getName()+"is be notifyed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("this is site " +site);
}
}
通知方的代碼 包含測試代碼
/**
* 測試wait nofify
*/
public class TextWn {
private static Express express = new Express(0,Express.CITY);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new CheckKm().start();
}
for (int i = 0; i < 3; i++) {
new CheckSite().start();
}
Thread.sleep(1000);
express.changeKm();
express.changeSite();
}
public static class CheckSite extends Thread {
@Override
public void run() {
super.run();
express.waitSite();
}
}
public static class CheckKm extends Thread {
@Override
public void run() {
super.run();
express.waitKm();
}
}
}
以上可以直接運行。
ThreadLocal
使用static 的靜態(tài)變量评也,數(shù)據(jù)隔離
ThreadLoacl 是線程變量炼杖,是一個以ThreadLocal為鍵,任意對象為值的存儲結(jié)構(gòu)盗迟。這個結(jié)構(gòu)被附帶在線程上坤邪,也就是說一個線程可以根據(jù)一個ThreadLocal對象查詢到綁定在這個線程上的一個值,ThreadLocal往往用來實現(xiàn)變量在線程之間的隔離罚缕。
ThreadLocal 接口只有四個方法:
- void set(Objcet value); 設置當前線程的局部變量的值
- Object get(); 返回當前線程的局部變量的值
- void remove(); 將當前線程局部變量的值刪除艇纺,減少內(nèi)存的占用。
- Objcet initalValue(); 返回該線程局部變量的初始化值邮弹。
測試代碼:
public class UseThreadLocal {
ThreadLocal<Integer> threadLocal = new ThreadLocal(){
@Override
protected Object initialValue() {
return 1;
}
};
public void startThreadT(){
Thread[] runs = new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for(int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
*類說明:測試線程黔衡,線程的工作是將ThreadLocal變量的值變化,并寫回腌乡,
* 看看線程之間是否會互相影響
*/
public class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName()+":start");
Integer s = threadLocal.get();
s = s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+" :"
+threadLocal.get());
// threadLocal.remove();
}
}
public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.startThreadT();
}
}
顯式鎖
Lock和synchronized 的比較
我們一般java 程序都是用synchronized 來進行加鎖的盟劫,使用synchronized 將會隱式的獲取鎖,但是它將鎖的獲取和釋放固化了与纽,也就是先獲取在釋放侣签,synchronized 是java 語言層面的鎖,也稱之為內(nèi)置鎖急迂。
synchronized 這種機制影所,一旦開始獲取鎖,是不能中斷的僚碎,也不提供嘗試獲取鎖的機制猴娩。
Lock 是有java 語法層面提供的,鎖的獲取和釋放是需要我們明顯的去操作,因此被成為顯式鎖卷中。并且提供了synchronized 不提供的機制矛双。
Lock 的接口和核心方法
在finally 中釋放鎖,目的是獲取鎖以后能被釋放
setLock.lock();
try {
//do....
}finally {
setLock.unlock();
}
可重入鎖ReentrantLock 仓坞、所謂鎖的公平與非公平
synchronized 關鍵字隱士的支持可重入背零,比如一個synchronized修飾的遞歸方法,在方法執(zhí)行時无埃,執(zhí)行線程在獲取了鎖以后仍能連續(xù)多次的獲的該鎖。ReentrantLock在調(diào)用lock()方法時毛雇,已經(jīng)獲取到了鎖的線程嫉称,能夠在調(diào)取lock()方法獲取所而不會阻塞。
公平和非公平鎖
如果在時間上灵疮,先對鎖進行獲取的請求一定先被滿足织阅,那么這個鎖是公平的,反之震捣,是不公平的荔棉。公平的獲取鎖,也就是等待時間最長的線程最優(yōu)先獲取鎖蒿赢,也可以說鎖獲取是順序的润樱。
ReentrantLock提供了一個構(gòu)造函數(shù),能夠控制鎖是否是公平的羡棵。事實上壹若,公平的鎖機制往往沒有非公平的效率高。原因是皂冰,在恢復一個被掛起的線程與該線程真正開始運行之間存在著嚴重的延遲店展。假設線程A持有一個鎖,并且線程B請求這個鎖。由于這個鎖已被線程A持有,因此B將被掛起秃流。當A釋放鎖時,B將被喚醒,因此會再次嘗試獲取鎖赂蕴。與此同時,如果C也請求這個鎖,那么C很可能會在B被完全喚醒之前獲得、使用以及釋放這個鎖舶胀。這樣的情況是一種“雙贏”的局面:B獲得鎖的時刻并沒有推遲,C更早地獲得了鎖,并且吞吐量也獲得了提高概说。
讀寫鎖 ReentrantReadWriteLock
之前提到鎖(synchronized和ReentrantLock)基本都是排他鎖,這些鎖在同一時刻只允許一個線程進行訪問峻贮,而讀寫鎖在同一時刻可以允許多個讀線程訪問席怪,但是在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞纤控。讀寫鎖維護了一對鎖挂捻,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖船万,使得并發(fā)性相比一般的排他鎖有了很大提升刻撒。
除了保證寫操作對讀操作的可見性以及并發(fā)性的提升之外骨田,讀寫鎖能夠簡化讀寫交互場景的編程方式。假設在程序中定義一個共享的用作緩存數(shù)據(jù)結(jié)構(gòu)声怔,它大部分時間提供讀服務(例如查詢和搜索)态贤,而寫操作占有的時間很少,但是寫操作完成之后的更新需要對后續(xù)的讀服務可見醋火。
一般情況下悠汽,讀寫鎖的性能都會比排它鎖好,因為大多數(shù)場景讀是多于寫的芥驳。在讀多于寫的情況下柿冲,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量.
代碼:
**
* 商品類
*/
public class GoodsInfo {
private final String name;
private double totalMoney; //銷售總額
private int storeNumber; //庫存數(shù)
public GoodsInfo(String name, double totalMoney, int storeNumber) {
this.name = name;
this.totalMoney = totalMoney;
this.storeNumber = storeNumber;
}
public double getTotalMoney() {
return totalMoney;
}
public int getStoreNumber() {
return storeNumber;
}
public void changeNumber(int sellNumber){
this.totalMoney += sellNumber*25;
this.storeNumber -= sellNumber;
}
}
/**
* 簡單的業(yè)務應用場景
*/
public interface GoodsService {
public GoodsInfo getNum();//獲得商品的信息
public void setNum(int number);//設置商品的數(shù)量
}
public class UseThread implements GoodsService{
private GoodsInfo goodsInfo;
private ReadWriteLock lock =new ReentrantReadWriteLock();
private Lock rlock = lock.readLock();
private Lock wlock = lock.writeLock();
public UseThread(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public GoodsInfo getNum() {
rlock.lock();
try {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.goodsInfo;
} finally {
rlock.unlock();
}
}
@Override
public void setNum(int number) {
wlock.lock();
try {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
goodsInfo.changeNumber(number);
}finally {
wlock.unlock();
}
}
}
public class BusiApp {
static final int readWriteRatio = 10;//讀寫線程的比例
static final int minthreadCount = 3;//最少線程數(shù)
//讀操作
private static class GetThread implements Runnable {
private GoodsService goodsService;
public GetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i=0;i<100;i++){//操作100次
goodsService.getNum();
}
System.out.println(Thread.currentThread().getName()+"讀取商品數(shù)據(jù)耗時:"
+(System.currentTimeMillis()-start)+"ms");
}
}
//寫操作
private static class SetThread implements Runnable {
private GoodsService goodsService;
public SetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
long start = System.currentTimeMillis();
Random r = new Random();
for(int i=0;i<10;i++){//操作10次
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
goodsService.setNum(r.nextInt(10));
}
System.out.println(Thread.currentThread().getName()
+"寫商品數(shù)據(jù)耗時:"+(System.currentTimeMillis()-start)+"ms---------");
}
}
public static void main(String[] args) {
GoodsInfo goodsInfo = new GoodsInfo("Cup",100000,10000);
GoodsService goodsService = new UseThread(goodsInfo);
for(int i = 0;i<minthreadCount;i++){
Thread setT = new Thread(new SetThread(goodsService));
for(int j=0;j<readWriteRatio;j++) {
Thread getT = new Thread(new GetThread(goodsService));
getT.start();
}
try {
TimeUnit.MILLISECONDS.sleep(100);
setT.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Condition接口
任意一個Java對象,都擁有一組監(jiān)視器方法(定義在java.lang.Object上)兆旬,主要包括wait()假抄、wait(long timeout)、notify()以及notifyAll()方法丽猬,這些方法與synchronized同步關鍵字配合宿饱,可以實現(xiàn)等待/通知模式。Condition接口也提供了類似Object的監(jiān)視器方法脚祟,與Lock配合可以實現(xiàn)等待/通知模式谬以。
用Lock和Condition實現(xiàn)等待通知