浅谈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引用于: 原博客地址html

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

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() : 释放一个许可,将其返回给信号量。
相关文章
相关标签/搜索