這些是筆者學(xué)習(xí)Java基礎(chǔ)時的筆記,供后來者參考(學(xué)習(xí)是持之以恒的的事情瀑梗,一定要堅(jiān)持喲烹笔,切記!切記E桌觥0啊!)
1亿鲜、線程安全問題
問題描述:當(dāng)多個窗口(多個線程)同時售票允蜈,票為共享數(shù)據(jù)源;當(dāng)不對窗口(線程)進(jìn)行限制的時候,就容易造成線程安全問題饶套。
//下面的例子
public class Ticket implements Runnable {
private static int num = 100;
@Override
public void run() {
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"...sale..."+num);
num--漩蟆;
}else{
break;
}
}
}
}
public static void main(String[] args){
Ticket ticket = new Ticket();
Thread thread = new Thread(ticket);
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread.start();
thread1.start();
thread2.start();
}
此時,運(yùn)行代碼妓蛮,可能產(chǎn)生重票和跳票的問題怠李,這就是多個線程同時操作相同資源,造成的線程不安全問題蛤克。
2捺癞、解決線程安全問題(使用同步機(jī)制)
(1)類鎖和對象鎖:
1、類鎖:在代碼中的方法加static和synchronized的鎖咖耘,或者synchroonized(xxx.class)
2翘簇、對象鎖:在代碼中的非靜態(tài)方法加了synchronized的鎖,或者synchronized(this)的代碼段儿倒;
3版保、私有鎖:在類內(nèi)部聲明一個私有屬性如private Object lock,在需要加鎖的代碼段synchronized(lock)
(2)同步代碼塊:
synchronized(鎖){ //鎖可以為任意對象夫否,但是需要保證多線程用的是同一把鎖
對同一個資源的操作語句彻犁。
}
(3)同步方法:
<1>同步方法的鎖:this;
public class Ticket implements Runnable {
private static int num = 100;
@Override
public void run() {
while(true){
synchronized (this){
if(num > 0){
System.out.println(Thread.currentThread().getName()+"...sale..."+num);
num--;
}else{
break;
}
}
}
}
}
<2>靜態(tài)同步方法的鎖:類名.class凰慈,使用synchronized修飾需要同步的方法汞幢;
public class Ticket implements Runnable {
private static int num = 100;
@Override
public void run() {
while(num>0){
printTicket();
}
}
private synchronized void printTicket(){
if(num > 0){
System.out.println(Thread.currentThread().getName()+"...sale..."+num);
num--;
}
}
}
3、死鎖問題
同步機(jī)制解決了線程安全問題微谓,同時也可能引入新的問題:線程死鎖問題森篷。
問題描述:兩個線程操作同一個資源,但是在該資源上添加了兩個鎖A豺型,B构韵,當(dāng)其中一個線程獲取了鎖A联贩,同時另一個線程獲取了鎖B珍剑,這時就造成了線程死鎖缕溉。
所以在平常開發(fā)過程中,為避免死鎖肴焊,就要盡量避免同步代碼塊的嵌套前联。
死鎖實(shí)例:
需求描述:用程序來描述以下情況:一手交錢一手交貨。商家與顧客兩人都是很小氣的人娶眷,顧客買商家的東西似嗤,商家收顧客的前,顧客說:先給我貨我再給你錢届宠;商家說:先給我錢我再給你貨烁落。最好的結(jié)局就是各自得到各自的東西壳咕。
代碼:
public class Customer extends Thread {
public static Object money = new Object();
@Override
public void run() {
synchronized (money){
System.out.println("客戶等商家給貨");
synchronized (Seller.goods){
System.out.println("客戶給商家錢");
}
}
}
}
public class Seller extends Thread {
public static Object goods = new Object();
@Override
public void run() {
synchronized (goods){
System.out.println("商家等客戶給錢");
synchronized (Customer.money){
System.out.println("商家給客戶活");
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args){
Thread t1 = new Customer();
Thread t2 = new Seller();
t1.start();
t2.start();
}
}
4、等待喚醒機(jī)制
(1)當(dāng)多個線程執(zhí)行相同的任務(wù)顽馋,操作相同在資源的時候谓厘,使用加鎖機(jī)制,同步線程沒有問題寸谜。但當(dāng)多線程的執(zhí)行任務(wù)不同時竟稳,加鎖機(jī)制就失效了;這時我們就使用等待喚醒機(jī)制熊痴,完成線程同步他爸。
(2)問題線程(兩個問題,一讀寫資源不同步果善,二诊笤、線程不是交替執(zhí)行(不是生產(chǎn)消費(fèi)模型)):
public class Resource {
String name;
String sex;
}
public class Input implements Runnable{
Resource r;
public Input(Resource r){
this.r = r;
}
@Override
public void run() {
int x=0;
while(true){
if(x == 0){
r.name = "張三";
r.sex = "男";
}else{
r.name = "小紅";
r.sex = "女";
}
x=(x+1)%2;
}
}
}
public class Output implements Runnable{
Resource r;
public Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
System.out.println("---------name:"+r.name+" sex:"+r.sex);
}
}
}
public class ThreadDemo2 {
public static void main(String[] args){
Resource resource = new Resource();
Thread thread = new Thread(new Input(resource));
Thread thread1 = new Thread(new Output(resource));
thread.start();
thread1.start();
}
}
(3)等待喚醒機(jī)制(必須在相同的鎖中,等待喚醒機(jī)制才能有效)
wait():放棄線程執(zhí)行權(quán)巾陕,線程進(jìn)入線程池
notify():喚醒線程池中任意一個線程
notifyAll():將線程池中所有線程都喚醒
優(yōu)化后的代碼:
public class Resource {
String name;
String sex;
boolean flag;
public synchronized void set(String name,String sex){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void print(){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("---------name:"+name+" sex:"+sex);
flag = false;
this.notify();
}
}
public class Input implements Runnable{
Resource r;
public Input(Resource r){
this.r = r;
}
@Override
public void run() {
int x=0;
while(true){
if(x == 0){
r.name = "張三";
r.sex = "男";
}else{
r.name = "小紅";
r.sex = "女";
}
x=(x+1)%2;
}
}
}
public class Output implements Runnable{
Resource r;
public Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
r.print();
}
}
}
(4)多生產(chǎn)多消費(fèi)者的問題
<1>當(dāng)前等待線程被喚醒的時候讨跟,沒有進(jìn)行標(biāo)記的判斷,所以會多生產(chǎn)和喚醒本方線程(生產(chǎn)者喚醒生產(chǎn)者鄙煤,消費(fèi)者
喚醒消費(fèi)者)的可能晾匠。為了解決這個問題,我們將條件判斷梯刚,換成while循環(huán)凉馆,將notify替換為notifyAll(),問題解決。
(5)線程的結(jié)束
<1>線程的stop方法已經(jīng)過時亡资,結(jié)束線程的一種方法是澜共,將循環(huán)的標(biāo)記設(shè)置為false;線程自動結(jié)束锥腻。
<2>當(dāng)線程的標(biāo)記讀不到時(即線程處于凍結(jié)狀態(tài)的時候)嗦董,調(diào)用interrupt()函數(shù)喚醒處于wait()、sleep()等凍結(jié)狀態(tài)
的線程旷太,讓其去讀取標(biāo)記展懈,結(jié)束線程销睁。
(6)setDaemon()守護(hù)線程
該方法必須在start方法調(diào)用之前調(diào)用供璧。將參數(shù)設(shè)置為true時,線程就變成了守護(hù)線程冻记,被守護(hù)線程結(jié)束睡毒,守護(hù)線程
自動結(jié)束。
(7)join()方法
當(dāng)某個線程調(diào)用改方法的時候冗栗,改線程可以搶奪其他線程的執(zhí)行權(quán)演顾;被中段的線程需要等待程A執(zhí)行終止完成供搀,才被喚醒。
(通俗一點(diǎn)說钠至,join方法是阻塞的調(diào)用該方法的線程,當(dāng)被調(diào)用線程結(jié)束之后執(zhí)行)葛虐。
(8)setPriority() 設(shè)置線程的優(yōu)先級(取值范圍1~10)
(9)yield(),臨時釋放執(zhí)行權(quán)棉钧。
(10)wait()方法和sleep()方法的區(qū)別:
<1>兩個方法可以讓線程處于凍結(jié)狀態(tài)屿脐;
<2>sleep()必須指定時間,wait()方法可以指定時間宪卿,也可以不指定時間的诵;
<3>sleep()會釋放執(zhí)行權(quán),不會釋放鎖佑钾。wait()會釋放執(zhí)行權(quán)西疤,同時也會釋放鎖;
(11)多生產(chǎn)者等待喚醒新寫法:
public class Product {
private int number=0; //產(chǎn)品編號
private boolean flag; //等待喚醒標(biāo)簽
private Lock lock = new ReentrantLock();
private Condition proCon = lock.newCondition();
private Condition cusCon = lock.newCondition();
public void get(){
lock.lock();
try{
while (flag){
try {
proCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println("生產(chǎn)者:"+Thread.currentThread()+"..生產(chǎn)產(chǎn)品:"+number);
flag = true;
cusCon.signal();
}finally{
lock.unlock();
}
}
public void product(){
lock.lock();
try{
while (!flag){
try {
cusCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消費(fèi)者:"+Thread.currentThread()+"..消費(fèi)產(chǎn)品:"+number);
flag = false;
proCon.signal();
}finally{
lock.unlock();
}
}
}