学习java基础关键字之synchronized和volatile

来源:互联网 发布:google chrome mac 编辑:程序博客网 时间:2024/06/05 22:36

一、synchronized,先看一个简单的示例:

public class Counter {
    public static int count = 0;
    
    public static void add() {
        //这里延迟1毫秒,使得结果明显
        count++;
        //这里每次运行的值都有可能不同,可能为1000
        System.out.println("运行结果:Counter.count=" + Counter.count);
    }
 
    public static void main(String[] args) {
        //同时启动1000个线程,去进行i++计算,看看实际结果
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.add();
                }
            }).start();
        }
    }
}

上面的main方法执行过后的结果如下:

运行结果:Counter.count=1
运行结果:Counter.count=3
运行结果:Counter.count=2
运行结果:Counter.count=4

。。。

运行结果:Counter.count=997
运行结果:Counter.count=1000
运行结果:Counter.count=999

并且每一次运行输出的结果都不一样,为什么会这样呢,相信很多人都清楚了,这里我就不说了(多线程的问题)

而我现在要做的是让这个示例输出的结果是从1-1000,那么就只需要用synchronized声明add方法就可以。

public class Counter {
    public static int count = 0;
    
    public synchronized static void add() {
        //这里延迟1毫秒,使得结果明显
        count++;
        //这里每次运行的值都有可能不同,可能为1000
        System.out.println("运行结果:Counter.count=" + Counter.count);
    }
 
    public static void main(String[] args) {
        //同时启动1000个线程,去进行i++计算,看看实际结果
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.add();
                }
            }).start();
        }
    }
}

上面的main方法执行过后的结果如下:

运行结果:Counter.count=1
运行结果:Counter.count=2
运行结果:Counter.count=3
运行结果:Counter.count=4

。。。

运行结果:Counter.count=998
运行结果:Counter.count=999
运行结果:Counter.count=1000

并且每一次输出都是这样的结果。

synchronized这个关键字我用的比较多,比如我在做公司的一个项目中就用来控制过资源的争夺问题,项目一块购,我在这个项目中只做了一个接口,购买支付接口,这里有一个问题是需要解决的:在多人同时购买同一个商品的时候,需要控制每个用户得到的云购码。

首先是云购码的生成:9位数字,如100000168,假如这一期这个商品的总价是6000元,则一共有从100000001到100006000这6000个云购码,这些云购码不是按顺序发放,而是随机发放,就是说,用户用一块钱来买这个商品,就会随机得到一个云购码。

这里我要做的是,假如N个用户同时购买这个商品,怎么样才能保证每个人随机得到的云购码都是不同的,没有重复的。

最简单的做法就是让N个买相同商品的人进行排队,像上面的示例用synchronized声明add方法就可以。

但是问题来了,性能会非常的低,因为来网站购买商品的人非常多,而且购买的商品也不尽全是相同的。如果像上面那样控制,效率非常低。

在这里,我写出了我的做法:

1、创建请求类RequestModel

public class RequestModel {
    private String sign; // 标志唯一
    private boolean isOverTime; // 标示是否超时
    private long systemTime;//系统时间
    // set/get方法
}

2、资源控制类SourceControl

public class SourceControl{

// 用ReentrantLock和Condition控制线程

    public static final ReentrantLock reentrantLock = new ReentrantLock();
    public static final Condition condition = reentrantLock.newCondition();

//资源

    public static Map<String, List<RequestModel>> map = new HashMap<String, List<RequestModel>>();

    /**
     * 把请求放进资源库
     * @param key 关键字
     * @param requestModel
      * @return 返回第一次放入的key
     */
    public synchronized static String putKey(String key, RequestModel requestModel) {
        String tmp = containsKey(key);
        if (!"0".equalsIgnoreCase(tmp)) {
            List<RequestModel> list = map.get(tmp);
            list.add(requestModel);
            return tmp;
        }else {
            // requestModel != null 加入队列
            List<RequestModel> list = new ArrayList<RequestModel>();
            list.add(requestModel);
            map.put(key, list);
            return key;
        }
    }
    
    /**
     * 请求资源
     * @param key
     * @param systemTime
     * @return 是否在队头,是返回true,否返回false
     */
    public static boolean getKey(String key, String sign) {
        // 判断是否在队头
        List<RequestModel> list = map.get(key);
        RequestModel tmp = list.get(0);
        if (tmp.getSign().equalsIgnoreCase(sign)) {
            // 如果请求超过10秒才拿到资源就设置请求超时
            int now = (int)(System.currentTimeMillis()/1000);
            if (now - tmp.getSystemTime()/1000 > 55) {
                tmp.setOverTime(true);
            }
            return true;
        }
        return false;
    }
    // 删除资源
    public static void removeRequestModel(String key, String sign) {
        List<RequestModel> list = map.get(key);
        for (RequestModel requestModel : list) {
            if (requestModel.getSign().equalsIgnoreCase(sign)) {
                list.remove(requestModel);
                break;
            }
        }
        if (list.isEmpty()) {
            map.remove(key);
        }
    }
    
    /**
     * 判断资源是否存在
     * @param key "1,2,3,4"
     * @return
     */
    public static String containsKey(String key) {
        if (null != map.keySet()) {
            String[] keys = key.split(",");
            Iterator<String> iterator = map.keySet().iterator();
            while(iterator.hasNext()){
                String tmp = iterator.next();
                String[] tmps = tmp.split(",");
                for (int i = 0; i < keys.length; i++) {
                    for (int j = 0; j < tmps.length; j++) {
                        if (keys[i].equalsIgnoreCase(tmps[j])) {
                            return tmp; // 找到就返回原来存入的key
                        }
                    }
                }
            }
        }
        return "0"; // 返回"0"表示没找到
    }
}

3、每个用户购买支付时,调用接口算一个线程

        String sign = UUID.randomUUID().toString();
        RequestModel requestModel = new RequestModel();
        requestModel.setSign(sign);
        requestModel.setOverTime(false);
        requestModel.setSystemTime(System.currentTimeMillis());

        // 加锁
        SourceControl.reentrantLock.lock();
        // 加入资源库
        String key = SourceControl.putKey(tmp, requestModel);

        boolean hasKey = true;
        while(hasKey){
            // 抢资源
            hasKey = SourceControl.getKey(key, sign);

            if (hasKey) {
                hasKey = false;
            }else {
                hasKey = true;
                SourceControl.condition.await();  // 继续等待
            }
        }
        // 是否超时
        if (requestModel.isOverTime()) {
           //请求超时
        }else {
          // 这里处理事务,即获取云购码,当然,还有其他的事要做的,比如生成订单,处理库存之类
        }
        // 释放资源,让其他线程继续抢夺资源
        SourceControl.removeRequestModel(key, sign);
        SourceControl.condition.signalAll();
        SourceControl.reentrantLock.unlock();

PS:synchronized我是配合着ReentrantLock和Condition这两个类来使用的


二、volatile,从这个单词的意思来说,易变的,不稳定的;(液体或油)易挥发的;爆炸性的;快活的,轻快的;

这个关键字我没有用过,在网上找了些资料,说是用volatile修饰的变量,我试了一下,比如下面的示例

public class Counter {
    public volatile static int count = 0;
    
    public  static void add() {
        //这里延迟1毫秒,使得结果明显
        count++;
        //这里每次运行的值都有可能不同,可能为1000
        System.out.println("运行结果:Counter.count=" + Counter.count);
    }
 
    public static void main(String[] args) {
        //同时启动1000个线程,去进行i++计算,看看实际结果
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.add();
                }
            }).start();
        }
    }
}

输出的结果令人很不满意

运行结果:Counter.count=1
运行结果:Counter.count=4
运行结果:Counter.count=3

。。。

运行结果:Counter.count=892
运行结果:Counter.count=882
运行结果:Counter.count=881

我想说的是,用volatile 这个关键字,在多线程操作资源的时候,并没有起到作用,所以推荐使用synchronized配合着ReentrantLock和Condition来操作多线程处理资源问题。


0 0