跳至主要內容

多个线程之间共享数据的方式探讨


多个线程之间共享数据的方式探讨

##内容摘要

多个线程之间共享数据,按照每个线程执行代码是否相同,我们可以采取不同的处理方式,这里通过简单的卖票示例说明了当每个线程执行相同代码的情况,对于多个线程执行不同代码的情况,处理方式比较灵活,这里主要介绍了2种方式,通过2种方式的对比和归纳,我们可以总结出在多个线程执行不同的代码情况下,如何进行代码的设计

##1. 如果每个线程执行的代码相同
可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统

###1.1 简单的卖票系统示例

class Ticket implements Runnable{  
    private  int tick = 20;  
    Object obj = new Object();  
  
    public void run(){  
        while(true){  
            synchronized(obj){  
                if(tick>0){  
                    //只能try,因为run是复写了Runnable接口的run,接口的run没有抛  
                    //try{Thread.sleep(10);}catch(Exception e){}  
                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  
                }  
            }  
        }  
    }  
}  
  
class  TicketDemo  
{  
    public static void main(String[] args) {  
          
        //只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据  
        Ticket t = new Ticket();  
  
        Thread t1 = new Thread(t);  
        Thread t2 = new Thread(t);  
        Thread t3 = new Thread(t);  
        Thread t4 = new Thread(t);  
        t1.start();  
        t2.start();  
        t3.start();  
        t4.start();  
    }  
} 
Thread-0....sale : 20
Thread-0....sale : 19
Thread-0....sale : 18
Thread-0....sale : 17
Thread-0....sale : 16
Thread-0....sale : 15
Thread-0....sale : 14
Thread-0....sale : 13
Thread-0....sale : 12
Thread-3....sale : 11
Thread-3....sale : 10
Thread-3....sale : 9
Thread-3....sale : 8
Thread-3....sale : 7
Thread-3....sale : 6
Thread-3....sale : 5
Thread-3....sale : 4
Thread-3....sale : 3
Thread-3....sale : 2
Thread-3....sale : 1

##2. 如果每个线程执行的代码不同

这时候不需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享。

###2.1 方式1
将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

思想:一个类提供数据和操作数据的同步方法,另外定义两个线程通过构造函数接收并操作数据,在主函数中直接创建线程对象,即可完成操作

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

思想:一个外部类里面有两个内部类,为了让这两个内部类共享数据,让它们都操作外部类的同一个成员,方法和数据都在这个成员身上,直接调用方法即可完成 数据的操作

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

###2.4 技巧总结
要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥或通信。极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。

###2.5 对于每个线程执行的代码不同下的3种方式,通过一个面试题来说明

需求:设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1,写出程序

###使用方式1实现
将数据和操作共享数据的方法封装在一个类中,定义两个Runnable实现类,让两个Runnable都持有共享数据的引用,在Runnable的构造函数中,直接传入去操作,在实现类的run方法中调用封装类的方法

public class MultyThreadShareMethod1 {  
      
    public static void main(String[] args){  
      
        //将数据封装到一个对象上,  
        ShareData2 data1 = new ShareData2();  
          
        //在runnable的构造函数中直接传入去操作  
        for(int i=0;i<2;i++){  
        new Thread(new MyRunnable1(data1)).start();  
        new Thread(new MyRunnable2(data1)).start();  
        }  
    }  
}  
 
//封装共享数据和操作共享数据方法的类  
class ShareData2{  
    private int j = 10;  
    public synchronized void increment() {  
        j++;  
        System.out.println(Thread.currentThread().getName()+" inc : "+j);  
    }  
    public synchronized void decrement() {  
        j--;  
        System.out.println(Thread.currentThread().getName()+" dec : "+j);  
    }  
}  
   
//增加的线程,需要传入一个共享数据  
class MyRunnable1 implements Runnable {  
      
    private ShareData2 data;  
    public MyRunnable1(ShareData2 data) {  
        this.data = data;  
    }  
    @Override  
    public void run() {  
        for(int i=0;i<10;i++){  
        data.increment();  
        }  
    }  
}  
  
//减少的线程,需要传入一个共享数据  
class MyRunnable2 implements Runnable {   
    private ShareData2 data;  
    public MyRunnable2(ShareData2 data) {  
        this.data = data;  
    }  
    @Override  
    public void run() {  
        for(int i=0;i<10;i++){  
        data.decrement();  
        }  
    }  
}  

输出结果

Thread-0 inc : 11
Thread-0 inc : 12
Thread-0 inc : 13
Thread-0 inc : 14
Thread-0 inc : 15
Thread-0 inc : 16
Thread-0 inc : 17
Thread-0 inc : 18
Thread-0 inc : 19
Thread-0 inc : 20
Thread-1 dec : 19
Thread-3 dec : 18
Thread-3 dec : 17
Thread-3 dec : 16
Thread-3 dec : 15
Thread-3 dec : 14
Thread-3 dec : 13
Thread-3 dec : 12
Thread-3 dec : 11
Thread-3 dec : 10
Thread-3 dec : 9
Thread-2 inc : 10
Thread-2 inc : 11
Thread-1 dec : 10
Thread-1 dec : 9
Thread-1 dec : 8
Thread-1 dec : 7
Thread-1 dec : 6
Thread-1 dec : 5
Thread-1 dec : 4
Thread-1 dec : 3
Thread-1 dec : 2
Thread-2 inc : 3
Thread-2 inc : 4
Thread-2 inc : 5
Thread-2 inc : 6
Thread-2 inc : 7
Thread-2 inc : 8
Thread-2 inc : 9
Thread-2 inc : 10

###使用方式2实现

将数据和操作共享数据的方法封装在一个类中

两个Runnable作为它的内部类,相对于方式1,这里没有将数据传给Runnable,而是让它们自己去取,在自己的run方法中调用操作数据的方法

这里的共享变量可以定义为静态类型的成员变量,也可以定义为final类型的局部变量。

public class MultyThreadShareData {  
      
    //共享数据作为外部类的成员变量  
    //private static ShareData data = new ShareData();  
    public static void main(String[] args){  
          
        //也可以定义为final类型的局部变量  
        final ShareData data = new ShareData();  
          
        //开启4条线程  
        for(int i=0;i<2;i++){  
          
        //增加的线程  
        new Thread(new Runnable(){  
            @Override  
            public void run() {  
                for(int i=0;i<100;i++){  
                    data.increment();  
                    }  
            }  
        }).start();  
        //减少的线程  
        new Thread(new Runnable(){  
            @Override  
            public void run() {  
                for(int i=0;i<100;i++){  
                    data.decrement();  
                }  
            }  
        }).start();  
        }  
    }  
}  
//封装共享数据和操作共享数据方法的类  
class ShareData{  
    private int j = 0;  
    public synchronized void increment() {  
        j++;  
        System.out.println(Thread.currentThread().getName()+" inc : "+j);  
    }  
    public synchronized void decrement() {  
        j--;  
        System.out.println(Thread.currentThread().getName()+" dec : "+j);  
    }  
}  

###两种方式的组合实现

public class MultyThreadShareDataTest { 
private int j;   
public static void main(String args[]){   
    MultyThreadShareDataTest tt = new MultyThreadShareDataTest();   
        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();   
           }   
       }   
    }   
}