目录
2.ThreadLocalRandom解决Random的缺陷
Random是我们使用随机数的常用的一个类,他的原理四是根据一个老的种子生成新的种子,再根据新的种子去生成随机数。
在多线程的环境下,根据老的种子获得新的种子可能是同一个,这样产生的随机数不就是一样的吗?这个可不允许,所以在Random中使用一个原子类达到多线程下也是随机数的效果。用CAS把老的种子替换为新的种子,这样做以后,在竞争比较激烈的环境下,会造成大量的线程自旋,消耗CPU,因为每次CAS操作只能有一个线程能成功。
Random的缺陷的本质就是多个线程使用同一个种子变量,而ThreadLocalRandom每个线程都有自己单独的种子变量,实现方法和ThreadLocal类似。
使用ThreadLocalRandom很简单,ThreadLocalRandom.current()就拿到实例。
在current方法中会有一个实例化方法localInit()
localInit里面会为每个线程设置一个随机数种子,把变量名为threadLocalRandomSeed,存于自己的线程栈中。
多线程环境下,ThreadLocalRandom的实例只会产生一个,里面只包含与线程无关的通用算法,种子存于具体线程,所以他是线程安全的。
所以ThreadLocalRandom不能多个线程共享一个实例,不然产生的随机数会相同。
sonar扫描到使用Random随机函数不安全, 推荐使用SecureRandom替换之, 当使用SecureRandom.getInstanceStrong()获取SecureRandom并调用next方式时, 在生产环境(linux)产生较长时间的阻塞。
使用SecureRandom.getInstanceStrong()是通过读取/dev/random来获得随机数,但是会导致阻塞,如果使用new 的方式来获取实例的话,是linux下从/dev/urandom读取,不会阻塞。
读取/dev/random会阻塞的原因,是因为/dev/random中的数据来自系统的扰动, 比如键盘输入, 鼠标点击, 等等, 当系统扰动很小时, 产生的随机数不够, 导致读取/dev/random的进程会阻塞等待.
我们写一个小测试来验证
- public class TestMain {
-
-
- private static Random random;
-
- static {
- try {
- random = SecureRandom.getInstanceStrong();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-
- public static void main(String[] args) throws NoSuchAlgorithmException {
- System.out.println("start.....");
- long start = System.currentTimeMillis();
- SecureRandom random = SecureRandom.getInstanceStrong();
- for(int i = 0; i < 100; i++) {
- System.out.println("第" + i + "个随机数.");
- random.nextInt(10000);
- }
- System.out.println("finish...time/ms:" + (System.currentTimeMillis() - start));
-
- }
- }
放到linux上运行就卡住了
启动阿尔沙斯 看到主线程是一个非守护线程,并且是在RUNNABLE的状态
并且我们发现这个main线程一直都是运行状态,所以我们查看线程的详细信息
然后就找到我们代码阻塞的地方: