美文网首页Android面试Android开发Android开发
Java多线程2-多个线程之间共享数据

Java多线程2-多个线程之间共享数据

作者: 香沙小熊 | 来源:发表于2018-11-26 19:03 被阅读53次

    线程范围的共享变量

    多个业务模块针对同一个static变量的操作 要保证在不同线程中 各模块操作的是自身对应的变量对象

    public class ThreadScopeSharaData {
    
        private static  int data = 0 ;
    
        public static void main(String[] args) {
            for(int i = 0 ;i<2 ;i++){
                new Thread(new Runnable(){
    
                    @Override
                    public void run() {
                        data = new Random().nextInt();
                        System.out.println(Thread.currentThread().getName()+ " put random data:"+data);
                        new A().get() ;
                        new B().get() ;
                    }
    
                }).start() ;
            }
    
        }
    
        static class A {
            public int get(){
                System.out.println("A from " + Thread.currentThread().getName()
                        + " get data :" + data);
                return data ;
            }
        }
    
        static class B{
            public int get(){
                System.out.println("B from " + Thread.currentThread().getName()
                        + " get data :" + data);
                return data ;
            }
        }
    }
    
    

    模块A ,B都需要访问static的变量data 在线程0中会随机生成一个data值 假设为10 那么此时模块A和模块B在线程0中得到的data的值为10 ;在线程1中 假设会为data赋值为20 那么在当前线程下

    模块A和模块B得到data的值应该为20

    看程序执行的结果:

    Thread-0 put random data:-2009009251
    Thread-1 put random data:-2009009251
    A from Thread-0 get data :-2009009251
    A from Thread-1 get data :-2009009251
    B from Thread-0 get data :-2009009251
    B from Thread-1 get data :-2009009251
    
    Thread-0 put random data:-2045829602
    Thread-1 put random data:-1842611697
    A from Thread-0 get data :-1842611697
    A from Thread-1 get data :-1842611697
    B from Thread-0 get data :-1842611697
    B from Thread-1 get data :-1842611697
    

    会出现两种情况
    1.由于线程执行速度,新的随机值将就的随机值覆盖 data 值一样
    2.data 值不一样,但 A、B线程都

    1.使用Map实现线程范围内数据的共享

    可是将data数据和当前允许的线程绑定在一块,在模块A和模块B去获取数据data的时候 是通过当前所属的线程去取得data的结果就行了。
    声明一个Map集合 集合的Key为Thread 存储当前所属线程 Value 保存data的值,代码如下:

    public class ThreadScopeSharaData {
    
    
        private static Map<Thread, Integer> threadData = new HashMap<>();
    
        public static void main(String[] args) {
    
            for (int i = 0; i < 2; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int data = new Random().nextInt();
                        System.out.println(Thread.currentThread().getName() + " put random data:" + data);
                        threadData.put(Thread.currentThread(), data);
                        new A().get();
                        new B().get();
    
                    }
                }).start();
    
            }
    
        }
    
        static class A {
            public void get() {
                int data = threadData.get(Thread.currentThread());
    
                System.out.println("A from " + Thread.currentThread().getName() + " get data:" + data);
    
            }
        }
    
        static class B {
            public void get() {
                int data = threadData.get(Thread.currentThread());
                System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data);
    
            }
        }
    }
    
    Thread-0 put random data:-123490895
    Thread-1 put random data:-1060992440
    A from Thread-0 get data:-123490895
    A from Thread-1 get data:-1060992440
    B from Thread-0 get data:-123490895
    B from Thread-1 get data:-1060992440
    
    2.ThreadLocal实现线程范围内数据的共享

    (1)订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
    (2)银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
    (3)例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。
    4.实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。
    5.实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。
    (1)对基本类型的数据的封装,这种应用相对很少见。
    (2)对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。

    public class ThreadLocalTest {
    
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
    
            for (int i = 0; i < 2; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int data = new Random().nextInt();
                        System.out.println(Thread.currentThread().getName() + " put random data:" + data);
                        threadLocal.set(data);
                        new A().get();
                        new B().get();
    
                    }
                }).start();
    
            }
    
        }
    
        static class A {
            public void get() {
                int data = threadLocal.get();
    
                System.out.println("A from " + Thread.currentThread().getName() + " get data:" + data);
    
            }
        }
    
        static class B {
            public void get() {
                int data = threadLocal.get();
                System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data);
    
            }
        }
    }
    
    Thread-0 put random data:-2015900409
    Thread-1 put random data:-645411160
    A from Thread-0 get data:-2015900409
    A from Thread-1 get data:-645411160
    B from Thread-0 get data:-2015900409
    B from Thread-1 get data:-645411160
    
    优化
    public class ThreadLocalTest {
    
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    
        //private static ThreadLocal<MyThreadScopeData> myThreadScopeDataThreadLocal = new ThreadLocal<>();
    
    
        public static void main(String[] args) {
    
            for (int i = 0; i < 2; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int data = new Random().nextInt();
                        System.out.println(Thread.currentThread().getName() + " put random data:" + data);
                        threadLocal.set(data);
    
    //                    MyThreadScopeData myThreadScopeData = new MyThreadScopeData();
    //                    myThreadScopeData.setName("name" + data);
    //                    myThreadScopeData.setAge(data);
    //                    myThreadScopeDataThreadLocal.set(myThreadScopeData);
    
                        //获取与当前线程绑定的实例并设置值  
                        MyThreadScopeData.getThreadInstance().setName("name" + data);
                        MyThreadScopeData.getThreadInstance().setAge(data);
                        new A().get();
                        new B().get();
    
                    }
                }).start();
    
            }
    
        }
    
        static class A {
            public void get() {
                int data = threadLocal.get();
    
    
    //            MyThreadScopeData myData = myThreadScopeDataThreadLocal.get();
    //
    //
    //            System.out.println("A from " + Thread.currentThread().getName()
    //                    + " getMyData: " + myData.getName() + "," + myData.getAge());
    
                MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
                System.out.println("A from " + Thread.currentThread().getName()
                        + " getMyData: " + myData.getName() + "," + myData.getAge());
            }
        }
    
        static class B {
            public void get() {
                int data = threadLocal.get();
                //System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data);
    
                MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
                System.out.println("B from " + Thread.currentThread().getName()
                        + " getMyData: " + myData.getName() + "," + myData.getAge());
            }
        }
    }
    
    //一个绑定当前线程的类
    class MyThreadScopeData {
    
        private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<>();
        private String name;
        private int age;
    
        private MyThreadScopeData() {
        }
    
        //定义一个静态方法,返回各线程自己的实例
        //这里不必用同步,因为每个线程都要创建自己的实例,所以没有线程安全问题。
        public static MyThreadScopeData getThreadInstance() {
            //获取当前线程绑定的实例
            MyThreadScopeData instance = map.get();
            if (instance == null) {
                instance = new MyThreadScopeData();
                map.set(instance);
            }
            return instance;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
    
    }
    
    Thread-1 put random data:-1041517189
    Thread-0 put random data:-98835751
    A from Thread-1 getMyData: name-1041517189,-1041517189
    A from Thread-0 getMyData: name-98835751,-98835751
    B from Thread-1 getMyData: name-1041517189,-1041517189
    B from Thread-0 getMyData: name-98835751,-98835751
    
    实例:

    设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1,写出程序。
    1、如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,卖票系统就可以这么做。

    public class SellTicket {
        //卖票系统,多个窗口的处理逻辑是相同的
        public static void main(String[] args) {
            Ticket t = new Ticket();
            new Thread(t).start();
            new Thread(t).start();
        }
    }
    
    /**
     * 将属性和处理逻辑,封装在一个类中
     *
     * @author yang
     */
    class Ticket implements Runnable {
    
        private int ticket = 10;
    
        public synchronized void run() {
            while (ticket > 0) {
                ticket--;
                System.out.println("当前票数为:" + ticket);
            }
        }
    }
    

    2、如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,例如,设计2个线程。一个线程对j增加1,另外一个线程对j减1,银行存取款系统。

    public class MultiThreadShareData {
        private int j;
        public static void main(String[] args) {
            MultiThreadShareData multiThreadShareData = new MultiThreadShareData();
            for(int i=0;i<2;i++){
                new Thread(multiThreadShareData.new ShareData1()).start();//增加
                new Thread(multiThreadShareData.new ShareData2()).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 ShareData1 implements Runnable {
            public void run() {
                for(int i=0;i<5;i++){
                    Inc();
                }
            }
        }
        class ShareData2 implements Runnable {
            public void run() {
                for(int i=0;i<5;i++){
                    Dec();
                }
            }
        }
    }
    
    Thread-0 inc 1
    Thread-0 inc 2
    Thread-0 inc 3
    Thread-0 inc 4
    Thread-0 inc 5
    Thread-1 dec 4
    Thread-1 dec 3
    Thread-2 inc 4
    Thread-2 inc 5
    Thread-2 inc 6
    Thread-2 inc 7
    Thread-2 inc 8
    Thread-1 dec 7
    Thread-1 dec 6
    Thread-1 dec 5
    Thread-3 dec 4
    Thread-3 dec 3
    Thread-3 dec 2
    Thread-3 dec 1
    Thread-3 dec 0
    
    特别感谢:

    JAVA 并发编程-多个线程之间共享数据
    多线程:(五)多个线程之间共享数据

    相关文章

      网友评论

        本文标题:Java多线程2-多个线程之间共享数据

        本文链接:https://www.haomeiwen.com/subject/zsabqqtx.html