java 多線程學習筆記------(3)線程同步
前提:
併發:同一個對象多個線程同時操作
處理多線程問題時,多個線程訪問同一個對象,並且某些線程還想修改這個對象。 這時候,我們就需要用到“線程同步”。 線程同步其實就是一種等待機制,多個需要同時訪問此對象的線程進入這個象的等待池形成隊列,等待前面的線程使用完畢後,下一個線程再使用。
由於同一進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突的問題。爲了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制(synchronized),當一個線程獲得對象的排它鎖,獨佔資源, 其他線程必須等待,使用後釋放鎖即可。
存在以下問題:
• 一個線程持有鎖會導致其它所有需要此鎖的線程掛起;
• 在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時, 引起性能問題;
• 如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先倒置,引起性能問題。
由於我們可以通過 private關鍵字來保證數據對象只能被方法訪問,所以我們只需針對方法提出一套機制,這套機制就是synchronized關鍵字它包括兩種用法synchronized方法synchronized塊。
• 同步方法
public synchronized void method(int args) {}
synchronized方法控制對“成員變量|類變量”對象的訪問:每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法 的對象的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨佔 該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲 得該鎖,重新進入可執行狀態。
缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率。
• 同步塊: synchronized (obj){ },obj稱之爲同步監視器
• obj可以是任何對象,但是推薦使用共享資源作爲同步監視器
• 同步方法中無需指定同步監視器,因爲同步方法的同步監視器是this即該對象本身,或class即類的模子
• 同步監視器的執行過程
• 第一個線程訪問,鎖定同步監視器,執行其中代碼
• 第二個線程訪問,發現同步監視器被鎖定,無法訪問
• 第一個線程訪問完畢,解鎖同步監視器
• 第二個線程訪問,發現同步監視器未鎖,鎖定並訪問
死鎖:
多個線程各自佔有一些共享資源,並且互相等待其他線程佔有的資源才能進行,而導致兩個 或者多個線程都在等待對方釋放資源,都停止執 行的情形。某一個同步塊同時擁有“兩個以上對 象的鎖”時,就可能會發生“死鎖”的問題。
有無synchronized比較
無synchronized,線程不安全
package Thread;
/**
* @author 賭徒
* 取票線程不安全
*/
public class UnsafeTest01 {
public static void main(String[] args) {
//一份資源
UnsafeWeb12306 web=new UnsafeWeb12306();
//多個代理
new Thread(web,"張三").start();
new Thread(web,"李四").start();
new Thread(web,"王二麻子").start();
}
}
class UnsafeWeb12306 implements Runnable{
//票數
private int ticketNums=10;
//結束條件
private boolean flag=true;
@Override
public void run() {
while(flag) {
test();
}
}
//線程不安全
public void test() {
if (ticketNums<0) {
flag=false;
return;
}
//模擬延時
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
package Thread;
/**
* @author 賭徒
*取錢線程不安全
*/
public class UnsafeTest02 {
public static void main(String[] args) {
Account account=new Account("賬戶", 100);
Drawing zhang=new Drawing(account, 80, "張三");
Drawing li=new Drawing(account, 90, "李四");
new Thread(zhang).start();
new Thread(li).start();
}
}
//模擬賬戶
class Account {
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
//模擬取錢
class Drawing extends Thread{
Account account;//取錢賬戶
int drawingmoney;//取錢數
int packetmoney;//口袋錢數
public Drawing(Account account, int drawingmoney,String name) {
super(name);
this.account = account;
this.drawingmoney = drawingmoney;
}
@Override
public void run() {
if (account.money-drawingmoney==0) {
return;
}
account.money-=drawingmoney;
packetmoney+=drawingmoney;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->賬戶餘額"+account.money);
System.out.println(Thread.currentThread().getName()+"-->口袋餘額"+packetmoney);
}
}
有synchronized,線程相對安全
package Thread;
/**
* @author 賭徒
*
* 線程安全:在併發時保證數據的正確性,效率儘可能高
*/
public class SynchronizedTest01 {
public static void main(String[] args) {
//一份資源
safeWeb12306 web=new safeWeb12306();
//多個代理
new Thread(web,"張三").start();
new Thread(web,"李四").start();
new Thread(web,"王二麻子").start();
}
}
class safeWeb12306 implements Runnable{
//票數
private int ticketNums=10;
//結束條件
private boolean flag=true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
test1();
}
}
//線程安全(同步)
public synchronized void test() {
if (ticketNums<=0) {
flag=false;
return;
}
//模擬延時
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
//線程安全(同步塊)
public void test1() {
if (ticketNums<=0) {//考慮沒票
flag=false;
return;
}
synchronized(this) {
if (ticketNums<=0) {//考慮一張票
flag=false;
return;
}
//模擬延時
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
}
package Thread;
/**
* @author 賭徒
*同步塊
*/
public class SynchronizedTest02 {
public static void main(String[] args) {
Account1 account=new Account1("賬戶", 100);
Drawing1 zhang=new Drawing1(account, 80, "張三");
Drawing1 li=new Drawing1(account, 90, "李四");
zhang.start();
li.start();
}
}
//模擬賬戶
class Account1 {
String name;
int money;
public Account1(String name, int money) {
this.name = name;
this.money = money;
}
}
//模擬取錢
class Drawing1 extends Thread{
Account1 account;//取錢賬戶
int drawingmoney;//取錢數
int packetmoney;//口袋錢數
public Drawing1(Account1 account, int drawingmoney,String name) {
super(name);
this.account = account;
this.drawingmoney = drawingmoney;
}
@Override
public void run() {
if (account.money<=0) {
return;
}
//同步塊
synchronized (account) {
if (account.money-drawingmoney<0) {
return;
}
account.money-=drawingmoney;
packetmoney+=drawingmoney;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->賬戶餘額"+account.money);
System.out.println(Thread.currentThread().getName()+"-->口袋餘額"+packetmoney);
}
}
}
死鎖
package Thread;
/**
* 死鎖:過多的同步可能造成相互不釋放資源
* 從而相互等待,一般發生於同步中持有多個對象的鎖
* @author 賭徒
* 解決:避免鎖套鎖
*
*/
public class DeadLock {
public static void main(String[] args) {
Markup markup1=new Markup(1, "張三");
Markup markup2=new Markup(0, "李四");
markup1.start();
markup2.start();
}
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
//化妝
class Markup extends Thread{
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
//選擇
int choice;
//名字
String nameString;
public Markup(int choice,String nameString) {
this.nameString=nameString;
this.choice = choice;
}
public void run() {
//化妝
markup();
}
//相互持有對方的對象鎖:可能造成死鎖
private void markup() {
if (choice==0) {
synchronized (lipstick) {//獲得口紅的鎖
System.out.println(this.nameString+"獲得口紅");
//一秒後想獲得鏡子的鎖
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized (mirror) {//獲得鏡子的鎖
// System.out.println(this.nameString+"獲得鏡子");
// }
}
synchronized (mirror) {//獲得鏡子的鎖
System.out.println(this.nameString+"獲得鏡子");
}
}else {
synchronized (mirror) {//獲得鏡子的鎖
System.out.println(this.nameString+"獲鏡子");
//二秒後想獲得口紅的鎖
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized (lipstick) {//獲得口紅的鎖
// System.out.println(this.nameString+"獲得口紅");
// }
}
synchronized (lipstick) {//獲得口紅的鎖
System.out.println(this.nameString+"獲得口紅");
}
}
}
}