多线程同步的五种方法,让你全面了解线程同步

一、为什么要线程同步因为当我们有多个线程要同时访问一个变量或对象时 , 如果这些线程中既有读又有写操作时 , 就会导致变量值或对象的状态出现混乱 , 从而导致程序异常 。
举个例子 , 如果一个银行账户同时被两个线程操作 , 一个取100块 , 一个存钱100块 。假设账户原本有0块 , 如果取钱线程和存钱线程同时发生 , 会出现什么结果呢?取钱不成功 , 账户余额是100.取钱成功了 , 账户余额是0.那到底是哪个呢?很难说清楚 。因此多线程同步就是要解决这个问题 。

多线程同步的五种方法,让你全面了解线程同步

文章插图
 
二、不同步时的代码Bank.JAVA
package threadTest; /*** @author lixiaoxi**/ public class Bank {private int count =0;//账户余额//存钱public void addMoney(int money){count +=money;System.out.println(System.currentTimeMillis()+"存进:"+money);}//取钱public void subMoney(int money){if(count-money < 0){System.out.println("余额不足");return;}count -=money;System.out.println(+System.currentTimeMillis()+"取出:"+money);}//查询public void lookMoney(){System.out.println("账户余额:"+count);} }【多线程同步的五种方法,让你全面了解线程同步】SyncThreadTest.java
package threadTest; public class SyncThreadTest {public static void main(String args[]){final Bank bank=new Bank();Thread tadd=new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}bank.addMoney(100);bank.lookMoney();System.out.println("n");}}});Thread tsub = new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){bank.subMoney(100);bank.lookMoney();System.out.println("n");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}});tsub.start();tadd.start();} }代码很简单 , 我就不解释了 , 看看运行结果怎样呢?截取了其中的一部分 , 是不是很乱 , 有些看不懂 。
余额不足 账户余额:0 余额不足 账户余额:100 1441790503354存进:100 账户余额:100 1441790504354存进:100 账户余额:100 1441790504354取出:100 账户余额:100 1441790505355存进:100 账户余额:100 1441790505355取出:100 账户余额:100 
三、使用同步时的代码1、同步方法
即有synchronized关键字修饰的方法 。由于java的每个对象都有一个内置锁 , 当用此关键字修饰方法时 , 内置锁会保护整个方法 。在调用该方法前 , 需要获得内置锁 , 否则就处于阻塞状态 。
修改后的Bank.java
/*** @author lixiaoxi **/ public class Bank {private int count =0;//账户余额//存钱public synchronized void addMoney(int money){count +=money;System.out.println(System.currentTimeMillis()+"存进:"+money);}//取钱public synchronized void subMoney(int money){if(count-money < 0){System.out.println("余额不足");return;}count -=money;System.out.println(+System.currentTimeMillis()+"取出:"+money);}//查询public void lookMoney(){System.out.println("账户余额:"+count);} }再看看运行结果:
余额不足 账户余额:0 余额不足 账户余额:0 1441790837380存进:100 账户余额:100 1441790838380取出:100 账户余额:0 1441790838380存进:100 账户余额:100 1441790839381取出:100 账户余额:0 
2、同步代码块
即有synchronized关键字修饰的语句块 。被该关键字修饰的语句块会自动被加上内置锁 , 从而实现同步 。
Bank.java代码如下:
package threadTest; package threadTest; /*** @author lixiaoxi**/ public class Bank {private int count =0;//账户余额//存钱public void addMoney(int money){synchronized (this) {count +=money;}System.out.println(System.currentTimeMillis()+"存进:"+money);}//取钱public void subMoney(int money){synchronized (this) {if(count-money < 0){System.out.println("余额不足");return;}count -=money;}System.out.println(+System.currentTimeMillis()+"取出:"+money);}//查询public void lookMoney(){System.out.println("账户余额:"+count);} }运行结果如下:
余额不足 账户余额:0 1441791806699存进:100 账户余额:100 1441791806700取出:100 账户余额:0 1441791807699存进:100 账户余额:100效果和方法一差不多 。
注:同步是一种高开销的操作 , 因此应该尽量减少同步的内容 。通常没有必要同步整个方法 , 使用synchronized代码块同步关键代码即可 。


推荐阅读