经典例子:老婆(朱丽叶)老公(罗密欧),使用银行卡和存折,或者网银等,同时对同一账户操作的安全问题。
此处用多线程实现,同时取款的模拟实现,使用同步代码块确保线程同步,查看取款安全隐患问题,代码如下:
--------------------------------------------------------------------------------------------------------------------------------------
* 线程同步 :使用同步块,实现线程同步
* 同步synchronized块的对象监视锁可为为: 任意的对象(此处为'account'对象)。
* (Runnable适用于共享同一对象(如:this),如果Thread继承就会有问题[保证Thread继承下安全:1.锁使用涉及当前对象外的对象(例如:SyncBlock、BankAccount对象外的其他类对象;2.锁使用静态当前类对象)])
* 多个线程使用同一把锁,如果线程安全必需确保:多个线程使用的是同一个对象监视锁对象
* synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
* 所有访问此同步代码块的线程都在同步块外等待,都会判断同步锁,降低效率,但确保线程安全问题,外,同步代码块外代码正常执行
* 同步方法优于同步代码块:性能和memory空间上。
* 同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可
--------------------------------------------------------------------------------------------------------------------------------------
private void makeWithdraw(int amount){
/*.........同步代码块外代码.............*/
synchronized (account) {
//同步代码
}
}
银行账户:
package com.tsxs.bank; public class BankAccount { //余额 private int balance = 500; //查询 public int getBalance(){ return banlance; } //取款 public void withdraw(int amount){ banlance = banlance - amount; } //存款 public void deposit(int amount){ banlance = banlance + amount; } }
同步代码块代码:
package com.tsxs.syncmethods; import com.tsxs.bank.BankAccount; /** * 此线程类实现Runnable接口<br /> * 线程同步 :使用同步块,实现线程同步<br /> * 同步synchronized块的对象监视锁可为为: 任意的对象(此处为'account'对象)。<br /> * (Runnable适用于共享同一对象(如:this),如果Thread继承就会有问题[保证Thread继承下安全:1.锁使用涉及当前对象外的对象(例如:SyncBlock、BankAccount对象外的其他类对象;2.锁使用静态当前类对象)])<br /> * 多个线程使用同一把锁,如果线程安全必需确保:多个线程使用的是同一个对象监视锁对象<br /> * 所有访问此同步代码块的线程都在同步块外等待,都会判断同步锁,降低效率,但确保线程安全问题,外,同步代码块外代码正常执行<br /> * 同步方法优于同步代码块:性能和memory空间上。 * */ public class SyncBlock implements Runnable { //所有Thread多线程线程都共享Runnable(接口对象)和account对象 private BankAccount account = new BankAccount(); @Override public void run() { for(int i = 0; i< 5; i++){ //总共取款5次 makeWithdraw(100); //每次取款100 if(account.getBalance() < 0){ System.out.println(""+Thread.currentThread().getName()+" 透支了!"); } } } /** * makeWithdraw 账户取款 * @param amount 取款金额<br /> * 打印log记录取款过程 * */ private void makeWithdraw(int amount){ /*.........同步代码块外代码.............*/ synchronized (account) { if(account.getBalance() >= amount){ //如果余额足够则取款 System.out.println(""+Thread.currentThread().getName()+" 准备取款!"); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+" 准备取款,等待0.5s线程中断!"+e.getMessage()); } account.withdraw(amount); System.out.println(""+Thread.currentThread().getName()+" 完成"+amount+"取款!余额为"+account.getBalance()); }else{ //余额不足则提示 System.out.println(""+"余额不足以支付"+Thread.currentThread().getName()+amount+" 的取款,余额为"+account.getBalance()); } } } }
测试代码:
package com.tsxs.test; import org.junit.Test; import com.tsxs.syncmethods.NoSync; import com.tsxs.syncmethods.SyncBlock; import com.tsxs.syncmethods.SyncMethod; public class TreadSyncTest { // @Test // public void test() { /*Junit不适合多线程并发测试。 因为线程还在激活状态的时候,Junit已经执行完成。 在Junit的TestRunner中,它没有被设计成搜寻Runnable实例, 并且等待这些线程发出报告,它只是执行它们并且忽略了它们的存在。 综上,不可能在Junit中编写和维护多线程的单元测试。 }*/ public static void main(String[] args) { //实现Runnable:所有Thread多线程线程都共享Runnable(接口对象) // NoSync target =new NoSync(); // SyncMethod target = new SyncMethod(); SyncBlock target = new SyncBlock(); //创建李琦和他老婆两个线程实现取款(同时) Thread lq = new Thread(target); lq.setName("罗密欧"); Thread lqwf = new Thread(target); lqwf.setName("朱丽叶"); //调用Thread对象的start()方法,启动线程,执行run()方法(OS) lq.start(); lqwf.start(); } }
测试结果:
朱丽叶 准备取款! 朱丽叶 完成100取款!余额为400 朱丽叶 准备取款! 朱丽叶 完成100取款!余额为300 朱丽叶 准备取款! 朱丽叶 完成100取款!余额为200 罗密欧 准备取款! 罗密欧 完成100取款!余额为100 罗密欧 准备取款! 罗密欧 完成100取款!余额为0 余额不足以支付罗密欧100 的取款,余额为0 余额不足以支付罗密欧100 的取款,余额为0 余额不足以支付罗密欧100 的取款,余额为0 余额不足以支付朱丽叶100 的取款,余额为0 余额不足以支付朱丽叶100 的取款,余额为0
分析结果:
双线程总共取款10次,账户总额为500.
取款结果:在多线程访问下,成功取款总额为500,并且其他取款下,正确提示信息。
多线程访问安全保证!