java并发入门
我们先说一下java并发的安全性问题
举个例子
我们先来看一下
一段代码
SingleSequence singleSequence = new SingleSequence();
String listString = singleSequence.getListString();
System.out.println(listString);
class SingleSequence {
private int count = 0;
private ArrayList<Integer> list = new ArrayList<>();
public String getListString() {
for (int i = 0; i < 1000; i++) {
count++;
list.add(count);
}
return ListUtils.getString(list);
}
}
我们来看这段简单的代码
意思就是单线程生成一个数列
打印结果就是1,2,3一直到1000
那么我们试一下把单线程数列改成多线程的数列
但是我们修改的数列存在问题,不安全
所以我们暂时叫做
UnsafeSequence
class UnsafeSequence {
private int count = 0;
private ArrayList<Integer> list = new ArrayList<>();
public void HandleList() {
for (int i = 0; i < 50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
count++;
list.add(count);
}
}
}).start();
}
}
public String getListString() {
String string = ListUtils.getString(list);
return string;
}
}
UnsafeSequence unsafeSequence = new UnsafeSequence();
unsafeSequence.HandleList();
String listString = unsafeSequence.getListString();
System.out.println(listString);
我们看一下
我们开启了50个线程对list进行操作
那么这样会产生什么问题
我们来看一下打印结果
我们发现list中的数据有偏差
比如
2525,2526,2528,2529,2530,2532,2533,2534
我们发现2527不见了,2531也不见了
另外
我们发现这个数列没有打印完
这个问题是因为主线程的打印
执行在了子线程之前
我们关键说一下多线程的安全问题
我们开启多线程对list进行操作
这时候可能会出现
比如
此时count为100
那么可能同时会有A,B,C三个线程
同时访问了count
那么就可能会在list中加入三个101
另外
比如此时count为100
A执行了count++,此时A还没执行list.add
但是B也执行了count++
那么A执行list.add的时候
加入的就是102
这样101这个数就没被加入到list中
那么我们修改一下代码
来一个安全的序列 SafeSequence
class SafeSequence {
private int count = 0;
private ArrayList<Integer> list = new ArrayList<>();
public void HandleList() {
for (int i = 0; i < 50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
handle();
}
}
}).start();
}
}
private synchronized void handle() {
count++;
list.add(count);
}
public String getListString() {
String string = ListUtils.getString(list);
return string;
}
}
再举一个例子
我们来一个Servlet来统计一下地址的访问数量
public class TestServlet extends BaseServlet {
private long count;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.doGet(request, response);
count++;
System.out.println("已被访问" + count + "次");
}
}
我们看一下这个TestServlet
每次访问这个Servlet并执行doGet方法的时候
count累加
然后打印出次数
乍一看这段代码好像没什么问题
但是这个Servlet并不是线程安全的
我们可以看到
count++这个操作
其实是一个
读取---修改---写入 的操作
它的结果是依赖于之前的状态的
虽然有时候我们统计访问次数不用那么精确
但是如果换成是其他的功能或操作
那么这段代码就是不安全的
我们如何来优化这段代码
我们可以使用一个线程安全类Atomic
public class TestServlet extends BaseServlet {
private final AtomicLong count = new AtomicLong(0);
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.doGet(request, response);
count.incrementAndGet();
System.out.println("已被访问" + count + "次");
}
}
我们使用了一个AtomicLong
在java.util.concurrent.atomic包中
包含了一些原子变量类
通过用AtomicLong来代替long类型
可以确保所有对count的访问操作都是原子的