【Java线程】i++的线程安全性实验

【实验目的】

验证i++方式生成序列号在多线程环境下的不确定性。

【实验类】

SnGenerator类,用于i++方式生成序列号

package com.hy.lab.nosynchonized;

public class SnGenerator {
    int i=0;

    // 此函数如不加synchronized则线程不安全,生成的序列号可能重复
    public int getSn(){
        return i++;
    }
}

 

TestTH类,继承自Thread类,用于连续取得100个序列号

package com.hy.lab.nosynchonized;

import java.util.List;
import java.util.concurrent.CountDownLatch;

public class TestTh extends Thread{
    List list;
    SnGenerator snGenerator;
    CountDownLatch cdl;

    public TestTh(List list, SnGenerator snGenerator, CountDownLatch cdl){
        this.list=list;
        this.snGenerator=snGenerator;
        this.cdl=cdl;
    }

    public void run(){
        for(int i=0;i<100;i++){
            list.add(snGenerator.getSn());
        }

        cdl.countDown();
    }
}

 

Test类,用于将诸类启动并输出结果:

package com.hy.lab.nosynchonized;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class Test {
    public static void main(String[] args) throws Exception{
        // 容纳序列号的列表
        List<Integer> list=new ArrayList<>();
        list= Collections.synchronizedList(list);

        // 线程不安全的序列号生成器
        SnGenerator snGenerator=new SnGenerator();

        CountDownLatch cdl=new CountDownLatch(2);

        // 启动两个线程各取100个序列号
        TestTh th1=new TestTh(list,snGenerator,cdl);
        th1.start();
        TestTh th2=new TestTh(list,snGenerator,cdl);
        th2.start();

        cdl.await();

        int listSize=list.size();

        // 存放序列号的哈希Set
        HashSet<Integer> set=new HashSet<>();
        for(Integer i:list){
            set.add(i);
        }

        int setSize=set.size();

        // 有一定几率列表长度和哈希set长度不等,
        // 线程不安全的情况下哈希set长度总是小于等于列表长度,这说明列表中存在重复的序列号
        String msg=String.format("listSize=%d ,setSize=%d",listSize,setSize);
        System.out.println(msg);
    }
}

 

【预期值】

运行Test类十次,每次列表长度和哈希Set长度都是200.

【实际值】

运行Test类十次,两次列表长度大于哈希Set长度.

listSize=200 ,setSize=200
listSize=200 ,setSize=200
listSize=200 ,setSize=199
listSize=200 ,setSize=200
listSize=200 ,setSize=200
listSize=200 ,setSize=200
listSize=200 ,setSize=200
listSize=200 ,setSize=199
listSize=200 ,setSize=200
listSize=200 ,setSize=200

【结论】

i++实际分为取值,累加,设值三步,后入线程可能会修改先入线程得到的i值。

【改善方法】

将SnGnerator类的getSn函数加上synchronized修饰

public class SnGenerator {
    int i=0;

    public synchronized int getSn(){
        return i++;
    }
}

END

posted @ 2022-08-22 16:10  逆火狂飙  阅读(201)  评论(0)    收藏  举报
生当作人杰 死亦为鬼雄 至今思项羽 不肯过江东