java单例模式双重锁java多线程并发去调⽤⼀个类的静态⽅法安全性探讨
java多线程并发去调⽤⼀个类的静态⽅法安全性探讨
转⾃:
这篇⽂章主要讲多线程对静态⽅法访问的数据安全性
总结如下:
1,java在执⾏静态⽅法时,会在内存中拷贝⼀份,如果静态⽅法所在的类⾥⾯没有静态的变量,那么线程访问就是安全的,⽐如在javaee 中服务器必然会多线程的处理请求此时如果设计全局需要调⽤的静态⽅法,可⽤此种设计。
2,java在执⾏静态⽅法时,如果使⽤静态变量,同时类的函数设计时使⽤到了静态数据,最好在调⽤函数时使⽤synchronized关键字,否则会导致数据的不⼀致⾏。
3,加静态全局的变量,在多线程访问下定会出现数据的不⼀致⾏,最好使⽤synchronized关键字,确保数据的⼀致性,典型的代表就是单例模式。
总的结论:java是线程安全的,即对任何⽅法(包括静态⽅法)都可以不考虑线程冲突,但有⼀个前提,就是不能存在全局变量。如果存在全局变量,则需要使⽤同步机制。
如下通过⼀组对⽐例⼦从头讲解:
在多线程中使⽤静态⽅法会发⽣什么事?也就是说多线程访问同⼀个类的static静态⽅法会发⽣什么事?是否会发⽣线程安全问题?public class Test {
public static void operation(){
// ... do something
}
}
事实证明只要在静态函数中没有处理多线程共享数据,就不存在着多线程访问同⼀个静态⽅法会出现资源冲突的问题。下⾯看⼀个例⼦:public class StaticThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
StaticAction.print();
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new StaticThread()).start();
}
}
}
public class StaticAction {
public static int i = 0;
public static void print() {
int sum = 0;
for (int i = 0; i < 10; i++) {
System.out.print("step " + i + " is running.");
sum += i;
}
if (sum != 45) {
System.out.println("Thread error!");
}
System.out.println("sum is " + sum);
}
}
实际执⾏的结果显⽰各个线程对静态⽅法的访问是交叉执⾏的,但是这并不影响各个线程静态⽅法print()中sum值的计算。也就是说,在此过程中没有使⽤全局变量的静态⽅法在多线程中是安全的,静态⽅法是否引起线程安全问题主要看该静态⽅法是否对全局变量(静态变量static member)进⾏修改操作。
在多线程中使⽤同⼀个静态⽅法时,每个线程使⽤各⾃的实例字段(instance field)的副本,⽽共享⼀个静态字段(static field)。所以说,如果该静态⽅法不去操作⼀个静态成员,只在⽅法内部使⽤实例字段(instance field),不会引起安全性问题。
但是,如果该静态⽅法操作了⼀个静态变量,则需要静态⽅法中采⽤互斥访问的⽅式进⾏安全处理。我们来看⼀下没有使⽤互斥访问的话会产⽣怎样的问题:public class StaticAction {
public static int i = 0;
public static void incValue() {
int temp = StaticAction.i;
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
temp++;
StaticAction.i = temp;
}
}
public class StaticThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
StaticAction.incValue();
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new StaticThread()).start();
}
try {
Thread.sleep(1000); //预留⾜够的时间让上⾯的线程跑完
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(StaticAction.i);
}
}
实际运⾏结果显⽰i值为随机的数字。为了实现互斥访问,这时我们需要加⼊⼀个synchronized关键字。代码修改如下:
public class StaticAction {
public static int i = 0;
public synchronized static void incValue() {
int temp = StaticAction.i;
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
temp++;
StaticAction.i = temp;
}
}
public class StaticThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
StaticAction.incValue();
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new StaticThread()).start();
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(StaticAction.i);
}
}
运⾏结果则必然是100。
加⼊synchronized关键字的静态⽅法称为同步静态⽅法。
在访问同步静态⽅法时,会获取该类的“Class”对象,所以当⼀个线程进⼊同步的静态⽅法中时,线程监视器获取类本⾝的对象锁,其它线程不能进⼊这个类的任何静态同步⽅法。它不像实例⽅法,因为多个线程可以同时访问不同实例同步实例⽅法。这个其实就是操作系统中的⽤信号量实现进程的互斥与同步问题,如果涉及在同⼀个类中有多个静态⽅法中处理多线程共享数据的话,那就变成⽤信号量解决⽣产者-消费者问题。也就是说,静态⽅法是⼀份临界资源,对静态⽅法的访问属于进⼊临界区;对静态变量的修改是⼀份临界资源,对静态变量的修
改属于进⼊临界区。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论