zoukankan      html  css  js  c++  java
  • Java静态static工具类线程安全问题研究

    针对静态方法有以下一些前提:

    • 静态方法和实例方法的区别是静态方法只能引用静态变量,静态方法通过类名来调用,实例方法通过对象实例来调用
    • 每个线程都有自己的线程栈,栈与线程同时创建,每一个虚拟机线程都有自己的程序计数器PC,在任何时刻,一个虚拟机线程只会执行一个方法的代码,这个方法称为该线程的当前方法,如果这个方法不是native的,程序计数器就保存虚拟机正在执行的字节码指令的地址。
    • 线程调用方法的时候会创建栈帧,用于保存局部变量表和操作数栈以及指向该类常量池的引用
    • 静态方法虽然是同一个方法,但是不同线程在调用,程序计数器的值是不一样的,操作这两个线程不会相互影响(假设不存在访问共享变量的情况)

    在设计工具类时,这要没有共享的变量,静态工具类方法不需要加锁。在使用单例模式做工具类,这个时候静态方法就需要加锁,因为所有的线程虽然都是有自己的方法栈,但是在方法栈中操作的是同一个对象的实体(所以需要加锁,加锁的代价是所有的线程需要等待锁的释放才能使用该对象的引用)在使用多例模式做工具类时,这个时候也是不需要加锁,因为所有的线程都有自己的方法栈,但是方法栈帧中创建了独立的对象引用,每个线程都是在操作各自方法栈帧中的局部对象引用,所以这时候不要同步。

    由于web天生并发性,导致我们的一般java工具类会在这样的环境下出现问题。

    其实问题的根源就是我们的工具类不是线程安全的。

    有一个生成md5的工具类:

    public class MD5 {  
        private static long[] state = new long[4];  
        private static long[] count = new long[2];  
        private static byte[] buffer = new byte[64];  
        private static byte[] digest = new byte[16];  
      
        private String digestHexStr="";  
        public static MD5() {  
        }  
        //计算MD5  
        public static String getMD5ofStr(String inbuf) {  
        }  
    }  

    变量state, count ,buffer ,digest 算法中用到的核心数据,digestHexStr存放计算的结果。在多线程并发访问的情况下,这些变量是会被“共享”的,所以会导致计算结果不准确甚至出现异常。

    有三种比较简单的方法可以解决:

    getMD5ofStr方法变成非static的普通方法,这样每次调用这个方法都必须new一个新的MD5对象。

    getMD5ofStr方法变成同步方法(同步代码块,显示锁,synchronized method都可以)。

    将被“共享”的变量放到方法getMD5ofStr里面,不设置成员变量。

    考虑到现在系统有些地方已经开始使用这个工具类了,不便改动结构,先采用第二种快速修复bug,然后腾出时间用第三种发放重构。

    PS:

    工具类能否设计成单例?如果能最好。单例能减少创建类和分配内存的开销,减少垃圾回收次数。

    工具类能否设计成不变类?如果能最好,不变类天生线程安全!

    在并发环境下,工具类能不能不用同步?不管怎么说,同步都是要有一些开销的。

    PPS:

    这样会好一些:

    public final class MD5 {  
        private MD5(){}  
        //计算MD5  
        public static String getMD5ofStr(String inbuf) {  
            long[] state = new long[4];  
            long[] count = new long[2];  
            byte[] buffer = new byte[64];  
            byte[] digest = new byte[16];  
            String digestHexStr="";  
            ........  
        }  
    }  

    其它示例:

    public class StaticTest {  
        private static int count = 0;  
        private static int counts = 0;  
      
        /** 
         * 不会存在并发问题 
         * 
         * @return 
         */  
        public static String getTestStr() {  
            String xx = Thread.currentThread().toString();  
            try {  
                Thread.sleep(10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            return xx;  
        }  
      
        /** 
         * 存不存在并发问题与传入的变量有关 
         * 假如thread a和thread b都在操作对象a则存在 
         * @param user 
         * @return 
         */  
        public static String getTestUser(User user) {  
            String str = "id: " + user.getId() + "name: " + user.getName();  
            try {  
                Thread.sleep(10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            return str;  
        }  
      
        /** 
         * 存在并发问题 
         * 
         * @return 
         */  
        public static int getTestCount() {  
            count++;  
            count++;  
            count++;  
            count++;  
            count++;  
            try {  
                Thread.sleep(10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            count++;  
            count++;  
            count++;  
            count++;  
            count++;  
            return count;  
        }  
      
        /** 
         * 不存在并发问题 
         * 
         * @return 
         */  
        public synchronized static int getTestCountS() {  
            counts++;  
            counts++;  
            counts++;  
            counts++;  
            counts++;  
            try {  
                Thread.sleep(10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            counts++;  
            counts++;  
            counts++;  
            counts++;  
            counts++;  
            return counts;  
        }  
      
        public static void main(String[] args) {  
            User user = new User();  
            for (int i = 0 ; i < 1000 ; i++){  
                final int finalI = i;  
                Thread thread = new Thread(new Runnable() {  
                    @Override  
                    public void run() {  
                        User userTmp = new User();  
                        user.setId(finalI);  
                        user.setName(Thread.currentThread().toString());  
                        userTmp.setId(finalI);  
                        userTmp.setName(Thread.currentThread().toString());  
                        //局部变量不存在问题  
                        System.out.println("getTestStr: " + Thread.currentThread()  + StaticTest.getTestStr());  
                        //与user有关  
                        System.out.println("getTestUser: " + Thread.currentThread() + StaticTest.getTestUser(user));  
                        System.out.println("getTestUseS: " + Thread.currentThread()  + StaticTest.getTestUser(userTmp));  
      
                        //线程不安全  
                        System.out.println("getTestCount: "  + Thread.currentThread() + StaticTest.getTestCount() % 10);  
      
                        //安全但是慢需要加锁  
                        System.out.println("getTestCountS: "  + Thread.currentThread() + StaticTest.getTestCountS() % 10);  
                    }  
                });  
                thread.start();  
            }  
      
      
        }  
    }  

    参考:

    http://blog.csdn.net/thekenofdis/article/details/74529886(以上内容部分转自此篇文章)

    https://www.cnblogs.com/LvLoveYuForever/p/6077148.html

    http://www.genshuixue.com/i-cxy/p/7637981

    http://wuhaocn.iteye.com/blog/2269391(以上内容部分转自此篇文章)

  • 相关阅读:
    兼容ie8 rgba()用法 滤镜filter的用法
    解決BufferedReader读取UTF-8文件中文乱码
    基于JavaScript实现表单密码的隐藏和显示出来
    Java多线程学习(转载)
    使用java 程序创建格式为utf-8文件的方法(写入和读取json文件)
    java获取classpath文件路径空格转变成了转义字符%20的问题
    java中Class.getResource用法
    事务传播行为和特性
    事务隔离级别
    使用Condition循环依次打印123
  • 原文地址:https://www.cnblogs.com/EasonJim/p/8301775.html
Copyright © 2011-2022 走看看