線程安全問題
在多線程程序中,當多個線程同時操作堆區/方法區同一個數據時,可能引發數據不一致的現象, 稱為線程安全問題。
讓每個線程都訪問自己的局部變量, 不會產生線程安全問題
如果多個線程必須同時操作堆區/方法區同一個數據 , 采用線程同步技術
語法:
synchronized ( 鎖對象 ) {
同步代碼塊
}
工作原理:
● 線程要執行同步代碼塊,必須先獲得鎖對象
● 任意對象都可以作為鎖對象, 任意對象都有一個內置鎖
● 一個鎖對象最多被一個線程持有
● 線程持有了鎖對象后,會一直持有, 直到執行完同步代碼塊后才會釋放鎖對象
package com.wkcto.chapter07.sync.p2;
/**
* 銀行帳戶類
* @author 蛙課網
*
*/
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執行權, 執行withdraw()方法, 先獲得OBJ鎖對象, 執行同步代碼塊
* 2) xiaoming在執行同步代碼塊期間, CPU執行權被baby搶走了, xiaoming轉為就緒狀態
* babay獲得CPU執行權, 要也執行同步代碼塊, 必須先獲得OBJ鎖對象,
* 現在OBJ鎖對象被xiaoming持有, baby線程轉到等待鎖對象池中阻塞
* 3) xiaoming重新獲得CPU執行權, 繼續執行同步代碼塊, 執行完同步代碼塊后, 釋放OBJ鎖對象
* 4) 等待鎖對象池中的baby如果獲得了鎖對象, 轉為就緒狀態
*/
}
}
package com.wkcto.chapter07.sync.p2;
/**
* 定義一個線程類,模擬 從銀行帳戶 中取錢
* @author 蛙課網
*
*/
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;
/**
* 使用線程模擬多人同時從某一帳戶中取錢
* 線程同步
* @author 蛙課網
*
*/
public class Test01 {
public static void main(String[] args) {
//先開戶
BankAccount account = new BankAccount();
//定義三個線程模擬三個人, 是從同一個帳戶中取錢
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();
}
}
同步代碼塊
同步代碼塊要想實現同步,必須保證使用同一個鎖對象
只要使用了同一個鎖對象的同步代碼塊就可以同步
package com.wkcto.chapter07.sync.p3;
/**
* 同步代碼塊, 只要使用相同的鎖對象就可以實現同步
* @author 蛙課網
*
*/
public class Test01 {
public static void main(String[] args) {
PrintNum pNum = new PrintNum();
//創建線程,調用m1()
new Thread(new Runnable() {
@Override
public void run() {
pNum.m1();
}
}).start();
//創建線程,調用m2()
new Thread(new Runnable() {
@Override
public void run() {
// pNum.m2(); //當使用this對象作為鎖對象時, 可以同步
new PrintNum().m2(); //使用this作為鎖對象, 不能同步, 與第一個線程的鎖對象不是一個
}
}).start();
}
}
package com.wkcto.chapter07.sync.p3;
/**
* 定義類
* 提供兩個 方法,分別打印字符串
* @author 蛙課網
*
*/
public class PrintNum {
private static final Object OBJ = new Object(); //定義常量
public void m1() {
// synchronized (OBJ) { //經常使用常量作為鎖對象
synchronized ( this ) { //有時也會使用this作為鎖對象 , 就是調用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;
/**
* 使用當前類的運行時類作為鎖對象
* @author 蛙課網
*
*/
public class Test02 {
public static void main(String[] args) {
//創建線程, 打印wkcto
new Thread(new Runnable() {
@Override
public void run() {
while( true ){
printText("wkcto");
}
}
}).start();
//創建線程, 打印bjpowernode
new Thread(new Runnable() {
@Override
public void run() {
while( true ){
printText("bjpowernode");
}
}
}).start();
}
private static final Object OBJ = new Object(); //常量
//靜態方法,打印一個字符串
public static void printText( String text) {
// synchronized (OBJ) {
synchronized ( Test02.class ) { //使用當前類的運行時類對象作為鎖對象, 有人把它稱為類鎖
//可以簡單的理解 為把當前類的字節碼文件作為鎖對象
for( int i = 0 ; i<text.length() ; i++){
System.out.print( text.charAt(i));
}
System.out.println();
}
}
}
同步方法
package com.wkcto.chapter07.sync.p5;
/**
* 同步實例方法, 就是把整個方法體作為同步代碼塊, 默認鎖對象 是this對象
*/
public class Test01 {
public static void main(String[] args) {
Test01 obj = new Test01();
//創建線程, 調用m1()
new Thread(new Runnable() {
@Override
public void run() {
obj.m1();
}
}).start();
//創建線程, 調用m2()
new Thread(new Runnable() {
@Override
public void run() {
obj.m2();
}
}).start();
}
/*
* 把整個方法體作為同步代碼塊,并且使用this作為鎖對象時, 可以直接使用synchronized修飾方法, 稱為同步方法
* 同步實例方法, 就是把整個方法體作為同步代碼塊, 默認鎖對象 是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;
/**
* 同步靜態方法, 就是把整個方法體作為同步代碼塊, 默認鎖對象 是當前類的運行時類對象
*/
public class Test02 {
public static void main(String[] args) {
//創建線程, 調用m1()
new Thread(new Runnable() {
@Override
public void run() {
Test02.m1();
}
}).start();
//創建線程, 調用m2()
new Thread(new Runnable() {
@Override
public void run() {
Test02.m2();
}
}).start();
}
/*
* 把整個方法體作為同步代碼塊,并且使用當前類的運行時類對象(Test02.class)作為鎖對象時, 可以直接使用synchronized修飾方法, 稱為同步方法
* 同步靜態方法, 就是把整個方法體作為同步代碼塊, 默認鎖對象 是當前類的運行時類對象(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);
}
}
}
}