多个线程访问共享对象和数据的方式
接下来我们探讨多个线程之间共享数据的方式:
现在有A线程,B线程,C线程,要访问一个共同的数据,我们程序代码该怎么写?举个例子
首先,我们来写一段卖票的代码,刚开始一共有100张票,然后,有两个窗口在卖这100张票,代码如下:
public class MultiThreadShareData2 { public static void main(String[] args) { ShareData1 data1 = new ShareData1(); new Thread(data1).start(); new Thread(data1).start(); } }
扫描二维码关注公众号,回复:
1271958 查看本文章
class ShareData1 implements Runnable { private int count = 100;// 这个数据是所有运行这个runnable对象的线程共享的 public void run() { while (true) { count--; } } } //这部分代码存在线程安全问题,因为count--不是原子性操作,减运算和赋值运算,所以在此过程中可能被其它线程打断,造成数据污染。 //我们可以使用原子操作类来声明count变量,解决线程安全问题。 |
以上代码思路:
多个线程之间共享数据的方式一:把数据封装在Runnable对象(封装数据的类实现Runnable接口)中,要共享数据的线程使用同一个Runnable对象,数据就共享了。也就是:如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable中封装了那个共享数据,把Runnable传给哪个线程,哪个线程就开始操作这个Runnable里面的数据了。这个Runnable同时传给100个线程,那么这100个线程就同时操作这一个Runnable对象。
设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
接下来,如果要共享数据的这些线程,它们要运行的代码不同,该怎么共享数据呢?比如说我一个线程要做加法,一个线程要做减法,这样就需要两个Runnable对象,需要两个run方法,一个run方法负责减,一个run方法负责加。数据对象只能有一个,因为要共享,但是Runnable对象要有两个,因为线程要做两件不同的事。这应该怎么实现呢?
以下是初步实现的代码:
public class MultiThreadShareData2 { public static void main(String[] args) { final ShareData1 data1 = new ShareData1(); new Thread(new Runnable() {
public void run() { data1.increment();
} }).start(); new Thread(new Runnable() {
public void run() { data1.decrement();
} }).start(); } static class ShareData1{ private int j = 0; public synchronized void increment(){//有多个线程操作,需要同步 j++; } Public synchronized void decrement(){ j--; } } } |
这样,就要有两个Runnable对象,因为它们的代码不一样,所以Runnable对象就要有多个。第一个的run方法运行减的逻辑,第二个的run方法运行加的逻辑。由于它们操作的是同一个ShareData1对象,那个共享数据j装在同一个ShareData1对象里面,这样就及可以运行不同的代码逻辑,又可以共享数据。这是一种方案。
这里,我们总结一下多线程访问共享对象和数据的几种方案:
l 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。
l 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:
Ø 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
public class MultiThreadShareData2 { public static void main(String[] args) { ShareData1 data2 = new ShareData1(); new Thread(new MyRunnable1(data2)).start(); new Thread(new MyRunnable2(data2)).start(); } } class MyRunnable1 implements Runnable { private ShareData1 data1; public MyRunnable1(ShareData1 data1) { this.data1 = data1; } public void run() { data1.decrement(); } } class MyRunnable2 implements Runnable { private ShareData1 data1; public MyRunnable2(ShareData1 data1) { this.data1 = data1; } public void run() { data1.increment(); } } class ShareData1 { private int j = 0; public synchronized void increment() { j++; } public synchronized void decrement() { j--; } } |
把共享数据放到一个对象里面,对数据的操作方法,也放到这个对象里面,然后有两个runnable都去操作同一个对象,就ok了。
Ø 将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。
public class MultiThreadShareData { // private static ShareData1 data1 = new ShareData1(); public static void main(String[] args) { final ShareData1 data1 = new ShareData1(); new Thread(new Runnable() { public void run() { data1.decrement(); } }).start(); new Thread(new Runnable() { public void run() { data1.increment(); } }).start(); } } class ShareData1{ private int j = 0; public synchronized void increment() { j++; } public synchronized void decrement() { j--; } } |
一个外部类里面有两个内部类,这两个内部类如何共享数据,都操作外部类的同一个成员,是不是可以,因为A内部也可以访问外部类的成员,B内部类也可以访问外部类的成员,它们共用了同一个外部类,所以,两个Runnable对象要共享同一份数据,可以把两个runnable作为一个类的内部类,把他们共享的数据作为那个外部类的成员变量。
Ø 上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。
Ø 总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。
l 极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。
那么,现在我们再回到之前的那道代码题:设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
代码实现如下:
//方案一,内部类共享成员变量 public class ThreadTest1 { private int j; public static void main(String args[]) { ThreadTest1 tt = new ThreadTest1(); Inc inc = tt.new Inc(); Dec dec = tt.new Dec(); for (int i = 0; i < 2; i++) { Thread t = new Thread(inc); t.start(); t = new Thread(dec); t.start(); } } private synchronized void inc() { j++; System.out.println(Thread.currentThread().getName() + "-inc:" + j); } private synchronized void dec() { j--; System.out.println(Thread.currentThread().getName() + "-dec:" + j); } class Inc implements Runnable { public void run() { for (int i = 0; i < 100; i++) { inc(); } } } class Dec implements Runnable { public void run() { for (int i = 0; i < 100; i++) { dec(); } } } } |
//共享数据封装到一个对象,然后内部类共享这个对象 class A { public static void main(String[] args) { new A().call(); }
JManager j = new JManager();
public void call() { for (int i = 0; i < 2; i++) { new Thread(new Runnable() { public void run() { while (true) { j.accumulate(); } } }).start(); new Thread(new Runnable() { public void run() { while (true) { j.subtract(); } } }).start(); } } } class JManager { private int j = 0; public synchronized void subtract() { j--; } public synchronized void accumulate() { j++; } } |