18、多線程 (線程安全、線程同步、等待喚醒機制、單例設計模式)
阿新 • • 發佈:2019-02-05
正在執行 喚醒 數據資源 線程等待 rgs 上一個 註意 current ring
線程操作共享數據的安全問題
*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(); } } 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--); } } } }
線程安全問題引發?
*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--); } } } }
同步代碼塊解決線程安全問題
*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--); } } } } }
同步代碼塊的執行原理
*A:同步代碼塊的執行原理
同步代碼塊: 在代碼塊聲明上 加上synchronized
synchronized (鎖對象) {
可能會產生線程安全問題的代碼
}
同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。
同步的上廁所原理
*A:同步的上廁所原理
a:不使用同步:線程在執行的過程中會被打擾
線程比喻成人
線程執行代碼就是上一個廁所
第一個人正在上廁所,上到一半,被另外一個人拉出來
b:使用同步:
線程比喻成人
線程執行代碼就是上一個廁所
鎖比喻成廁所門
第一個人上廁所,會鎖門
第二個人上廁所,看到門鎖上了,等待第一個人上完再去上廁所
同步方法
*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();
}
}
*B:同步方法
/*
* 采用同步方法形式,解決線程的安全問題
* 好處: 代碼簡潔
* 將線程共享數據,和同步,抽取到一個方法中
* 在方法的聲明上,加入同步關鍵字
*
* 問題:
* 同步方法有鎖嗎,肯定有,同步方法中的對象鎖,是本類對象引用 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--);
}
}
}
JDK1.5新特性Lock接口
*A:JDK1.5新特性Lock接口
查閱API,查閱Lock接口描述,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
Lock接口中的常用方法
void lock()
void unlock()
Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。
我們使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket
Lock接口改進售票案例
*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();
}
}
}
}
}
線程的死鎖原理?
*A:線程的死鎖原理
當線程任務中出現了多個同步(多個鎖) 時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程序出現無限等待,這種現象我們稱為死鎖。這種情況能避免就避免掉。
synchronzied(A鎖){
synchronized(B鎖){
}
}
線程的死鎖代碼實現
*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();
}
線程等待與喚醒案例介紹
*A:線程等待與喚醒案例介紹
等待喚醒機制所涉及到的方法:
wait() :等待,將正在執行的線程釋放其執行資格 和 執行權,並存儲到線程池中。
notify():喚醒,喚醒線程池中被wait()的線程,一次喚醒一個,而且是任意的。
notifyAll(): 喚醒全部:可以將線程池中的所有wait() 線程都喚醒。
其實,所謂喚醒的意思就是讓 線程池中的線程具備執行資格。必須註意的是,這些方法都是在 同步中才有效。同時這些方法在使用時必須標明所屬鎖,這樣才可以明確出這些方法操作的到底是哪個鎖上的線程。
線程等待與喚醒案例資源類編寫
*A:線程等待與喚醒案例資源類編寫
/*
* 定義資源類,有2個成員變量
* name,sex
* 同時有2個線程,對資源中的變量操作
* 1個對name,age賦值
* 2個對name,age做變量的輸出打印
*/
public class Resource {
public String name;
public String sex;
}
線程等待與喚醒案例輸入和輸出線程
*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);
}
}
}
線程等待與喚醒案例測試類
*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();
}
}
線程等待與喚醒案例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();
}
}
線程等待與喚醒案例數據安全解決
*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();
}
}
線程等待與喚醒案例通信的分析
*A:線程等待與喚醒案例通信的分析
輸入:賦值後,執行方法wait()永遠等待
輸出:變量值打印輸出,在輸出等待之前,喚醒
輸入的notify(),自己在wait()永遠等待
輸入:被喚醒後,重新對變量賦值,賦值後,必須喚醒輸出的線程notify(),自己的wait()
線程等待與喚醒案例的實現
*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();
}
}
總結
18、多線程 (線程安全、線程同步、等待喚醒機制、單例設計模式)