多线程设计模式-线程特有存储模式

定义: 通过不共享变量实现线程安全,并规避锁的消耗及相关锁带来的问题

线程特有存储模式UML图

ApplicationThread: 线程持有存储模式的客户端,表示各个应用线程
TSObjectProxy: 用于访问线程特有对象的代理对象
    getThreadSpecific: 获取与其所属TSObjectProxy实例相关联的线程特有对象实例
    setThreadSpecific: 建立其所属TSObjectProxy实例与执行线程持有对象实例的关联
    removeThreadSpecific: 删除其所属TSObjectProxy实例与线程特有对象实例的关联
TSObject: 表示线程持有对象
ThreadSpecificStorage: 线程特有存储,可以理解为一个Map
    get: 获取与指定TSObjectProxy实例关联的TSObject实例
    set: 设置指定TSObjectProxy实例与指定TSObject实例的关联关系

想要在并发环境下生成随机数,此时可以考虑SecureRandom类来生成随机数,但是从源码中我们可以了解,SecureRandom#nextInt()方法是继承自线程安全类java.util.Random,而Random类随机数生成的具体实现(见如下代码:方法java.util.Random#next())使用了CAS机制,导致在并发环境下性能极其糟糕。同时,还有一个问题是如果使用synchronized方法会导致除生成随机数的线程在运行,其它线程都必须等待,性能也不甚理想。有鉴于其,可以考虑不在多个线程间共享SecureRandom实例,采用线程特有存储模式来解决。也就是说用ThreadLocal类实现生成随机数的线程每个线程有且仅有一个SecureRandom实例,从而避免大量的上下文切换。

    protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

下列代码主要是为了展示多线程编程模式-线程特有存储模式,在Java1.7中,并发领域的权威大神Doug Lea先生引入类java.util.concurrent.ThreadLocalRandom,就是适用于并发场景下生成随机数的类,可以直接拿来使用。如果对于java.util.Random, java.util.concurrent.ThreadLocalRandom及ThreadLocal<Random>三者之间性能的差异有多少,可以阅读推荐文章多线程环境下生成随机数

package com.bruce.threadSpecificStorage;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* @Author: Bruce
* @Date: 2019/6/4 12:27
* @Version 1.0
*/
public class ThreadSpecificSecureRandom {

    public static final ThreadSpecificSecureRandom INSTANCE =
            new ThreadSpecificSecureRandom();

    private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {

        @Override
        protected SecureRandom initialValue() {
            SecureRandom srnd;
            try {
                srnd = SecureRandom.getInstance("SHA1PRNG");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                srnd = new SecureRandom();
            }
            return srnd;
        }
    };

    private ThreadSpecificSecureRandom() {}

    public int nextInt(int upperBound) {
        SecureRandom secureRandom = SECURE_RANDOM.get();
        return secureRandom.nextInt(upperBound);
    }

    public void setSeed(long seed) {
        SecureRandom secureRandom = SECURE_RANDOM.get();
        secureRandom.setSeed(seed);
    }
}
package com.bruce.threadSpecificStorage;

import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.concurrent.*;

/**
* @Author: Bruce
* @Date: 2019/6/4 13:09
* @Version 1.0
*/
public class SmsVerficationCodeSender {

    private Logger LOG = LoggerFactory.getLogger(SmsVerficationCodeSender.class);

    public static void config() {
        PropertyConfigurator.configure("C:/Users/Bruce/Downloads/LearningJavaProject/practice/designpatternconcurrent/src/main/resource/log4j.properties");
    }

    private static final ExecutorService EXECUTOR =
            new ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "VerfCodeSender");
                    t.setDaemon(true);
                    return t;
                }
            }, new ThreadPoolExecutor.DiscardPolicy());

    public static void main(String[] args) {

        config();

        SmsVerficationCodeSender client = new SmsVerficationCodeSender();
        client.sendVerificationSms("1223049442");
        client.sendVerificationSms("1234442343");
        client.sendVerificationSms("1988323232");

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void sendVerificationSms(final String msisdn) {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                int verificationCode = ThreadSpecificSecureRandom.INSTANCE.nextInt(99999);
                DecimalFormat df = new DecimalFormat("00000");
                String txtVerCode = df.format(verificationCode);
                sendSms(msisdn, txtVerCode);
            }
        };
        EXECUTOR.submit(task);
    } 

    private void sendSms(String msisdn, String verificationCode) {
        LOG.info("Sending verification code " + verificationCode + " to " + msisdn);
    }
}

线程持有存储模式可以在不使用锁的情况下实现线程安全,避免锁的开销及相关问题,如上下文切换、死锁

需要注意的是,模式隐藏了系统结果,使应用难以理解。

模式应用场景:
1. 需要使用非线程安全对象,但是不希望引入锁
2. 使用线程安全对象,但希望避免其使用的锁的开销和相关问题
3. 隐式参数传递
4. 特定于线程的单例模式

参考资料

黄文海 Java多线程编程实战指南(设计模式篇)

黄文海的Github

多线程环境下生成随机数

猜你喜欢

转载自blog.csdn.net/u010145219/article/details/91346627