AtomicInteger虽然是原子性的Integer类型,在赋值,自增上,不能被其他线程打断,从而保证了数据的安全性,但是,在线程作用范围较小的情况下,虽然是不能被打断的,但是在自增之前,线程已经抢先运行了还未自增前的值,这样又导致了数据错乱。
下面用代码来解释下
public class Test {
private static final ThreadPoolExecutor EXECUTOR;
static {
EXECUTOR = new ThreadPoolExecutor(4, 5, 2000, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
}
public static void main(String[] args) {
File file = new File("D:\\javacccc\\eee\\");
List<String> list = new CopyOnWriteArrayList<String>();
getFile(file, list);
// System.out.println(list);
AtomicInteger ai = new AtomicInteger(0);
for (int i = 0; i < list.size(); i++) {
System.out.println(EXECUTOR);
EXECUTOR.execute(new Runnable() {
@Override
public void run() {
BufferedReader bfr = null;
BufferedWriter bfw = null;
try {
// synchronized (list) {
System.out.println("list " + list.get(ai.get()));
System.out.println(ai.get());
bfr = new BufferedReader(new FileReader(new File(list.get(ai.get()))));
bfw = new BufferedWriter(new FileWriter(new File("d:\\aaa\\" + ai.get() + ".java")));
System.out.println(list.get(ai.get()));
String s = null;
while ((s = bfr.readLine()) != null) {
System.out.println("ok");
bfw.write(s);
}
System.out.println(Thread.currentThread().getName() + "下载成功");
ai.incrementAndGet();
// }
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
EXECUTOR.shutdown();
}
下面是getFile方法
public static void getFile(File src, List<String> list) {
File[] files = src.listFiles();
for (File filea : files) {
if (filea.isFile()) {
list.add(src.getAbsolutePath() + File.separator + filea.getName());
} else {
continue;
}
}
}
在上面的代码块那里,我需要同时下载三个文件,所以我创建了线程池,并在for循环里提交。
list D:\javacccc\eee\asdasd.java
ai: 0
list D:\javacccc\eee\asdasd.java
ai: 0
list D:\javacccc\eee\asdasd.java
ai: 0
可是在控制台输出的ai的值却依旧是0,三个线程操作了同一个文件,也就是说下载了三次。
这是为什么呢,通过debug发现,ai是有在自增的,由于原子性的存在,但是,因为线程范围较小,线程运行的速度比较快,在ai自增之前,另外两个线程已经先拿到ai = 0的值然后运行完了,我们都知道 线程和主进程是可以同时运行的,所以能在ai自增之前完成了ai值获取的操作,导致了数据出现异常,那怎么解决呢。
@Override
public void run() {
BufferedReader bfr = null;
BufferedWriter bfw = null;
try {
int a = ai.get();
ai.incrementAndGet()
System.out.println("list " + list.get(ai.get()));
System.out.println("ai: "+ai.get());
我们可以在run方法的开头,定义一个变量,将ai的值赋给变量,然后再进行自增操作,这样,线程进来一开始是读取到a的值而不是ai的值,而且变量是线程私有的,不会立即被其他线程获取,如果一开始就让ai进行自增操作的话,会出现越界情况,所以如果让线程拿到的是a的数据,a会先从0开始,然后在根据ai的自增进行变化。
list D:\javacccc\eee\asdasd.java
ai: 0
list D:\javacccc\eee\asdasdqw.java
ai: 1
list D:\javacccc\eee\askdjasda.java
ai: 2
D:\javacccc\eee\asdasdqw.java
D:\javacccc\eee\asdasd.java
版权声明:本文为WXZCYQ原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。