• 浅谈synchronized、Lock、ThreadLocal和semaphore


    浅谈synchronized、Lock、ThreadLocal和semaphore - 格式化版本

    1. 背景

    在进行多线程编程时,最让人头痛的无非是线程安全问题,对共享资源的访问控制,如果稍加不注意就可能导致莫名其名错误,主要体现有:

    • 创建单例对象时,内存中可能存在多个实例。
    • 一个线程正在读取数据,由于另一个写线程的介入,可能导致读线程读取到的数据脏乱不堪。
    • 同一对象可能同时被多个线程使用,造成结果上面的偏差

    2. synchronized 的介绍

    为了防止多线程造成需要单例化的对象存在多实例问题,synchronized作为懒汉式模式创建实例的常使用的关键字,使用如下:

    private SocketManager() {
        }
    
        private static SocketManager INSTANCE;
    
        public static SocketManager getInstance() {
            if (INSTANCE == null) {
                synchronized (SocketManager.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new SocketManager();
                    }
                }
            }
            return INSTANCE;
        }
    

    3. Lock的介绍

    Lock是java中锁操作接口,比synchronized使用上面更为灵活。其主要实现类分为ReentrantLock (重入锁)和ReentrantReadWriteLock(读写锁)。其中ReentrantLock(重入锁)构造时,由于布尔参数不同又分为公平重入锁和非公平重入锁,其中非公平的重入锁处理效率比公平重入锁高,所以在创建时,一般使用ReentrantLock(false)。 另一个ReentrantReadWriteLock专门用于对读写操作的加锁(两个读线程不会冲突,两个写线程会冲突,一个读一个写线程会冲突,但是两个读线程不会冲突),如果ReentrantLock处理能力就不够,再这个情况下使用ReentrantLock。总之,一般情况下,ReentrantLock基本就能处理问题,在读写上就可以选择使用ReentrantLock处理。

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);
        //HashMap 非线程安全
        public static HashMap<Integer, String> pairs = new HashMap<>();
    
    
        public static void setPair(int key, String value) {
            reentrantReadWriteLock.writeLock().lock();
            pairs.put(key, value);
            reentrantReadWriteLock.writeLock().unlock();
        }
    
        public static String getValue(int key) {
            reentrantReadWriteLock.readLock().lock();
            String value = pairs.get(key);
            reentrantReadWriteLock.readLock().unlock();
            return value;
        }
    

    Case 1 :
    在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待,别无他法。这会极大影响程序执行效率。因此,就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间 (解决方案:tryLock(long time, TimeUnit unit)) 或者 能够响应中断 (解决方案:lockInterruptibly())),这种情况可以通过 Lock 解决。

    Case 2 :
    我们知道,当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 。

    Case 3 :
    我们可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的。

    4. ThreadLocal的介绍

    前面讲的都是在多线程情况下,共享资源保持一致性,保证对象的唯一性和一致性。但是在某些情境中,同一对象需要在不同线程中相互独立,即每一个线程中都拥有该对象的一个副本。(PS: SimpleDateForma非线程安全)

    // 测试代码
    public class Main {
    
        public static void main(String... args) {
    
            for (int i = 0; i < 5; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        CountUtils.addCount();
                    }
                }.start();
            }
        }
    }
    
    
    // 没有使用ThreadLocal 
    public class CountUtils {
    
        private static int countNum = 0;
    
        public static void addCount() {
            synchronized (CountUtils.class) {
                countNum++;
                System.out.println(Thread.currentThread().getName() + ":" + countNum);
            }
        }
    }
    
    // 输出结果:
    Thread-1:1
    Thread-3:2
    Thread-2:3
    Thread-0:4
    Thread-4:5
    
    
    • 静态字段位于全局区,同时能够被多个线程修改。
    public class CountUtils {
    
        private static ThreadLocal<Integer> integerThreadLocal = new InheritableThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
    
        public static void addCount() {
            synchronized (CountUtils.class) {
                int countNum = integerThreadLocal.get();
                countNum ++ ;
                System.out.println(Thread.currentThread().getName() + ":" + countNum);
            }
        }
    }
    
    // 输出结果:
    
    Thread-2:1
    Thread-1:1
    Thread-3:1
    Thread-0:1
    Thread-4:1
    
    • 总结: ThreadLocal采用Map<ThreadInfo,E>方式将线程操作的对象进行区分,不同的线程取值并非同一个。

    5. semaphore的介绍

    semaphore (信号量) 控制线程的出入问题,创建该对象时指明可用的资源数(synchronized可用资源数为1),当有资源空闲时,线程可进入,否则阻塞等待。项目中弹幕处理,维护弹幕池可用弹幕总数,当显示的弹幕已经达到弹幕总数,信号量为0,当某一弹幕移除屏幕,将弹幕控件放入弹幕控件池进行复用,并将信号量加1,定时器定时判断信号量,当信号量不为0时,从弹幕控制池取弹幕控件展示。

    • tryAcquire() : 仅在调用时此信号量存在一个可用许可,才从信号量获取许可。
    • acquire() : 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
    • release() : 释放一个许可,将其返回给信号量。
  • 相关阅读:
    如何查看python的当前版本号
    Android数据库程序开发必备工具SqliteDev 狼人:
    Android四种Activity的加载模式 狼人:
    20+ 个很棒的 jQuery 文件上传插件或教程(此文值得“推荐”和“收藏”) 狼人:
    艾伟:10分钟去除天天团购系统版权 狼人:
    WIN7,server2008 IIS 伪静态 不能显示特殊图片 加号 减号 “+”,""号的图片需要转义才可以显示 狼人:
    近百余款最新HTML5应用案例给大伙儿共享了 狼人:
    shopex李钟伟:独立电子商务发展与网络购物 狼人:
    《天将客户关系管理系统》 狼人:
    Shopex V4.8.4 V4.8.5 后台拿Shell 0Days 狼人:
  • 原文地址:https://www.cnblogs.com/android-er/p/7253117.html
Copyright © 2020-2023  润新知