java多线程问题_【java 多线程】多线程并发同步问题及解决方法

一、线程并发同步概念

线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。

线程同步,就是当线程发出一个功能调用时,在没有得到结果之前,该调用就不会返回,其他线程也不能调用该方法。

就一般而言,我们在说同步、异步的时候,特指那些需要其他组件来配合或者需要一定时间来完成的任务。在多线程编程里面,一些较为敏感的数据时不允许被多个线程同时访问的,使用线程同步技术,确保数据在任何时刻最多只有一个线程访问,保证数据的完整性。

二、线程同步中可能存在安全隐患

用生活中的场景来举例:小生去银行开个银行账户,银行给 me 一张银行卡和一张存折,小生用银行卡和存折来搞事情:

银行卡疯狂存钱,存完一次就看一下余额;同时用存折子不停地取钱,取一次钱就看一下余额;

具体代码实现如下:

先弄一个银行账户对象,封装了存取插钱的方法:

1 packagecom.test.threadDemo2;2

3 /**

4 * 银行账户5 *@authorAdministrator6 *7 */

8 public classAcount {9 private int count=0;10

11 /**

12 * 存钱13 *@parammoney14 */

15 public void addAcount(String name,intmoney) {16 17 //存钱

18 count +=money;19 System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName());20 SelectAcount(name);21 22 }23

24 /**

25 * 取钱26 *@parammoney27 */

28 public void subAcount(String name,intmoney) {29 30 //先判断账户现在的余额是否够取钱金额

31 if(count-money < 0){32 System.out.println("账户余额不足!");33 return;34 }35 //取钱

36 count -=money;37 System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName());38 SelectAcount(name);39 40 }41

42 /**

43 * 查询余额44 */

45 public voidSelectAcount(String name) {46 System.out.println(name+"...余额:"+count);47 }48 }

编写银行卡对象:

1 packagecom.test.threadDemo2;2 /**

3 * 银行卡负责存钱4 *@authorAdministrator5 *6 */

7 public class Card implementsRunnable{8 privateString name;9 private Account account = newAccount();10

11 publicCard(String name,Account account) {12 this.account =account;13 this.name =name;14 }15

16 @Override17 public voidrun() {18

19 while(true) {20 try{21 Thread.sleep(1000);22 } catch(InterruptedException e) {23 e.printStackTrace();24 }25 account.addAccount(name,100);26 }27 }28

29 }

编写存折对象(和银行卡方法几乎一模一样,就是名字不同而已):

packagecom.test.threadDemo2;/*** 存折负责取钱

*@authorAdministrator

**/

public class Paper implementsRunnable{privateString name;private Account account = newAccount();publicPaper(String name,Account account) {this.account =account;this.name =name;

}

@Overridepublic voidrun() {while(true) {try{

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

account.subAccount(name,50);

}

}

}

主方法测试,演示银行卡疯狂存钱,存折疯狂取钱:

1 packagecom.test.threadDemo2;2

3 public classThreadDemo2 {4 public static voidmain(String[] args) {5

6 //开个银行帐号

7 Account account = newAccount();8 //开银行帐号之后银行给张银行卡

9 Card card = new Card("card",account);10 //开银行帐号之后银行给张存折

11 Paper paper = new Paper("存折",account);12

13 Thread thread1 = newThread(card);14 Thread thread2 = newThread(paper);15

16 thread1.start();17 thread2.start();18 }19 }

结果显示:从中可以看出 bug

3ea52d73ca30a978d23cd68835658645.png

从上面的例子里就可以看出,银行卡存钱和存折取钱的过程中使用了 sleep() 方法,这只不过是小生模拟“系统卡顿”现象:银行卡存钱之后,还没来得及查余额,存折就在取钱,刚取完钱,银行卡这边“卡顿”又好了,查询一下余额,发现钱存的数量不对!当然还有“卡顿”时间比较长,存折在卡顿的过程中,把钱全取了,等银行卡这边“卡顿”好了,一查发现钱全没了的情况可能。

因此多个线程一起访问共享的数据的时候,就会可能出现数据不同步的问题,本来一个存钱的时候不允许别人打断我(当然实际中可以存在刚存就被取了,有交易记录在,无论怎么动这个帐号,都是自己的银行卡和存折在动钱。小生这个例子里,要求的是存钱和查钱是一个完整过程,不可以拆分开),但从结果来看,并没有实现小生想要出现的效果,这破坏了线程“原子性”。

三、线程同步中可能存在安全隐患的解决方法

从上面的例子中可以看出线程同步中存在安全隐患,我们必须不能忽略,所以要引入“锁”(术语叫监听器)的概念:

3.1 同步代码块:

使用 synchronized() 对需要完整执行的语句进行“包裹”,synchronized(Obj obj) 构造方法里是可以传入任何类的对象,

但是既然是监听器就传一个唯一的对象来保证“锁”的唯一性,因此一般使用共享资源的对象来作为 obj 传入 synchronized(Obj obj) 里:

只需要锁 Account 类中的存钱取钱方法就行了:

packagecom.test.threadDemo2;/*** 银行账户

*@authorAdministrator

**/

public classAcount {private int count=0;/*** 存钱

*@parammoney*/

public void addAcount(String name,intmoney) {synchronized(this) {//存钱

count +=money;

System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName());

SelectAcount(name);

}

}/*** 取钱

*@parammoney*/

public void subAcount(String name,intmoney) {synchronized(this) {//先判断账户现在的余额是否够取钱金额

if(count-money < 0){

System.out.println("账户余额不足!");return;

}//取钱

count -=money;

System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName());

SelectAcount(name);

}

}/*** 查询余额*/

public voidSelectAcount(String name) {

System.out.println(name+"...余额:"+count);

}

}

3.2 同步方法

者在方法的申明里申明 synchronized 即可:

1 packagecom.test.threadDemo2;2 /**

3 * 银行账户4 *@authorAdministrator5 *6 */

7 public classAcount {8 private intcount;9

10 /**

11 * 存钱12 *@parammoney13 */

14 public synchronizedvoid addAcount(String name,intmoney) {15 //存钱

16 count +=money;17 System.out.println(name+"...存入:"+money);18 }19

20 /**

21 * 取钱22 *@parammoney23 */

24 public synchronized void subAcount(String name,intmoney) {25 //先判断账户现在的余额是否够取钱金额

26 if(count-money < 0){27 System.out.println("账户余额不足!");28 return;29 }30 //取钱

31 count -=money;32 System.out.println(name+"...取出:"+money);33 }34

35 /**

36 * 查询余额37 */

38 public voidSelectAcount(String name) {39 System.out.println(name+"...余额:"+count);40 }41 }

运行效果:

f15aa7ee9e838a797ca239e22ab96e0a.png

3.3 使用同步锁:

account 类创建私有的 ReetrantLock 对象,调用 lock() 方法,同步执行体执行完毕之后,需要用 unlock() 释放锁。

packagecom.test.threadDemo2;importjava.util.concurrent.locks.ReentrantLock;/*** 银行账户

*@authorAdministrator

**/

public classAcount {private intcount;private ReentrantLock lock = new ReentrantLock();/*** 存钱

*@parammoney*/

public void addAcount(String name,intmoney) {

lock.lock();try{//存钱

count +=money;

System.out.println(name+"...存入:"+money);

}finally{

lock.unlock();

}

}/*** 取钱

*@parammoney*/

public void subAcount(String name,intmoney) {

lock.lock();try{//先判断账户现在的余额是否够取钱金额

if(count-money < 0){

System.out.println("账户余额不足!");return;

}//取钱

count -=money;

System.out.println(name+"...取出:"+money);

}finally{

lock.unlock();

}

}/*** 查询余额*/

public voidSelectAcount(String name) {

System.out.println(name+"...余额:"+count);

}

}

运行效果:

f141d71805c70c3b304038ec6dc4f06d.png

四、死锁

当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形:

线程 A 当前持有互斥所锁 lock1,线程 B 当前持有互斥锁 lock2。

接下来,当线程 A 仍然持有 lock1 时,它试图获取 lock2,因为线程 B 正持有 lock2,因此线程 A 会阻塞等待线程 B 对 lock2 的释放。

如果此时线程 B 在持有 lock2 的时候,也在试图获取 lock1,因为线程 A 正持有 lock1,因此线程 B 会阻塞等待 A 对 lock1 的释放。

二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。

1 packagecom.testDeadLockDemo;2

3 public classLockA {4

5 privateLockA(){}6

7 public static final LockA lockA = newLockA();8 }

1 packagecom.testDeadLockDemo;2

3 public classLockB {4 privateLockB(){}5

6 public static final LockB lockB = newLockB();7 }

packagecom.testDeadLockDemo;public class DeadLock implementsRunnable{private int i=0;

@Overridepublic voidrun() {while(true) {if(i%2==0){synchronized(LockA.lockA) {

System.out.println("if...lockA");synchronized(LockB.lockB) {

System.out.println("if...lockB");

}

}

}else{synchronized(LockB.lockB) {

System.out.println("else...lockB");synchronized(LockA.lockA) {

System.out.println("else...lockA");

}

}

}

i++;

}

}

}

测试:

1 packagecom.testDeadLockDemo;2

3 public classTest {4 public static voidmain(String[] args) {5 DeadLock deadLock = newDeadLock();6

7 Thread t1 = newThread(deadLock);8 Thread t2 = newThread(deadLock);9 t1.start();10 t2.start();11

12 }13 }

运行结果:

cfd33b220045625ea85528d772704bf3.png

五、线程通信

在共享资源中增加镖旗,当镖旗为真的时候才可以存钱,存完了就把镖旗设置成假,当取款的时候发现镖旗为假的时候,可以取款,取完款就把镖旗设置为真。

只需修改 Account 类 和 测试类 即可

1 packagecom.test.threadDemo2;2

3 /**

4 * 银行账户5 *@authorAdministrator6 *7 */

8 public classAcount {9 private boolean flag=false; //默认flag 为false,要求必须先存款再取款

10 private int count=0;11

12 /**

13 * 存钱14 *@parammoney15 */

16 public void addAcount(String name,intmoney) {17 synchronized(this) {18 //flag 为true 表示可以存款,否则不可以存款

19 if(flag) {20 try{21 this.wait();22 } catch(InterruptedException e) {23 //TODO Auto-generated catch block

24 e.printStackTrace();25 }26 }else {27 //存钱

28 count +=money;29 System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName());30 SelectAcount(name);31 flag = true;32 this.notifyAll();33 }34 }35 }36

37 /**

38 * 取钱39 *@parammoney40 */

41 public void subAcount(String name,intmoney) {42 synchronized(this) {43 if(!flag) {44 try{45 this.wait();46 } catch(InterruptedException e) {47 e.printStackTrace();48 }49 }else {50 //先判断账户现在的余额是否够取钱金额

51 if(count-money < 0){52 System.out.println("账户余额不足!");53 return;54 }55 //取钱

56 count -=money;57 System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName());58 SelectAcount(name);59 flag = false;60 this.notifyAll();61 }62 }63 }64

65 /**

66 * 查询余额67 */

68 public voidSelectAcount(String name) {69 System.out.println(name+"...余额:"+count);70 }71 }

1 packagecom.test.threadDemo2;2

3 public classThreadDemo2 {4 public static voidmain(String[] args) {5

6 //开个银行帐号

7 Acount acount = newAcount();8

9 //开银行帐号之后银行给张银行卡

10 Card card1 = new Card("card1",acount);11 Card card2 = new Card("card2",acount);12 Card card3 = new Card("card3",acount);13

14 //开银行帐号之后银行给张存折

15 Paper paper1 = new Paper("paper1",acount);16 Paper paper2 = new Paper("paper2",acount);17

18 //创建三个银行卡

19 Thread thread1 = new Thread(card1,"card1");20 Thread thread2 = new Thread(card2,"card2");21 Thread thread3 = new Thread(card3,"card3");22 //创建两个存折

23 Thread thread4 = new Thread(paper1,"paper1");24 Thread thread5 = new Thread(paper2,"paper2");25

26 thread1.start();27 thread2.start();28 thread3.start();29

30 thread4.start();31 thread5.start();32 }33 }

运行结果:

4f245ac0c62feef4039e5e05b4ab78ee.png

使用同步锁也可以达到相同的目的:

packagecom.test.threadDemo2;importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.ReentrantLock;/*** 银行账户

*@authorAdministrator

**/

public classAcount2 {private boolean flag=false; //默认flag 为false,要求必须先存款再取款

private int count=0;private final ReentrantLock lock = new ReentrantLock();private final Condition condition =lock.newCondition();/*** 存钱

*@parammoney*/

public void addAcount(String name,intmoney) {

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();

}

}/*** 取钱

*@parammoney*/

public void subAcount(String name,intmoney) {

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 voidSelectAcount(String name) {

System.out.println(name+"...余额:"+count);

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值