234-java并发入门





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的访问操作都是原子的




 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值