一.目标
谈到多线程共享数据,理想情况下我们希望做到“同步”和“互斥”。这是目标我们暂且把它先放到这。
二.分类
多线程共享数据通常的场景有一下两种:
场景一:
卖票,我们都买过火车票。要买火车票我们可以去车站,也可以通过代售点(或网购),但不管有多少种方式火车票的总数是一定的。
场景抽象:
对于卖票系统每个线程的核心执行的代码都相同(就是票数–)。
解决方法:
只需创建一个Runnable,这个Runnable里有那个共享数据。
代码模拟:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
多线程共享数据;public
class Ticket implementsRunnable{ privateint
ticket = 10; publicvoid
run() { while(ticket>0){ ticket--; System.out.println("当前票数为:"+ticket); } }} |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
多线程共享数据;public
class SellTicket { /** * @param args */ publicstatic
void main(String[] args) { Ticket t =new
Ticket(); newThread(t).start(); newThread(t).start(); }} |
场景二:比较常见的例子,银行问题,我们对账户可以存钱也可以取钱,怎么保证这样的数据共享呢?
场景抽象:
每个线程执行的代码不同(比如上面的问题,对每个账户可以执行++操作和–操作),这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable之间的数据共享
解决方案:
有两种方法来解决此类问题:
- 将共享数据封装成另外一个对象中封装成另外一个对象中,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象身上完成,这样容易实现针对数据进行各个操作的互斥和通信
- 将Runnable对象作为偶一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法。
代码模拟:
以一道面试题为例:
“设计4个线程。,其中两个线程每次对j增加1,另外两个线程对j每次减1”
(第一种解法)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class MyData { privateint
j=0; public synchronized
voidadd(){ j++; System.out.println("线程"+Thread.currentThread().getName()+"j为:"+j); } public synchronized
voiddec(){ j--; System.out.println("线程"+Thread.currentThread().getName()+"j为:"+j); } publicint
getData(){ returnj; }} |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class AddRunnable implementsRunnable{ MyData data; publicAddRunnable(MyData data){ this.data= data; } publicvoid
run() { data.add(); }} |
|
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class DecRunnable implementsRunnable { MyData data; publicDecRunnable(MyData data){ this.data = data; } publicvoid
run() { data.dec(); }} |
测试代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class TestOne { /** * @param args */ publicstatic
void main(String[] args) { MyData data =new
MyData(); Runnable add =new
AddRunnable(data); Runnable dec =new
DecRunnable(data); for(inti=0;i<2;i++){ newThread(add).start(); newThread(dec).start(); } } |
解法分析:
优点:
1.这种解法代码写的有条理,简单易读,从main中很容易整理出思路
2.将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以和容易做到同步,只要在方法上加”synchronized“
不足:
代码写的比较繁琐,需要有多个类,不是那么简洁
个人观点:为了有条理个人比较喜欢这种写法。
(第二种解法)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class MyData { privateint
j=0; public synchronized
voidadd(){ j++; System.out.println("线程"+Thread.currentThread().getName()+"j为:"+j); } public synchronized
voiddec(){ j--; System.out.println("线程"+Thread.currentThread().getName()+"j为:"+j); } publicint
getData(){ returnj; }} |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public
class TestThread { /** * @param args */ publicstatic
void main(String[] args) { finalMyData data =
newMyData(); for(inti=0;i<2;i++){ newThread(new
Runnable(){ publicvoid
run() { data.add(); } }).start(); newThread(new
Runnable(){ publicvoid
run() { data.dec(); } }).start(); } }} |
解法分析:
与第一种方法的区别在于第二种方法巧妙的用了内部类共享外部类数据的思想,即把要共享的数据变得全局变量,这样就保证了操作的是同一份数据。同时内部类的方式使代码更加简洁。但是不如第一种解法条理那么清楚。
本文探讨了多线程环境下共享数据的两种典型场景:单一操作和混合操作,并提供了具体的代码示例,展示了如何通过不同方法实现数据同步与互斥。
885

被折叠的 条评论
为什么被折叠?



