【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 @   逆火狂飙  阅读(188)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
历史上的今天:
2021-08-22 【oracle】常用表操作语句
2021-08-22 【oracle sql plus】清屏命令 clear screen
2021-08-22 【oracle】取余函数mod
2021-08-22 【Swing】使用lamda表达式简化事件响应的模板代码
2017-08-22 【Canvas与图标】对数图标 120*120
2013-08-22 【Canvas与函数极值】函数f(x)=((x+3)^2+1)^0.5+((x-5)^2+4)^0.5 的值域是?
生当作人杰 死亦为鬼雄 至今思项羽 不肯过江东
点击右上角即可分享
微信分享提示