准备知识点:
1.竞态:指多线程情况下计算的正确性依赖于相对时间顺序或线程的交错;
2.状态变量:类的实例变量、静态变量;
3.共享变量:能够被多个线程共同访问的变量,状态变量也属于共享变量;
4.竞态产生模式:有两种
a) 读-改-写
b) 检查-行动
在下面的程序中直接演示了这两种情况
5.局部变量的使用不会产生竞态;
6.原子性:意思是不可分割,就好比去饮料机买饮料,我只负责选择然后取货,选择->取货这期间的处理是我们看不到的,相当于一盒黑盒,我们无法去左右它,这个黑盒我们称为原子性;
7.下面的程序中
静态变量mark 的 ++ 操作代码上看似一步其实jvm中解析出的指令分为了三步,也就是竞态模式的 读-改-写
1) load--将mark的值从内存中读取到寄存器r1里
2) increment--寄存器值+1
3) store--将寄存器里的内容写入mark的内存空间
getId()方法中的if-else条件则是竞态模式中的检查-行动
package test
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 模拟多线程中竞态的产生
*
*/
public class RaceCondition {
public static void main(String[] args) {
Client[] cl = new Client[100];
//先准备好待会一起发送
for (int i = 0; i < 100; i++) {
Client client = new Client(String.valueOf(i));
cl[i] = client;
}
//发送
for (int i = 0; i < cl.length; i++) {
cl[i].start();;
}
}
}
/**
* 模拟web中给每个请求发放一个唯一的id标记
* 前缀4000+年月日时分秒毫秒+后缀000-999
*/
class RequestUtil{
//这个属于状态变量(类的实例变量,静态变量都属于状态变量)
private static final RequestUtil REQUEST_UTIL = new RequestUtil();
private static final int MAX = 999;
//这个属于状态变量(类的实例变量,静态变量都属于状态变量),能够被多个线程共同访问的变量也叫共享变量
private static int mark = -1;
private static final SimpleDateFormat FORMAT= new SimpleDateFormat("yyyyMMddHHmmsssss");
//私有化构造方法
private RequestUtil(){}
public static RequestUtil getInstance(){
return REQUEST_UTIL;
}
public static String getReqId(){
if(mark > MAX){
mark = 0;
}else{
mark++;
}
return "4000" + FORMAT.format(new Date()) + new DecimalFormat("000").format(mark);
}
}
/**
* 模拟客户端请求
*
*/
class Client extends Thread{
private String name;
public Client(String name) {
this.name = name;
}
@SuppressWarnings("static-access")
@Override
public void run() {
String reqId = RequestUtil.getInstance().getReqId();
System.out.println("线程"+name+":获取请求id["+reqId+"]");
}
}
因为出现了竞态产生的条件,结果可以看出不同线程可能获取到了相同的id。
解决这个竞态比较简单的方法就是让getId()方法变成原子性,可使用关键字 synchronized ,public static synchronized String getReqId()
mark