一、线程同步
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
二、多线程同步解决方案
2.1 同步代码块:
使用 synchronized() 对需要完整执行的语句进行“包裹”,synchronized(Obj obj) 构造方法里是可以传入任何类的对象,
但是既然是监听器就传一个唯一的对象来保证“锁”的唯一性,因此一般使用共享资源的对象来作为 obj 传入 synchronized(Obj obj) 里:
只需要锁 Account 类中的存钱取钱方法就行了:
package com.test.threadDemo2; /** * 银行账户 * @author Administrator * */ public class Acount { private int count=0; /** * 存钱 * @param money */ public void addAcount(String name,int money) { synchronized(this) { // 存钱 count += money; System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName()); SelectAcount(name); } } /** * 取钱 * @param money */ public void subAcount(String name,int money) { synchronized(this) { // 先判断账户现在的余额是否够取钱金额 if(count-money < 0){ System.out.println("账户余额不足!"); return; } // 取钱 count -= money; System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName()); SelectAcount(name); } } /** * 查询余额 */ public void SelectAcount(String name) { System.out.println(name+"...余额:"+count); } }
2.2 同步方法
或者在方法的申明里申明 synchronized 即可:
package com.test.threadDemo2; /** * 银行账户 * @author Administrator * */ public class Acount { private int count; /** * 存钱 * @param money */ public synchronized void addAcount(String name,int money) { // 存钱 count += money; System.out.println(name+"...存入:"+money); } /** * 取钱 * @param money */ public synchronized void subAcount(String name,int money) { // 先判断账户现在的余额是否够取钱金额 if(count-money < 0){ System.out.println("账户余额不足!"); return; } // 取钱 count -= money; System.out.println(name+"...取出:"+money); } /** * 查询余额 */ public void SelectAcount(String name) { System.out.println(name+"...余额:"+count); } }
2.3 使用同步锁:
account 类创建私有的 ReetrantLock 对象,调用 lock() 方法,同步执行体执行完毕之后,需要用 unlock() 释放锁。
package com.test.threadDemo2; import java.util.concurrent.locks.ReentrantLock; /** * 银行账户 * @author Administrator * */ public class Acount { private int count; private ReentrantLock lock = new ReentrantLock(); /** * 存钱 * @param money */ public void addAcount(String name,int money) { lock.lock(); try{ // 存钱 count += money; System.out.println(name+"...存入:"+money); }finally { lock.unlock(); } } /** * 取钱 * @param money */ public void subAcount(String name,int money) { lock.lock(); try{ // 先判断账户现在的余额是否够取钱金额 if(count-money < 0){ System.out.println("账户余额不足!"); return; } // 取钱 count -= money; System.out.println(name+"...取出:"+money); }finally { lock.unlock(); } } /** * 查询余额 */ public void SelectAcount(String name) { System.out.println(name+"...余额:"+count); } }
三、线程通信
在共享资源中增加镖旗,当镖旗为真的时候才可以存钱,存完了就把镖旗设置成假,当取款的时候发现镖旗为假的时候,可以取款,取完款就把镖旗设置为真。
只需修改 Account 类 和 测试类 即可:
package com.test.threadDemo2; /** * 银行账户 * @author Administrator * */ public class Acount { private boolean flag=false; // 默认flag 为false,要求必须先存款再取款 private int count=0; /** * 存钱 * @param money */ public void addAcount(String name,int money) { synchronized(this) { // flag 为false 表示可以存款,否则不可以存款 if(flag) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else { // 存钱 count += money; System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName()); SelectAcount(name); flag = true; this.notifyAll(); } } } /** * 取钱 * @param money */ public void subAcount(String name,int money) { synchronized(this) { if(!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { // 先判断账户现在的余额是否够取钱金额 if(count-money < 0){ System.out.println("账户余额不足!"); return; } // 取钱 count -= money; System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName()); SelectAcount(name); flag = false; this.notifyAll(); } } } /** * 查询余额 */ public void SelectAcount(String name) { System.out.println(name+"...余额:"+count); } }
package com.test.threadDemo2; public class ThreadDemo2 { public static void main(String[] args) { // 开个银行帐号 Acount acount = new Acount(); // 开银行帐号之后银行给张银行卡 Card card1 = new Card("card1",acount); Card card2 = new Card("card2",acount); Card card3 = new Card("card3",acount); // 开银行帐号之后银行给张存折 Paper paper1 = new Paper("paper1",acount); Paper paper2 = new Paper("paper2",acount); // 创建三个银行卡 Thread thread1 = new Thread(card1,"card1"); Thread thread2 = new Thread(card2,"card2"); Thread thread3 = new Thread(card3,"card3"); // 创建两个存折 Thread thread4 = new Thread(paper1,"paper1"); Thread thread5 = new Thread(paper2,"paper2"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } }
使用同步锁也可以达到相同的目的:
package com.test.threadDemo2; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 银行账户 * @author Administrator * */ public class Acount2 { private boolean flag=false; // 默认flag 为false,要求必须先存款再取款 private int count=0; private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); /** * 存钱 * @param money */ public void addAcount(String name,int money) { lock.lock(); try { // flag 为false 表示可以存款,否则不可以存款 if(flag) { try { condition.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else { // 存钱 count += money; System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName()); SelectAcount(name); flag = true; condition.signalAll(); } }finally { lock.unlock(); } } /** * 取钱 * @param money */ public void subAcount(String name,int money) { lock.lock(); try { if(!flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } }else { // 先判断账户现在的余额是否够取钱金额 if(count-money < 0){ System.out.println("账户余额不足!"); return; } // 取钱 count -= money; System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName()); SelectAcount(name); flag = false; condition.signalAll(); } }finally { lock.unlock(); } } /** * 查询余额 */ public void SelectAcount(String name) { System.out.println(name+"...余额:"+count); } }