线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。
线程同步,就是当线程发出一个功能调用时,在没有得到结果之前,该调用就不会返回,其他线程也不能调用该方法。
就一般而言,我们在说同步、异步的时候,特指那些需要其他组件来配合或者需要一定时间来完成的任务。在多线程编程里面,一些较为敏感的数据时不允许被多个线程同时访问的,使用线程同步技术,确保数据在任何时刻最多只有一个线程访问,保证数据的完整性。
用生活中的场景来举例:小生去银行开个银行账户,银行给 me 一张银行卡和一张存折,小生用银行卡和存折来搞事情:
银行卡疯狂存钱,存完一次就看一下余额;同时用存折子不停地取钱,取一次钱就看一下余额;
具体代码实现如下:
先弄一个银行账户对象,封装了存取插钱的方法:
1 package com.test.threadDemo2;
2
3/**
4 * 银行账户
5 * @author Administrator
6 **/
7 public class Acount {
8 private int count=0;
9
10 /**
11 * 存钱
12 * @param money
13 */
14 public void addAcount(String name,int money) {
15
16 // 存钱
17 count += money;
18 System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName());
19 SelectAcount(name);
20
21 }
22
23 /**
24 * 取钱
25 * @param money
26 */
27 public void subAcount(String name,int money) {
28
29 // 先判断账户现在的余额是否够取钱金额
30 if(count-money < 0){
31 System.out.println("账户余额不足!");
32 return;
33 }
34 // 取钱
35 count -= money;
36 System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName());
37 SelectAcount(name);
38
39 }
40
41 /**
42 * 查询余额
43 */
44 public void SelectAcount(String name) {
45 System.out.println(name+"...余额:"+count);
46 }
47 }
编写银行卡对象:
1 package com.test.threadDemo2;
2 /**
3 * 银行卡负责存钱
4 * @author Administrator
5 **/
6 public class Card implements Runnable{
7 private String name;
8 private Account account = new Account();
9
10 public Card(String name,Account account) {
11 this.account = account;
12 this.name = name
13 }
14 @Override
15 public void run() {
16
17 while(true) {
18 try {
19 Thread.sleep(1000);
20 } catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23 account.addAccount(name,100);
24 }
25 }
26
27 }
编写存折对象(和银行卡方法几乎一模一样,就是名字不同而已):
1 package com.test.threadDemo2;
2/**
3 * 存折负责取钱
4 * @author Administrator
5 **/
6public class Paper implements Runnable{
7 private String name;
8 private Account account = new Account();
9public Paper(String name,Account account) {
10 this.account = account;
11 this.name = name;
12 }
13
14 @Override
15 public void run() {
16 while(true) {
17 try {
18 Thread.sleep(1000);
19 }
20 catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23 account.subAccount(name,50);
24 }
25
26 }
27
28 }
主方法测试,演示银行卡疯狂存钱,存折疯狂取钱:
1 package com.test.threadDemo2;
2
3 public class ThreadDemo2 {
4 public static void main(String[] args) {
5
6 // 开个银行帐号
7 Account account = new Account();
8 // 开银行帐号之后银行给张银行卡
9 Card card = new Card("card",account);
10 // 开银行帐号之后银行给张存折
11 Paper paper = new Paper("存折",account);
12
13 Thread thread1 = new Thread(card);
14 Thread thread2 = new Thread(paper);
15
16 thread1.start();
17 thread2.start();
18 }
19 }
结果显示:从中可以看出 bug
从上面的例子里就可以看出,银行卡存钱和存折取钱的过程中使用了 sleep() 方法,这只不过是小生模拟“系统卡顿”现象:银行卡存钱之后,还没来得及查余额,存折就在取钱,刚取完钱,银行卡这边“卡顿”又好了,查询一下余额,发现钱存的数量不对!当然还有“卡顿”时间比较长,存折在卡顿的过程中,把钱全取了,等银行卡这边“卡顿”好了,一查发现钱全没了的情况可能。
因此多个线程一起访问共享的数据的时候,就会可能出现数据不同步的问题,本来一个存钱的时候不允许别人打断我(当然实际中可以存在刚存就被取了,有交易记录在,无论怎么动这个帐号,都是自己的银行卡和存折在动钱。小生这个例子里,要求的是存钱和查钱是一个完整过程,不可以拆分开),但从结果来看,并没有实现小生想要出现的效果,这破坏了线程“原子性”。
从上面的例子中可以看出线程同步中存在安全隐患,我们必须不能忽略,所以要引入“锁”(术语叫监听器)的概念:
使用 synchronized() 对需要完整执行的语句进行“包裹”,synchronized(Obj obj) 构造方法里是可以传入任何类的对象,
但是既然是监听器就传一个唯一的对象来保证“锁”的唯一性,因此一般使用共享资源的对象来作为 obj 传入 synchronized(Obj obj) 里:
只需要锁 Account 类中的存钱取钱方法就行了:
1 package com.test.threadDemo2;
2/**
3 * 银行账户
4 * @author Administrator
5 **/
6public class Acount {
7 private int count=0;
8 /**
9 * 存钱
10 * @param money
11 */
12 public void addAcount(String name,int money) {
13 synchronized(this) {
14 // 存钱
15 count += money;
16 System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName());
17 SelectAcount(name);
18 }
19 }
20 /**
21 * 取钱
22 * @param money
23 */
24 public void subAcount(String name,int money) {
25 synchronized(this) {
26 // 先判断账户现在的余额是否够取钱金额
27 if(count-money < 0){
28 System.out.println("账户余额不足!");
29 return;
30 }
31 // 取钱
32 count -= money;
33 System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName());
34 SelectAcount(name);
35 }
36 }
37 /**
38 * 查询余额
39 */
40 public void SelectAcount(String name) {
41 System.out.println(name+"...余额:"+count);
42 }
43 }
者在方法的申明里申明 synchronized 即可:
1 package com.test.threadDemo2;
2 /**
3 * 银行账户
4 * @author Administrator
5 **/
6 public class Acount {
7 private int count;
8
9 /**
10 * 存钱
11 * @param money
12 */
13 public synchronized void addAcount(String name,int money) {
14 // 存钱
15 count += money;
16 System.out.println(name+"...存入:"+money);
17 }
18
19 /**
20 * 取钱
21 * @param money
22 */
23 public synchronized void subAcount(String name,int money) {
24 // 先判断账户现在的余额是否够取钱金额
25 if(count-money < 0){
26 System.out.println("账户余额不足!");
27 return;
28 }
29 // 取钱
30 count -= money;
31 System.out.println(name+"...取出:"+money);
32 }
33
34 /**
35 * 查询余额
36 */
37 public void SelectAcount(String name) {
38 System.out.println(name+"...余额:"+count);
39 }
40 }
运行效果:
account 类创建私有的 ReetrantLock 对象,调用 lock() 方法,同步执行体执行完毕之后,需要用 unlock() 释放锁。
1package com.test.threadDemo2;
2
3import java.util.concurrent.locks.ReentrantLock;
4/**
5 * 银行账户
6 * @author Administrator
7 **/
8public class Acount {
9 private int count;
10 private ReentrantLock lock = new ReentrantLock();
11 /**
12 * 存钱
13 * @param money
14 */
15 public void addAcount(String name,int money) {
16 lock.lock();
17 try{
18 // 存钱
19 count += money;
20 System.out.println(name+"...存入:"+money);
21 }finally {
22 lock.unlock();
23 }
24 }
25 /**
26 * 取钱
27 * @param money
28 */
29 public void subAcount(String name,int money) {
30 lock.lock();
31 try{
32 // 先判断账户现在的余额是否够取钱金额
33 if(count-money < 0){
34 System.out.println("账户余额不足!");
35 return;
36 }
37 // 取钱
38 count -= money;
39 System.out.println(name+"...取出:"+money);
40 }finally {
41 lock.unlock();
42 }
43 }
44 /**
45 * 查询余额
46 */
47 public void SelectAcount(String name) {
48 System.out.println(name+"...余额:"+count);
49 }
50}
运行效果:
当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形:
线程 A 当前持有互斥所锁 lock1,线程 B 当前持有互斥锁 lock2。
接下来,当线程 A 仍然持有 lock1 时,它试图获取 lock2,因为线程 B 正持有 lock2,因此线程 A 会阻塞等待线程 B 对 lock2 的释放。
如果此时线程 B 在持有 lock2 的时候,也在试图获取 lock1,因为线程 A 正持有 lock1,因此线程 B 会阻塞等待 A 对 lock1 的释放。
二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。
1package com.testDeadLockDemo;
2
3public class LockA {
4
5 private LockA(){}
6
7 public static final LockA lockA = new LockA();
8 }
9
10
11package com.testDeadLockDemo;
12
13 public class LockB {
14
15 private LockB(){}
16
17 public static final LockB lockB = new LockB();
18 }
19
20
21package com.testDeadLockDemo;
22
23public class DeadLock implements Runnable{
24 private int i=0;
25
26 @Override
27 public void run() {
28 while(true) {
29 if(i%2==0){
30 synchronized(LockA.lockA) {
31 System.out.println("if...lockA");
32 synchronized(LockB.lockB) {
33 System.out.println("if...lockB");
34 }
35 }
36 }else {
37 synchronized(LockB.lockB) {
38 System.out.println("else...lockB");
39 synchronized(LockA.lockA) {
40 System.out.println("else...lockA");
41 }
42 }
43 }
44 i++;
45 }
46
47 }
48}
测试:
1 package com.testDeadLockDemo;
2
3 public class Test {
4 public static void main(String[] args) {
5 DeadLock deadLock = new DeadLock();
6
7 Thread t1 = new Thread(deadLock);
8 Thread t2 = new Thread(deadLock);
9 t1.start();
10 t2.start();
11
12 }
13 }
运行结果:
在共享资源中增加镖旗,当镖旗为真的时候才可以存钱,存完了就把镖旗设置成假,当取款的时候发现镖旗为假的时候,可以取款,取完款就把镖旗设置为真。
只需修改 Account 类 和 测试类 即可
1 package com.test.threadDemo2;
2
3 /**
4 * 银行账户
5 * @author Administrator
6 **/
7 public class Acount {
8 private boolean flag=false;
9 // 默认flag 为false,要求必须先存款再取款
10 private int count=0;
11
12 /**
13 * 存钱
14 * @param money
15 */
16 public void addAcount(String name,int money) {
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 * @param money
40 */
41 public void subAcount(String name,int money) {
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 void SelectAcount(String name) {
69 System.out.println(name+"...余额:"+count);
70 }
71 }
72
73
74 package com.test.threadDemo2;
75
76 public class ThreadDemo2 {
77 public static void main(String[] args) {
78
79 // 开个银行帐号
80 Acount acount = new Acount();
81
82 // 开银行帐号之后银行给张银行卡
83 Card card1 = new Card("card1",acount);
84 Card card2 = new Card("card2",acount);
85 Card card3 = new Card("card3",acount);
86
87 // 开银行帐号之后银行给张存折
88 Paper paper1 = new Paper("paper1",acount);
89 Paper paper2 = new Paper("paper2",acount);
90
91 // 创建三个银行卡
92 Thread thread1 = new Thread(card1,"card1");
93 Thread thread2 = new Thread(card2,"card2");
94 Thread thread3 = new Thread(card3,"card3");
95 // 创建两个存折
96 Thread thread4 = new Thread(paper1,"paper1");
97 Thread thread5 = new Thread(paper2,"paper2");
98
99 thread1.start();
100 thread2.start();
101 thread3.start();
102
103 thread4.start();
104 thread5.start();
105 }
106 }
运行结果:
使用同步锁也可以达到相同的目的:
1package com.test.threadDemo2;
2
3import java.util.concurrent.locks.Condition;
4import java.util.concurrent.locks.ReentrantLock;
5/**
6 * 银行账户
7 * @author Administrator
8 **/
9public class Acount2 {
10 private boolean flag=false;
11 // 默认flag 为false,要求必须先存款再取款
12 private int count=0;
13 private final ReentrantLock lock = new ReentrantLock();
14 private final Condition condition = lock.newCondition();
15 /**
16 * 存钱
17 * @param money
18 */
19 public void addAcount(String name,int money) {
20 lock.lock();
21 try {
22 // flag 为true 表示可以存款,否则不可以存款
23 if(flag) {
24 try {
25 condition.await();
26 } catch (InterruptedException e) {
27 // TODO Auto-generated catch block e.printStackTrace();
28 }
29 }else {
30 // 存钱
31 count += money;
32 System.out.println(name+"...存入:"+money+"..."+Thread.currentThread().getName());
33 SelectAcount(name);
34 flag = true;
35 condition.signalAll();
36 }
37 }finally {
38 lock.unlock();
39 }
40 }
41 /**
42 * 取钱
43 * @param money
44 */
45 public void subAcount(String name,int money) {
46 lock.lock();
47 try {
48 if(!flag) {
49 try {
50 condition.await();
51 } catch (InterruptedException e) {
52 e.printStackTrace();
53 }
54 }else {
55 // 先判断账户现在的余额是否够取钱金额
56 if(count-money < 0){
57 System.out.println("账户余额不足!");
58 return;
59 }
60 // 取钱
61 count -= money;
62 System.out.println(name+"...取出:"+money+"..."+Thread.currentThread().getName());
63 SelectAcount(name);
64 flag = false;
65 condition.signalAll();
66 }
67 }finally {
68 lock.unlock();
69 }
70 }
71 /**
72 * 查询余额
73 */
74 public void SelectAcount(String name) {
75 System.out.println(name+"...余额:"+count);
76 }
77}
文章来源:https://www.cnblogs.com/mujingyu/p/7856388.html