1.取款问题
比如你在银行里存了1000块,从银行柜台取出600,同时也在取款机取出600,正常的话第二次在取款机取款由于存款少于600所以取不出来,我们实践一下:
银行账户类
public class BankAccount {
int money=1000;
public int getMoney(int num) {
if ( money<num) {
return -1;
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= num;
System.out.println("剩了"+money);
return money;
}
}
}
线程类
class ThreadBank extends Thread {
BankAccount myAccount;
ThreadBank(BankAccount myAccount) {
this.myAccount = myAccount;
}
@Override
public void run() {
if(myAccount.getMoney(600)!=-1){
System.out.println("取款成功");
}else{
System.out.println("余额不足,不能取款");
}
}
}
测试类
public static void main(String[] args) {
BankAccount myAccount= new BankAccount();
ThreadBank thread1=new ThreadBank(myAccount);
ThreadBank thread2=new ThreadBank(myAccount);
thread1.start();
thread2.start();}
结果
剩了400
取款成功
剩了-200
取款成功
从程序结果可以看出两次取钱都成功了,这就是线程同步导致的问题。
2.解决取款同步问题
利用sychronized关键字解决同步问题,Synchronized关键字:
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
Java中的每个对象都有一把锁(lock)或者监视器。当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再访问该synchronied方法了,直到之前的那个线程执行方法完毕后(或者抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法
注意:如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
1)在getMoney方法前面加入synchronized加入关键字
public class BankAccount {
int money=1000;
public synchronized int getMoney(int num) {
if ( money<num) {
return -1;
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= num;
System.out.println("剩了"+money);
return money;
}
}
}
2) static synchronized方法
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行
静态synchronized方法
class Bank {
public static synchronized void getMoney() {
for (int i = 0; i < 10; i++) {
System.out.println("===== " + i);
}
}
线程类
class ThreadStatic extends Thread {
Bank bank;
ThreadStatic(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.getMoney();
}
}
测试类
public static void main(String[] args) {
Bank bank1 = new Bank();
Bank bank2 = new Bank();
ThreadStatic thread1 = new ThreadStatic(bank1);
ThreadStatic thread2 = new ThreadStatic(bank2);
thread1.start();
thread2.start();
}
结果
===== 0
===== 1
===== 2
===== 3
===== 4
===== 5
===== 6
===== 7
===== 8
===== 9
===== 0
===== 1
===== 2
===== 3
===== 4
===== 5
===== 6
===== 7
===== 8
===== 9
把static去掉,大家可以实践一下
3)Synchronized块写法
Synchronized(object){}表示线程执行的时候对object上锁
class ThreadStatic extends Thread {
Bank bank;
ThreadStatic(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.getMoney();
}
}
This表示对本对象加锁
public static void main(String[] args) {
Bank bank1 = new Bank();
ThreadStatic thread1 = new ThreadStatic(bank1);
ThreadStatic thread2 = new ThreadStatic(bank1);
thread1.start();
thread2.start();
}
两个线程调用同一个对象的方法,那么输出结果是什么呢,大家打印出来看一下,如果你没有写错的话,打印出来的结果肯定是按顺序执行的。
Synchronized方法和方法块写法区别:
Synchonized方法是一种粗粒度的并发控制,在某一时刻只能有一个线程执行该synchronized方法,synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的