1、多線程安全問題 2、等待喚醒機制 ...
今日內容介紹
1、多線程安全問題
2、等待喚醒機制
01線程操作共用數據的安全問題
*A:線程操作共用數據的安全問題
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。
程式每次運行結果和單線程運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是線程安全的。
02售票的案例
*A:售票的案例
/*
* 多線程併發訪問同一個數據資源
* 3個線程,對一個票資源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//創建Runnable介面實現類對象
Tickets t = new Tickets();
//創建3個Thread類對象,傳遞Runnable介面實現類
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
public class Tickets implements Runnable{
//定義出售的票源
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
if( ticket > 0){
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
}
03線程安全問題引發
*A:線程安全問題引發
/*
* 多線程併發訪問同一個數據資源
* 3個線程,對一個票資源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//創建Runnable介面實現類對象
Tickets t = new Tickets();
//創建3個Thread類對象,傳遞Runnable介面實現類
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
/*
* 通過線程休眠,出現安全問題
*/
public class Tickets implements Runnable{
//定義出售的票源
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
//對票數判斷,大於0,可以出售,變數--操作
if( ticket > 0){
try{
Thread.sleep(10); //加了休眠讓其他線程有執行機會
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
}
04同步代碼塊解決線程安全問題
*A:同步代碼塊解決線程安全問題
*A:售票的案例
/*
* 多線程併發訪問同一個數據資源
* 3個線程,對一個票資源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//創建Runnable介面實現類對象
Tickets t = new Tickets();
//創建3個Thread類對象,傳遞Runnable介面實現類
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
/*
* 通過線程休眠,出現安全問題
* 解決安全問題,Java程式,提供技術,同步技術
* 公式:
* synchronized(任意對象){
* 線程要操作的共用數據
* }
* 同步代碼塊
*/
public class Tickets implements Runnable{
//定義出售的票源
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
//線程共用數據,保證安全,加入同步代碼塊
synchronized(obj){
//對票數判斷,大於0,可以出售,變數--操作
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
}
}
05同步代碼塊的執行原理
A:同步代碼塊的執行原理
同步代碼塊: 在代碼塊聲明上 加上synchronized
synchronized (鎖對象) {
可能會產生線程安全問題的代碼
}
同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。
06同步的上廁所原理
*A:同步的上廁所原理
a:不使用同步:線程在執行的過程中會被打擾
線程比喻成人
線程執行代碼就是上一個廁所
第一個人正在上廁所,上到一半,被另外一個人拉出來
b:使用同步:
線程比喻成人
線程執行代碼就是上一個廁所
鎖比喻成廁所門
第一個人上廁所,會鎖門
第二個人上廁所,看到門鎖上了,等待第一個人上完再去上廁所
07同步方法
*A:同步方法:
/*
* 多線程併發訪問同一個數據資源
* 3個線程,對一個票資源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//創建Runnable介面實現類對象
Tickets t = new Tickets();
//創建3個Thread類對象,傳遞Runnable介面實現類
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
*A:同步方法
/*
* 採用同步方法形式,解決線程的安全問題
* 好處: 代碼簡潔
* 將線程共用數據,和同步,抽取到一個方法中
* 在方法的聲明上,加入同步關鍵字
*
* 問題:
* 同步方法有鎖嗎,肯定有,同步方法中的對象鎖,是本類對象引用 this
* 如果方法是靜態的呢,同步有鎖嗎,絕對不是this
* 鎖是本類自己.class 屬性
* 靜態方法,同步鎖,是本類類名.class屬性
*/
public class Tickets implements Runnable{
//定義出售的票源
private int ticket = 100;
public void run(){
while(true){
payTicket();
}
}
public synchronized void payTicket(){
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
08JDK1.5新特性Lock介面
*A:JDK1.5新特性Lock介面
查閱API,查閱Lock介面描述,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
Lock介面中的常用方法
void lock()
void unlock()
Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。
我們使用Lock介面,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket
09Lock介面改進售票案例
*A:Lock介面改進售票案例
/*
* 多線程併發訪問同一個數據資源
* 3個線程,對一個票資源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//創建Runnable介面實現類對象
Tickets t = new Tickets();
//創建3個Thread類對象,傳遞Runnable介面實現類
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
/*
* 使用JDK1.5 的介面Lock,替換同步代碼塊,實現線程的安全性
* Lock介面方法:
* lock() 獲取鎖
* unlock()釋放鎖
* 實現類ReentrantLock
*/
public class Tickets implements Runnable{
//定義出售的票源
private int ticket = 100;
//在類的成員位置,創建Lock介面的實現類對象
private Lock lock = new ReentrantLock();
public void run(){
while(true){
//調用Lock介面方法lock獲取鎖
lock.lock();
//對票數判斷,大於0,可以出售,變數--操作
if( ticket > 0){
try{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}catch(Exception ex){
}finally{
//釋放鎖,調用Lock介面方法unlock
lock.unlock();
}
}
}
}
}
10線程的死鎖原理
*A:線程的死鎖原理
當線程任務中出現了多個同步(多個鎖) 時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程式出現無限等待,
這種現象我們稱為死鎖。這種情況能避免就 避免掉。
synchronzied(A鎖){
synchronized(B鎖){
}
}
11線程的死鎖代碼實現
*A:線程的死鎖代碼實現
public class DeadLock implements Runnable{
private int i = 0;
public void run(){
while(true){
if(i%2==0){
//先進入A同步,再進入B同步
synchronized(LockA.locka){
System.out.println("if...locka");
synchronized(LockB.lockb){
System.out.println("if...lockb");
}
}
}else{
//先進入B同步,再進入A同步
synchronized(LockB.lockb){
System.out.println("else...lockb");
synchronized(LockA.locka){
System.out.println("else...locka");
}
}
}
i++;
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock dead = new DeadLock();
Thread t0 = new Thread(dead);
Thread t1 = new Thread(dead);
t0.start();
t1.start();
}
}
public class LockA {
private LockA(){}
public static final LockA locka = new LockA();
}
public class LockB {
private LockB(){}
public static final LockB lockb = new LockB();
}
### 12線程等待與喚醒案例介紹
*A:線程等待與喚醒案例介紹
等待喚醒機制所涉及到的方法:
wait() :等待,將正在執行的線程釋放其執行資格 和 執行權,並存儲到線程池中。
notify():喚醒,喚醒線程池中被wait()的線程,一次喚醒一個,而且是任意的。
notifyAll(): 喚醒全部:可以將線程池中的所有wait() 線程都喚醒。
其實,所謂喚醒的意思就是讓 線程池中的線程具備執行資格。必須註意的是,這些方法都是在 同步中才有效。同時這些方法在使用
時必須標明所屬鎖,這樣才可以明確出這些方法操作的到底是哪個鎖上的線程。
13線程等待與喚醒案例資源類編寫
*A:線程等待與喚醒案例資源類編寫
/*
* 定義資源類,有2個成員變數
* name,sex
* 同時有2個線程,對資源中的變數操作
* 1個對name,age賦值
* 2個對name,age做變數的輸出列印
*/
public class Resource {
public String name;
public String sex;
}
14線程等待與喚醒案例輸入和輸出線程
A:線程等待與喚醒案例輸入和輸出線程
/*
* 輸入的線程,對資源對象Resource中成員變數賦值
* 一次賦值 張三,男
* 下一次賦值 lisi,nv
*/
public class Input implements Runnable {
private Resource r=new Resource();
public void run() {
int i=0;
while(true){
if(i%2==0){
r.name="張三";
r.sex="男";
}else{
r.name="lisi";
r.sex="女";
}
i++;
}
}
}
/*
* 輸出線程,對資源對象Resource中成員變數,輸出值
*/
public class Output implements Runnable {
private Resource r=new Resource() ;
public void run() {
while(true){
System.out.println(r.name+"..."+r.sex);
}
}
}
15線程等待與喚醒案例測試類
A:線程等待與喚醒案例測試類
/*
* 開啟輸入線程和輸出線程,實現賦值和列印值
*/
public class ThreadDemo{
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input();
Output out = new Output();
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
16線程等待與喚醒案例null值解決
A:線程等待與喚醒案例null值解決
/*
* 輸入的線程,對資源對象Resource中成員變數賦值
* 一次賦值 張三,男
* 下一次賦值 lisi,nv
*/
public class Input implements Runnable {
private Resource r;
public Input(Resource r){
this.r=r;
}
public void run() {
int i=0;
while(true){
if(i%2==0){
r.name="張三";
r.sex="男";
}else{
r.name="lisi"
r.sex="女"
}
i++;
}
}
}
/*
* 輸出線程,對資源對象Resource中成員變數,輸出值
*/
public class Output implements Runnable {
private Resource r;
public Output(Resource r){
this.r=r;
}
public void run() {
while(true){
System.out.println(r.name+"..."+r.sex);
}
}
}
}
/*
* 開啟輸入線程和輸出線程,實現賦值和列印值
*/
public class ThreadDemo{
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
17線程等待與喚醒案例數據安全解決
A:線程等待與喚醒案例數據安全解決
/*
* 輸入的線程,對資源對象Resource中成員變數賦值
* 一次賦值 張三,男
* 下一次賦值 lisi,nv
*/
public class Input implements Runnable {
private Resource r;
public Input(Resource r){
this.r=r;
}
public void run() {
int i=0;
while(true){
synchronized(r){
if(i%2==0){
r.name="張三";
r.sex="男";
}else{
r.name="lisi"
r.sex="女"
}
i++;
}
}
}
/*
* 輸出線程,對資源對象Resource中成員變數,輸出值
*/
public class Output implements Runnable {
private Resource r;
public Output(Resource r){
this.r=r;
}
public void run() {
while(true){
synchronized(r){
System.out.println(r.name+"..."+r.sex);
}
}
}
}
}
/*
* 開啟輸入線程和輸出線程,實現賦值和列印值
*/
public class ThreadDemo{
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
18線程等待與喚醒案例通信的分析
*A:線程等待與喚醒案例通信的分析
輸入:賦值後,執行方法wait()永遠等待
輸出:變數值列印輸出,在輸出等待之前,喚醒
輸入的notify(),自己在wait()永遠等待
輸入:被喚醒後,重新對變數賦值,賦值後,必須喚醒輸出的線程notify(),
自己的wait()
19線程等待與喚醒案例的實現
*A 線程等待與喚醒案例的實現
/*
* 定義資源類,有2個成員變數
* name,sex
* 同時有2個線程,對資源中的變數操作
* 1個對name,age賦值
* 2個對name,age做變數的輸出列印
*/
public class Resource {
public String name;
public String sex;
public boolean flag = false;
}
/*
* 輸入的線程,對資源對象Resource中成員變數賦值
* 一次賦值 張三,男
* 下一次賦值 lisi,nv
*/
public class Input implements Runnable {
private Resource r ;
public Input(Resource r){
this.r = r;
}
public void run() {
int i = 0 ;
while(true){
synchronized(r){
//標記是true,等待
if(r.flag){
try{r.wait();}catch(Exception ex){}
}
if(i%2==0){
r.name = "張三";
r.sex = "男";
}else{
r.name = "lisi";
r.sex = "nv";
}
//將對方線程喚醒,標記改為true
r.flag = true;
r.notify();
}
i++;
}
}
}
/*
* 輸出線程,對資源對象Resource中成員變數,輸出值
*/
public class Output implements Runnable {
private Resource r ;
public Output(Resource r){
this.r = r;
}
public void run() {
while(true){
synchronized(r){
//判斷標記,是false,等待
if(!r.flag){
try{r.wait();}catch(Exception ex){}
}
System.out.println(r.name+".."+r.sex);
//標記改成false,喚醒對方線程
r.flag = false;
r.notify();
}
}
}
}
/*
* 開啟輸入線程和輸出線程,實現賦值和列印值
*/
public class ThreadDemo{
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
作業測試
1、wait和sleep的區別
2、線程的生命周期(五中狀態的切換流程)
3、有一個抽獎池,該抽獎池中存放了獎勵的金額,該抽獎池用一個數組int[] arr = {10,5,20,50,100,200,500,800,2,80,300};
創建兩個抽獎箱(線程)設置線程名稱分別為“抽獎箱1”,“抽獎箱2”,隨機從arr數組中獲取獎項元素並列印在控制臺上,格式如下:
抽獎箱1 又產生了一個 10 元大獎
抽獎箱2 又產生了一個 100 元大獎
//.....
4、某公司組織年會,會議入場時有兩個入口,在入場時每位員工都能獲取一張雙色球彩票,假設公司有100個員工,利用多線程模擬年會入場過程,
並分別統計每個入口入場的人數,以及每個員工拿到的彩票的號碼。線程運行後列印格式如下:
編號為: 2 的員工 從後門 入場! 拿到的雙色球彩票號碼是: [17, 24, 29, 30, 31, 32, 07]
編號為: 1 的員工 從後門 入場! 拿到的雙色球彩票號碼是: [06, 11, 14, 22, 29, 32, 15]
//.....
從後門入場的員工總共: 13 位員工
從前門入場的員工總共: 87 位員工