上一篇獲得了三個喜歡和一個關(guān)注地来,給我了繼續(xù)寫下去的動力,謝謝熙掺,謝謝N窗摺!币绩!
以前沒寫過蜡秽,寫的時候發(fā)現(xiàn)真的很有挑戰(zhàn),自己也從中收獲很多缆镣,希望簡友們覺得如果覺得有一篇文章真的給自己一點感動芽突,一點幫助等,請給作者一個贊董瞻,可能真的會產(chǎn)生蝴蝶效應(yīng)寞蚌,再次謝謝你們看我的文章。
以上都是廢話钠糊,哈哈挟秤。接著上篇,繼續(xù)討論多線程的數(shù)據(jù)安全問題
- 為什么會出現(xiàn)重復(fù)的數(shù)據(jù)和負數(shù)的數(shù)據(jù)的情況抄伍?
因為多線程操作的是共享數(shù)據(jù)艘刚,mTicket,是三個線程的共享數(shù)據(jù)截珍。如果每個線程都有自己單獨的數(shù)據(jù)昔脯,是沒有問題啄糙,因為那是單線程了。
重復(fù)的數(shù)據(jù):System.out.println(Thread.currentThread().getName() + " 出售車票= " + mTicket-- + " 張");這行代碼翻譯成計算機可以執(zhí)行的指令云稚,當0號線執(zhí)行sop()隧饼,輸出7,但是剛要執(zhí)行--操作的時候静陈。1號線線程搶到了CPU的執(zhí)行權(quán)燕雁,這時候0號線程沒有執(zhí)行 -- 操作,所以mTicket 還是7鲸拥,所以輸出7拐格,出現(xiàn)重復(fù)的數(shù)據(jù)。
負數(shù)的數(shù)據(jù):當mTicket =1時刑赶,0號線程做判斷 > 1,進入sleep(10)捏浊,放棄執(zhí)行資格和執(zhí)行權(quán),1號線程判斷 >0,進去進入sleep(10)撞叨,這時候0號線程醒來搶到執(zhí)行資格金踪,輸入0,1號線程醒來牵敷,搶到執(zhí)行資格胡岔,輸入-1。
- 怎么解決多線程操作共享數(shù)據(jù)的問題呢枷餐?
- 同步代碼塊 格式 : synchronized(對象){ 需要同步的代碼}
把多個語句操作共享數(shù)據(jù)的代碼給鎖起來靶瘸,讓任意時刻只能有一個線程執(zhí)行。
public class RunnableDemo1 implements Runnable {
//定義一百張車票
private int mTicket = 100;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
if(mTicket > 0) {
// 模擬卡機 讓線程休眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 出售車票= " + mTicket-- + " 張");
}
}
}
}
}
用同步代碼塊就可以解決毛肋,這里主要是那個obj怨咪,多線程同步的時候,必須是同一把鎖润匙。要不然還會出現(xiàn)共享數(shù)據(jù)的問題诗眨。
- 同步方法:就是把關(guān)鍵字synchronized 加在方法上
public class RunnableDemo implements Runnable {
//定義一百張車票
private int mTicket = 100;
@Override
public void run() {
while (true) {
sale();
}
}
private synchronized void sale() {
if (mTicket > 0) {
// 模擬卡機 讓線程休眠10毫秒
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + " 出售車票= " + mTicket-- + " 張");
}
}
}
非靜態(tài)方法的鎖是本類對象的引用 this。
public class RunnableDemo1 implements Runnable {
//定義一百張車票
private static int mTicket = 100;
@Override
public void run() {
while (true) {
sale();
}
}
private synchronized static void sale() {
if (mTicket > 0) {
// 模擬卡機 讓線程休眠10毫秒
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + " 出售車票= " + mTicket-- + " 張");
}
}
}
靜態(tài)方法的鎖是本類對象的class 字節(jié)碼文件趁桃。
- 總結(jié)下:
- 同步的前提
多個線程使用的是同一個鎖對象
多個線程操作共享資源 - 同步的好處
同步的出現(xiàn)解決了多線程的安全問題辽话。 - 同步的弊端
當線程相當多時肄鸽,因為每個線程都會去判斷同步上的鎖卫病,這是很耗費資源的,無形中會降低程序的運行效率典徘,但是為了數(shù)據(jù)的安全也值了蟀苛,魚和熊掌不能兼得。
- 兩個線程實現(xiàn)交替打哟濉(等待與喚醒機制)
/**
* 兩個線程 實現(xiàn)交替打印 一
* <p>
* 輸入線程 輸出 一
* <p>
* 輸出線程 輸出 二
*/
public class AlternatePrintThread {
public static boolean mFlag = true;
public static void main(String[] args) {
new Thread(new InputThread()).start();
new Thread(new OutputThread()).start();
}
}
class InputThread implements Runnable {
@Override
public void run() {
while (true) {
synchronized (AlternatePrintThread.class) {
if (AlternatePrintThread.mFlag) {
try {
AlternatePrintThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
AlternatePrintThread.mFlag = true;
System.out.println("我是一 ");
AlternatePrintThread.class.notify();
}
}
}
}
class OutputThread implements Runnable {
@Override
public void run() {
while (true) {
synchronized (AlternatePrintThread.class) {
if (!AlternatePrintThread.mFlag) {
try {
AlternatePrintThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
AlternatePrintThread.mFlag = false;
System.out.println(".......我是二 ");
AlternatePrintThread.class.notify();
}
}
}
}
實現(xiàn)思路:
交替打印肯定是兩個線程 定義兩個線程類
不管你用的什么對象作為鎖帜平,連個線程一定是同一把鎖
- 多生產(chǎn)與多消費
public class ThreadDemo {
public static void main(String[] args) {
Product p = new Product();
new Thread(new Produce(p)).start();
new Thread(new Produce(p)).start();
new Thread(new Produce(p)).start();
new Thread(new Produce(p)).start();
new Thread(new Consumer(p)).start();
new Thread(new Consumer(p)).start();
new Thread(new Consumer(p)).start();
new Thread(new Consumer(p)).start();
}
}
class Product {
//名字
private String name;
//計數(shù)器
private int count;
//標記
private boolean flag = true;
//生產(chǎn)方法幽告,是讓生產(chǎn)線程調(diào)用
public synchronized void set(String name) {
while (!flag)
try {
this.wait();
} catch (Exception e) {
}
this.name = name + count++;
System.out.println(Thread.currentThread().getName() + "生產(chǎn)第 " + this.name);
flag = false;
this.notify();
}
//消費方法,是讓消費線程調(diào)用
public synchronized void get() {
while (flag)
try {
this.wait();
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "消費第... " + this.name);
flag = true;
this.notify();
}
}
//生產(chǎn)者線程
class Produce implements Runnable {
private Product p;
Produce(Product p) {
this.p = p;
}
public void run() {
while (true)
p.set("Iphone");
}
}
//消費者線程
class Consumer implements Runnable {
private Product p;
Consumer(Product p) {
this.p = p;
}
public void run() {
while (true)
p.get();
}
}
notify():喚醒的是等待順序喚醒(這是的 多生產(chǎn)與多消費不能用這個)
notifyAll():喚醒的是等待的全部線程 (太浪費資源裆甩,如果我想喚醒 A線程的按等待順序的線程的一個 就好了 冗锁,所以Lock接口來了)
線程還有一個特點就是 從那倒下去的 從那起來繼續(xù)執(zhí)行(要想讓一段代碼流轉(zhuǎn)起來,那就是死循環(huán)嗤栓,滿足條件才讓出去冻河,否則一直轉(zhuǎn)圈)
- jdk since 1.5出現(xiàn)行的管理線程的類
/**
* since JDK1.5 Lock ,Condition 生產(chǎn)者 和 消費者
*/
public class LockDemo {
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(new ConsumeThread(resource)).start();
new Thread(new ConsumeThread(resource)).start();
new Thread(new ConsumeThread(resource)).start();
new Thread(new ConsumeThread(resource)).start();
new Thread(new ProductThread(resource)).start();
new Thread(new ProductThread(resource)).start();
new Thread(new ProductThread(resource)).start();
new Thread(new ProductThread(resource)).start();
}
}
/**
* 資源類
*/
class Resource {
private String productName;
private int count = 0;
private boolean flag = true;
private Lock lock = new ReentrantLock();
// 用鎖創(chuàng)建了 兩個線程的管理器 用自己的管理器去讓自己的管理的線程等待 用對方的管理器去喚醒對方等待的線程
private Condition pro = lock.newCondition();
private Condition con = lock.newCondition();
public void setProductName(String productName) {
lock.lock();
while (!this.flag) {
try {
pro.await();//自己的線程管理器
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.productName = productName + " " + count++;
System.out.println("生產(chǎn)=" + this.productName);
this.flag = false;// 和 while (this.flag) 方法必須向反 要不 這個線程會無限等待
con.signal();//對方的線程管理器
lock.unlock();
}
public void getProductName() {
lock.lock();
while (this.flag) {
try {
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消費=......" + this.productName);
this.flag = true;
pro.signal();
lock.unlock();
}
}
/**
* 生產(chǎn)線程
*/
class ProductThread implements Runnable {
Resource mResource;
public ProductThread(Resource resource) {
this.mResource = resource;
}
@Override
public void run() {
while (true) {
mResource.setProductName("Iphone100");
}
}
}
/**
* 消費線程
*/
class ConsumeThread implements Runnable {
Resource mResource;
public ConsumeThread(Resource resource) {
this.mResource = resource;
}
@Override
public void run() {
while (true) {
mResource.getProductName();
}
}
}
JDK1.5中茉帅,java.util.concurrent.locks包
提供線程的新的管理方式
接口Lock叨叙,替代了原有的synchronized的使用,使用靈活廣泛
接口中的方法 獲取鎖lock() 釋放鎖 unlock()
接口實現(xiàn)類[ReentrantLock]
接口Condition 替代原有監(jiān)視器方法 wait notify notifyAll
新舊方法的對比
接口中Condition Object類
await() wait()
signal() notify()
signalAll() notifyAll()
獲取接口的實現(xiàn)類對象堪澎,用Lock接口方法newCondition實現(xiàn)
分解成截然不同對象(線程管理對象)
- 死鎖
/**
* 多線程的死鎖
*/
public class DeadLockDemo {
public static class DeadRunnable implements Runnable {
private boolean mFlag;
public DeadRunnable(boolean flag) {
this.mFlag = flag;
}
@Override
public void run() {
while (true) {
if (mFlag) {
//A鎖
synchronized (ALock.mALock) {
System.out.println("A..............鎖");
synchronized (BLock.mBLock) {
System.out.println("B..............鎖");
}
}
} else {
//B鎖
synchronized (BLock.mBLock) {
System.out.println("B..............鎖");
synchronized (ALock.mALock) {
System.out.println("A..............鎖");
}
}
}
}
}
}
/**
* A鎖
*/
public static class ALock {
public static final ALock mALock = new ALock();
}
/**
* B鎖
*/
public static class BLock {
public static final BLock mBLock = new BLock();
}
public static void main(String[] args) {
new Thread(new DeadRunnable(true)).start();
new Thread(new DeadRunnable(false)).start();
}
}
多線程的一種程序的假死狀態(tài)擂错,同步的嵌套,停了樱蛤,但是沒退出钮呀,出現(xiàn)在多線程爭搶同一個同步鎖的時候,才會出現(xiàn)