java 静态类 安全_Java静态static工具类线程安全问题研究

本文探讨了Java工具类在多线程环境下的线程安全问题,以MD5计算为例,指出静态方法中共享变量可能导致的并发问题。提出了三种解决方案,包括使方法非静态、同步方法和局部变量。强调了单例和不变类在工具类设计中的优势,并提供了修改后的代码示例,以提高并发性能和安全性。
摘要由CSDN通过智能技术生成

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

静态方法和实例方法的区别是静态方法只能引用静态变量,静态方法通过类名来调用,实例方法通过对象实例来调用

每个线程都有自己的线程栈,栈与线程同时创建,每一个虚拟机线程都有自己的程序计数器PC,在任何时刻,一个虚拟机线程只会执行一个方法的代码,这个方法称为该线程的当前方法,如果这个方法不是native的,程序计数器就保存虚拟机正在执行的字节码指令的地址。

线程调用方法的时候会创建栈帧,用于保存局部变量表和操作数栈以及指向该类常量池的引用

静态方法虽然是同一个方法,但是不同线程在调用,程序计数器的值是不一样的,操作这两个线程不会相互影响(假设不存在访问共享变量的情况)

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

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

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

有一个生成md5的工具类:

public classMD5 {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 staticMD5() {

}//计算MD5

public staticString getMD5ofStr(String inbuf) {

}

}

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

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

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

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

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

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

PS:

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

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

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

PPS:

这样会好一些:

public final classMD5 {privateMD5(){}//计算MD5

public staticString 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 classStaticTest {private static int count = 0;private static int counts = 0;/*** 不会存在并发问题

*

*@return

*/

public staticString getTestStr() {

String xx=Thread.currentThread().toString();try{

Thread.sleep(10);

}catch(InterruptedException e) {

e.printStackTrace();

}returnxx;

}/*** 存不存在并发问题与传入的变量有关

* 假如thread a和thread b都在操作对象a则存在

*@paramuser

*@return

*/

public staticString getTestUser(User user) {

String str= "id: " + user.getId() + "name: " +user.getName();try{

Thread.sleep(10);

}catch(InterruptedException e) {

e.printStackTrace();

}returnstr;

}/*** 存在并发问题

*

*@return

*/

public static intgetTestCount() {

count++;

count++;

count++;

count++;

count++;try{

Thread.sleep(10);

}catch(InterruptedException e) {

e.printStackTrace();

}

count++;

count++;

count++;

count++;

count++;returncount;

}/*** 不存在并发问题

*

*@return

*/

public synchronized static intgetTestCountS() {

counts++;

counts++;

counts++;

counts++;

counts++;try{

Thread.sleep(10);

}catch(InterruptedException e) {

e.printStackTrace();

}

counts++;

counts++;

counts++;

counts++;

counts++;returncounts;

}public static voidmain(String[] args) {

User user= newUser();for (int i = 0 ; i < 1000 ; i++){final int finalI =i;

Thread thread= new Thread(newRunnable() {

@Overridepublic voidrun() {

User userTmp= newUser();

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();

}

}

}

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值