線程安全問題
在多線程程序中,當(dāng)多個(gè)線程同時(shí)操作堆區(qū)/方法區(qū)同一個(gè)數(shù)據(jù)時(shí),可能引發(fā)數(shù)據(jù)不一致的現(xiàn)象, 稱為線程安全問題。
讓每個(gè)線程都訪問自己的局部變量, 不會(huì)產(chǎn)生線程安全問題
如果多個(gè)線程必須同時(shí)操作堆區(qū)/方法區(qū)同一個(gè)數(shù)據(jù) , 采用線程同步技術(shù)
語法:
synchronized ( 鎖對象 ) {
同步代碼塊
}
工作原理:
● 線程要執(zhí)行同步代碼塊,必須先獲得鎖對象
● 任意對象都可以作為鎖對象, 任意對象都有一個(gè)內(nèi)置鎖
● 一個(gè)鎖對象最多被一個(gè)線程持有
● 線程持有了鎖對象后,會(huì)一直持有, 直到執(zhí)行完同步代碼塊后才會(huì)釋放鎖對象
package com.wkcto.chapter07.sync.p2;
/**
* 銀行帳戶類
* @author 蛙課網(wǎng)
*
*/
public class BankAccount {
int balance = 10000 ; //余額 ,單位:萬元
private static final Object OBJ = new Object(); //常量
//取錢的操作, 約定, 每次取錢1000
public void withdraw() {
synchronized ( OBJ ) { //一般使用常量作為鎖對象
System.out.println(Thread.currentThread().getName() + "取錢 前, 余額為:" + balance);
balance -= 1000;
System.out.println(Thread.currentThread().getName() + "取了1000萬后, 余額為:" + balance);
}
/*
* 1) xiaoming獲得了CPU執(zhí)行權(quán), 執(zhí)行withdraw()方法, 先獲得OBJ鎖對象, 執(zhí)行同步代碼塊
* 2) xiaoming在執(zhí)行同步代碼塊期間, CPU執(zhí)行權(quán)被baby搶走了, xiaoming轉(zhuǎn)為就緒狀態(tài)
* babay獲得CPU執(zhí)行權(quán), 要也執(zhí)行同步代碼塊, 必須先獲得OBJ鎖對象,
* 現(xiàn)在OBJ鎖對象被xiaoming持有, baby線程轉(zhuǎn)到等待鎖對象池中阻塞
* 3) xiaoming重新獲得CPU執(zhí)行權(quán), 繼續(xù)執(zhí)行同步代碼塊, 執(zhí)行完同步代碼塊后, 釋放OBJ鎖對象
* 4) 等待鎖對象池中的baby如果獲得了鎖對象, 轉(zhuǎn)為就緒狀態(tài)
*/
}
}
package com.wkcto.chapter07.sync.p2;
/**
* 定義一個(gè)線程類,模擬 從銀行帳戶 中取錢
* @author 蛙課網(wǎng)
*
*/
public class PersonThread extends Thread {
private BankAccount bankaccount; //銀行帳戶
public PersonThread(BankAccount bankaccount) {
super();
this.bankaccount = bankaccount;
}
@Override
public void run() {
bankaccount.withdraw();
}
}
package com.wkcto.chapter07.sync.p2;
/**
* 使用線程模擬多人同時(shí)從某一帳戶中取錢
* 線程同步
* @author 蛙課網(wǎng)
*
*/
public class Test01 {
public static void main(String[] args) {
//先開戶
BankAccount account = new BankAccount();
//定義三個(gè)線程模擬三個(gè)人, 是從同一個(gè)帳戶中取錢
PersonThread xiaoming = new PersonThread(account);
PersonThread bingbing = new PersonThread(account);
PersonThread baby = new PersonThread(account);
xiaoming.setName("xiaoming");
bingbing.setName("bingbing");
baby.setName("baby");
xiaoming.start();
bingbing.start();
baby.start();
}
}
同步代碼塊
同步代碼塊要想實(shí)現(xiàn)同步,必須保證使用同一個(gè)鎖對象
只要使用了同一個(gè)鎖對象的同步代碼塊就可以同步
package com.wkcto.chapter07.sync.p3;
/**
* 同步代碼塊, 只要使用相同的鎖對象就可以實(shí)現(xiàn)同步
* @author 蛙課網(wǎng)
*
*/
public class Test01 {
public static void main(String[] args) {
PrintNum pNum = new PrintNum();
//創(chuàng)建線程,調(diào)用m1()
new Thread(new Runnable() {
@Override
public void run() {
pNum.m1();
}
}).start();
//創(chuàng)建線程,調(diào)用m2()
new Thread(new Runnable() {
@Override
public void run() {
// pNum.m2(); //當(dāng)使用this對象作為鎖對象時(shí), 可以同步
new PrintNum().m2(); //使用this作為鎖對象, 不能同步, 與第一個(gè)線程的鎖對象不是一個(gè)
}
}).start();
}
}
package com.wkcto.chapter07.sync.p3;
/**
* 定義類
* 提供兩個(gè) 方法,分別打印字符串
* @author 蛙課網(wǎng)
*
*/
public class PrintNum {
private static final Object OBJ = new Object(); //定義常量
public void m1() {
// synchronized (OBJ) { //經(jīng)常使用常量作為鎖對象
synchronized ( this ) { //有時(shí)也會(huì)使用this作為鎖對象 , 就是調(diào)用m1()方法的對象
for(int i = 1; i<=200; i++){
System.out.println("wkcto is NB website");
}
}
}
public void m2() {
// synchronized (OBJ) {
synchronized (this) {
for(int i = 1; i<=200; i++){
System.out.println("bjpowernode is a good school");
}
}
}
}
ackage com.wkcto.chapter07.sync.p4;
/**
* 使用當(dāng)前類的運(yùn)行時(shí)類作為鎖對象
* @author 蛙課網(wǎng)
*
*/
public class Test02 {
public static void main(String[] args) {
//創(chuàng)建線程, 打印wkcto
new Thread(new Runnable() {
@Override
public void run() {
while( true ){
printText("wkcto");
}
}
}).start();
//創(chuàng)建線程, 打印bjpowernode
new Thread(new Runnable() {
@Override
public void run() {
while( true ){
printText("bjpowernode");
}
}
}).start();
}
private static final Object OBJ = new Object(); //常量
//靜態(tài)方法,打印一個(gè)字符串
public static void printText( String text) {
// synchronized (OBJ) {
synchronized ( Test02.class ) { //使用當(dāng)前類的運(yùn)行時(shí)類對象作為鎖對象, 有人把它稱為類鎖
//可以簡單的理解 為把當(dāng)前類的字節(jié)碼文件作為鎖對象
for( int i = 0 ; i<text.length() ; i++){
System.out.print( text.charAt(i));
}
System.out.println();
}
}
}
同步方法
package com.wkcto.chapter07.sync.p5;
/**
* 同步實(shí)例方法, 就是把整個(gè)方法體作為同步代碼塊, 默認(rèn)鎖對象 是this對象
*/
public class Test01 {
public static void main(String[] args) {
Test01 obj = new Test01();
//創(chuàng)建線程, 調(diào)用m1()
new Thread(new Runnable() {
@Override
public void run() {
obj.m1();
}
}).start();
//創(chuàng)建線程, 調(diào)用m2()
new Thread(new Runnable() {
@Override
public void run() {
obj.m2();
}
}).start();
}
/*
* 把整個(gè)方法體作為同步代碼塊,并且使用this作為鎖對象時(shí), 可以直接使用synchronized修飾方法, 稱為同步方法
* 同步實(shí)例方法, 就是把整個(gè)方法體作為同步代碼塊, 默認(rèn)鎖對象 是this對象
*/
public synchronized void m1() {
for(int i =1; i<=500; i++){
System.out.println( Thread.currentThread().getName() + "-->" + i);
}
}
public void m2() {
synchronized ( this ) {
for(int i =1; i<=500; i++){
System.out.println( Thread.currentThread().getName() + "======>" + i);
}
}
}
}
package com.wkcto.chapter07.sync.p5;
/**
* 同步靜態(tài)方法, 就是把整個(gè)方法體作為同步代碼塊, 默認(rèn)鎖對象 是當(dāng)前類的運(yùn)行時(shí)類對象
*/
public class Test02 {
public static void main(String[] args) {
//創(chuàng)建線程, 調(diào)用m1()
new Thread(new Runnable() {
@Override
public void run() {
Test02.m1();
}
}).start();
//創(chuàng)建線程, 調(diào)用m2()
new Thread(new Runnable() {
@Override
public void run() {
Test02.m2();
}
}).start();
}
/*
* 把整個(gè)方法體作為同步代碼塊,并且使用當(dāng)前類的運(yùn)行時(shí)類對象(Test02.class)作為鎖對象時(shí), 可以直接使用synchronized修飾方法, 稱為同步方法
* 同步靜態(tài)方法, 就是把整個(gè)方法體作為同步代碼塊, 默認(rèn)鎖對象 是當(dāng)前類的運(yùn)行時(shí)類對象(Test02.class)
*/
public synchronized static void m1() {
for(int i =1; i<=500; i++){
System.out.println( Thread.currentThread().getName() + "-->" + i);
}
}
public static void m2() {
synchronized ( Test02.class ) {
for(int i =1; i<=500; i++){
System.out.println( Thread.currentThread().getName() + "======>" + i);
}
}
}
}