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 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/WXZCYQ/article/details/99683429