【Java多线程与并发库】7.多个线程之间共享数据的方式探讨

时间:2021-03-26 01:04:21


多个线程访问共享对象和数据的方式
1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个
Runnable对象中有一个共享数据,例如售票系统就可以这么做这么做。

2.如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有
如下两种方式来实现这些Runnable对象之间的数据共享:
(1)将数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。
每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对
该数据进行的各个操作的互斥和通信。

(2)将这些Runnable对象作为某一类中的内部类,共享数据作为外部类中的成员变量,
每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操
作的互斥和通信。作为内部类的各个Runnable对象调用外部类的这些方法。

(3)上面两种方式的组合:将共享数据封装在另一个对象中,每个线程对共享数据的操作
方法也分配到那个对象身上去完成,对象作为这个外部对象中的成员内部类或局部内部类。

总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类
中,这样比较容易实现他们之间的同步互斥通信。

我们通过完成下边这个面试题来实践该理论:
问题:设计4个线程,其中两个线程每次对j增加1,
另外两个线程对j每次减少1。写出线程。

下面先是我自己的写法:

package cn.edu.hpu.test;

public class ThreadTest8 {

private static int j = 0;

public static void main(String[] args) {
R1 r1=new R1();
R2 r2=new R2();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r2).start();
new Thread(r2).start();

}

static class R1 implements Runnable{

public synchronized void run() {
j=j+1;
System.out.println(Thread.currentThread().getName()
+":j增加1,变为:"+j);
}

}

static class R2 implements Runnable{

public synchronized void run() {
j=j-1;
System.out.println(Thread.currentThread().getName()
+":j减去1,变为:"+j);
}

}

}

效果:


【Java多线程与并发库】7.多个线程之间共享数据的方式探讨



我的思路就是开线程的时候,给run方法加一个线程锁,每一个线程操作j的时候都是


独立的不被打断的。



为了符合一开始的理论,我把j封装到一个Class里面使用:


package cn.edu.hpu.test;

public class ThreadTest8 {

private static Data data = new Data();

public static void main(String[] args) {
data.setJ(0);
R1 r1=new R1(data);
R2 r2=new R2(data);
new Thread(r1).start();
new Thread(r1).start();
new Thread(r2).start();
new Thread(r2).start();

}

static class R1 implements Runnable{

private Data data;
public R1(Data data){
this.data=data;
}

public synchronized void run() {
int j=data.getJ();
data.setJ(j+1);
System.out.println(Thread.currentThread().getName()
+":j加上1,变为:"+data.getJ());
}

}

static class R2 implements Runnable{

private Data data;
public R2(Data data){
this.data=data;
}

public synchronized void run() {
int j=data.getJ();
data.setJ(j-1);
System.out.println(Thread.currentThread().getName()
+":j减去1,变为:"+data.getJ());
}

}

}

class Data{

private int j;

public int getJ() {
return j;
}

public void setJ(int j) {
this.j = j;
}

}

这里在创建Runnable的时候,将Data对象传入进去。



下面的代码和上面的代码的区别是,第一段代码的线程是取共用的全局变量中去取数据,


而第二段代码的线程是根据传进来的对象来进行数据的操作,而这个对象是一个共用的全局变量。



但是实际上上面两个方法是有一个严重的问题的,就是数据同步问题。


反复运行上面的代码,会出现类似这种情况:


【Java多线程与并发库】7.多个线程之间共享数据的方式探讨



表面上看每一个线程的Run方法都是加了锁的,但是单单只能保证每一个Runnable在


操作数据的时候不会被打断,但是j的操作权是开放的,别的线程也可以同时修改j的值,


所以这里没有真正实现互斥。



解决办法就是,在要被操作的类中进行加锁,每一个线程在操作这个类的时候,j是否能被加还


是被减的权利是j所在的类而决定的,就是j的提供一个给j加1和给j减1的方法,然后全部加锁,


这样就保证一个线程在改变j的数据的时候,因为方法加锁,所以另外一个线程无法去改变这个值。



最终代码:


package cn.edu.hpu.test;

public class ThreadTest8 {

private static Data data = new Data();

public static void main(String[] args) {
data.setJ(0);
R1 r1=new R1(data);
R2 r2=new R2(data);
new Thread(r1).start();
new Thread(r1).start();
new Thread(r2).start();
new Thread(r2).start();

}

static class R1 implements Runnable{

private Data data;
public R1(Data data){
this.data=data;
}

public synchronized void run() {
data.add();
}

}

static class R2 implements Runnable{

private Data data;
public R2(Data data){
this.data=data;
}

public synchronized void run() {
data.minus();
}

}

}

class Data{

private int j;

public void setJ(int j){
this.j=j;
}

public synchronized void add(){
j++;
System.out.println(Thread.currentThread().getName()
+":j加上1,变为:"+j);
}

public synchronized void minus(){
j--;
System.out.println(Thread.currentThread().getName()
+":j减去1,变为:"+j);
}
}

结果:


【Java多线程与并发库】7.多个线程之间共享数据的方式探讨


【Java多线程与并发库】7.多个线程之间共享数据的方式探讨

【Java多线程与并发库】7.多个线程之间共享数据的方式探讨

【Java多线程与并发库】7.多个线程之间共享数据的方式探讨

可以看到,不论怎么运行,j的值都会平稳的改变。


所以我们按照上面的方式操作就会真正实现多个线程共享数据。


​​​