线程安全问题
在多线程程序中,当多个线程同时操作堆区/方法区同一个数据时,可能引发数据不一致的现象, 称为线程安全问题。
让每个线程都访问自己的局部变量, 不会产生线程安全问题
如果多个线程必须同时操作堆区/方法区同一个数据 , 采用线程同步技术
语法:
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("sxbdqn 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();
//创建线程, 打印sxbdqn
new Thread(new Runnable() {
@Override
public void run() {
while( true ){
printText("sxbdqn");
}
}
}).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);
}
}
}
}