多个线程访问共享对象和数据的方式
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);
}
}
}
效果:
我的思路就是开线程的时候,给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对象传入进去。
下面的代码和上面的代码的区别是,第一段代码的线程是取共用的全局变量中去取数据,
而第二段代码的线程是根据传进来的对象来进行数据的操作,而这个对象是一个共用的全局变量。
但是实际上上面两个方法是有一个严重的问题的,就是数据同步问题。
反复运行上面的代码,会出现类似这种情况:
表面上看每一个线程的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);
}
}
结果:
可以看到,不论怎么运行,j的值都会平稳的改变。
所以我们按照上面的方式操作就会真正实现多个线程共享数据。