- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章详谈ThreadLocal-单例模式下高并发线程安全由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在多例的情况下,每个对象在堆中声明内存空间,多线程对应的Java栈中的句柄或指针指向堆中不同的对象,对象各自变量的变更只会印象到对应的栈,也就是对应的线程中,不会影响到其它线程。所以多例的情况下不需要考虑线程安全的问题,因为一定是安全的.
而在单例的情况下却完全不一样了,在堆中只有一个对象,多线程对应的Java栈中的句柄或指针指向同一个对象,方法的参数变量和方法内变量是线程安全的,因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。但是成员变量只有一份,所有指向堆中该对象的句柄或指针都可以随时修改和读取它,所以是非线程安全的.
主程序:
public static void main(String[] args) { //Mirror是个单例的,只构建了一个对象 Mirror mirror = new Mirror("魔镜"); //三个线程都在用这面镜子 MirrorThread thread1 = new MirrorThread(mirror,"张三"); MirrorThread thread2 = new MirrorThread(mirror,"李四"); MirrorThread thread3 = new MirrorThread(mirror,"王二"); thread1.start(); thread2.start(); thread3.start(); }
很好理解,创建了一面镜子,3个人一起照镜子.
MirrorThread:
public class MirrorThread extends Thread{ private Mirror mirror; private String threadName; public MirrorThread(Mirror mirror, String threadName){ this.mirror = mirror; this.threadName = threadName; } //照镜子 public String lookMirror() { return threadName+" looks like "+ mirror.getNowLookLike().get(); } //化妆 public void makeup(String makeupString) { mirror.getNowLookLike().set(makeupString); } @Override public void run() { int i = 1;//阈值 while(i<5) { try { long nowFace = (long)(Math.random()*5000); sleep(nowFace); StringBuffer sb = new StringBuffer(); sb.append("第"+i+"轮从"); sb.append(lookMirror()); makeup(String.valueOf(nowFace)); sb.append("变为"); sb.append(lookMirror()); System.out.println(sb); } catch (InterruptedException e) { e.printStackTrace(); } i++; } }}
也很好理解,就是不断的更新自己的外貌同时从镜子里读取自己的外貌.
重点是Mirror:
public class Mirror { private String mirrorName; //每个人要看到自己的样子,所以这里要用ThreadLocal private ThreadLocal<String> nowLookLike; public Mirror(String mirrorName){ this.mirrorName=mirrorName; nowLookLike = new ThreadLocal<String>(); } public String getMirrorName() { return mirrorName; } public ThreadLocal<String> getNowLookLike() { return nowLookLike; }}
对每个人长的样子用ThreadLocal类型来表示.
先看测试结果:
第1轮从张三 looks like null变为张三 looks like 3008 。
第2轮从张三 looks like 3008变为张三 looks like 490 。
第1轮从王二 looks like null变为王二 looks like 3982 。
第1轮从李四 looks like null变为李四 looks like 4390 。
第2轮从王二 looks like 3982变为王二 looks like 1415 。
第2轮从李四 looks like 4390变为李四 looks like 1255 。
第3轮从王二 looks like 1415变为王二 looks like 758 。
第3轮从张三 looks like 490变为张三 looks like 2746 。
第3轮从李四 looks like 1255变为李四 looks like 845 。
第4轮从李四 looks like 845变为李四 looks like 1123 。
第4轮从张三 looks like 2746变为张三 looks like 2126 。
第4轮从王二 looks like 758变为王二 looks like 4516 。
OK,一面镜子所有人一起照,而且每个人都只能看的到自己的变化,这就达成了单例线程安全的目的.
我们来细看下它是怎么实现的.
先来看Thread:
Thread中维护了一个ThreadLocal.ThreadLocalMapthreadLocals = null; ThreadLocalMap这个Map的key是ThreadLocal,value是维护的成员变量。现在的跟踪链是Thread->ThreadLocalMap-><ThreadLocal,Object>,那么我们只要搞明白Thread怎么跟ThreadLocal关联的,从线程里找到自己关心的成员变量的快照这条线就通了.
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
再来看ThreadLocal:它里面核心方法两个get()和set(T) 。
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
方法里通过Thread.currentThread()的方法得到当前线程,然后做为key存储到当前线程对象的threadLocals中,也就是TreadLocalMap中.
OK,这样整个关系链已经建立,真正要去访问的成员变量在一个map中,key是线程号,值是属于该线程的快照.
ThreadLocal里还有map的创建createMap(t, value)、取值时对象的初始值setInitialValue()、线程结束时对象的释放remove()等细节,有兴趣的可以继续跟进了解下.
ThreadLocal应用其实很多,例如Spring容器中实例默认是单例的,transactionManager也一样,那么事务在处理时单例的manager是如何控制每个线程的事务要如何处理呢,这里面就应用了大量的ThreadLocal.
。
多线程的并发问题主要存在于多个线程对于同一个变量进行修改产生的数据不一致的问题,同一个变量指的值同一个对象的成员变量或者是同一个类的静态变量。之前我们常听过尽量不要使用静态变量,会引起并发问题,那么随着Spring框架的深入人心,单例中的成员变量也出现了多线程并发问题。Struts2接受参数采用成员变量自动封装,为此在Spring的配置采用多例模式,而SpringMVC将Spring的容器化发挥到极致,将接受的参数放到了注解和方法的参数中,从而避免了单例出现的线程问题。今天,我们讨论的是JDK从1.2就出现的一个并发工具类ThreadLocal,他除了加锁这种同步方式之外的另一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。我们先看一下官方是怎么解释这个变量的?
大致意思是:此类提供了局部变量表。这些变量与普通变量不同不同之处是,每一个通过get或者set方法访问一个线程都是他自己的,将变量的副本独立初始化。ThreadLocal实例通常作用于希望将状态与线程关联的类中的私有静态字段(例如,用户ID或交易ID).
只要线程是活动的并且可以访问{@code ThreadLocal}实例, 每个线程都会对其线程局部变量的副本保留隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。也就是说,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。而每个线程的副本全部放到ThreadLocalMap中.
public class ThreadLocalExample { public static class MyRunnable implements Runnable { private ThreadLocal<Double> threadLocal = new ThreadLocal(); private Double variable; @Override public void run() { threadLocal.set(Math.floor(Math.random() * 100D)); variable = Math.floor(Math.random() * 100D); try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println("ThreadValue==>"+threadLocal.get()); System.out.println("Variable==>"+variable); } } public static void main(String[] args) { MyRunnable sharedRunnableInstance = new MyRunnable(); Thread thread1 = new Thread(sharedRunnableInstance); Thread thread2 = new Thread(sharedRunnableInstance); thread1.start(); thread2.start(); } }
通过上面的例子,我们发现将Double放入ThreadLocal中,不会出现多线程并发问题,而成员变量variable却发生了多线程并发问题.
通过源码我们发现ThreadLocal主要提供了下面五个方法
/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.* * 返回此线程局部变量的当前线程副本中的值。* 如果该变量没有当前线程的值,则首先将其初始化为调用{@link #initialValue}方法返回的值。* @return the current thread's value of this thread-local*/public T get() { }/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** 将此线程局部变量的当前线程副本设置为指定值。* 大多数子类将不需要重写此方法,而仅依靠{@link #initialValue}方法来设置线程局部变量的值。** @param value the value to be stored in the current thread's copy of* this thread-local.* 要存储在此本地线程的当前线程副本中的值。*/public void set(T value) { }/*** Removes the current thread's value for this thread-local* variable. If this thread-local variable is subsequently* {@linkplain #get read} by the current thread, its value will be* reinitialized by invoking its {@link #initialValue} method,* unless its value is {@linkplain #set set} by the current thread* in the interim. This may result in multiple invocations of the* {@code initialValue} method in the current thread.* 删除此线程局部变量的当前线程值。* 如果此线程局部变量随后被当前线程{@linkplain #get read}调用,* 则其值将通过调用其{@link #initialValue}方法来重新初始化,* 除非当前值是在此期间被设置{@linkplain #set set}。* 这可能会导致在当前线程中多次调用{@code initialValue}方法。* @since 1.5*/public void remove() { }/*** Returns the current thread's "initial value" for this* thread-local variable. This method will be invoked the first* time a thread accesses the variable with the {@link #get}* method, unless the thread previously invoked the {@link #set}* method, in which case the {@code initialValue} method will not* be invoked for the thread. Normally, this method is invoked at* most once per thread, but it may be invoked again in case of* subsequent invocations of {@link #remove} followed by {@link #get}.* 返回此线程局部变量的当前线程的“初始值”。* 除非线程先前调用了{@link #set}方法,* 否则线程第一次使用{@link #get}方法访问该变量时将调用此方法,* 在这种情况下,{@ code initialValue}方法将不会为线程被调用。* 通常,每个线程最多调用一次此方法,* 但是在随后调用{@link #remove}之后再调用{@link #get}的情况下,可以再次调用此方法。** <p>This implementation simply returns {@code null}; if the* programmer desires thread-local variables to have an initial* value other than {@code null}, {@code ThreadLocal} must be* subclassed, and this method overridden. Typically, an* anonymous inner class will be used.* 此实现仅返回{@code null};如果程序员希望线程局部变量的初始值不是{@code null},* 则必须将{@code ThreadLocal}子类化,并重写此方法。通常,将使用匿名内部类。** @return the initial value for this thread-local*/protected T initialValue(){ }
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //通过当前线程获取ThreadLocalMap //Thread类中包含一个ThreadLocalMap的成员变量 ThreadLocalMap map = getMap(t); //如果不为空,则通过ThreadLocalMap中获取对应value值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果为空,需要初始化值 return setInitialValue();}private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //如果为空,则创建 createMap(t, value); return value;}
首先是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。 如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value.
在setInitialValue方法中,首先执行了initialValue方法(我们上面提到的最后一个方法),接着通过当前线程获取ThreadLocalMap,如果不存在则创建。创建的代码很简单,只是通过ThreadLocal对象和设置的Value值创建ThreadLocalMap对象.
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}
这个方法和setInitialValue方法的业务逻辑基本相同,只不过setInitialValue调用了initialValue()的钩子方法。这里代码简单,我们就不做过多解释.
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this);}
这个方法是从jdk1.5才出现的。处理逻辑也很很简单。通过当前线程获取到ThreadLocalMap对象,然后移除此ThreadLocal.
protected T initialValue() { return null;}
是不是感觉简单了,什么也没有处理,直接返回一个null,那么何必如此设计呢?当我们发现他的修饰符就会发现,他应该是一个钩子方法,主要用于提供子类实现的。追溯到源码中我们发现,Supplier的影子,这就是和jdk8的lamda表达式关联上了.
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { private final Supplier<? extends T> supplier; SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); }}
在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。之所以这里是一个map,是因为通过线程会存在多个类中定义ThreadLocal的成员变量。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals; 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找.
ThreadLocal属于一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量。下面我们来看看ThreadLocalMap这个类的一个entry:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object val Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }}public WeakReference(T referent) { super(referent); //referent:ThreadLocal的引用}//Reference构造方法 Reference(T referent) { this(referent, null);//referent:ThreadLocal的引用}Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}
在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,就会造成内存泄漏.
考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项).
总结:THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏.
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我.
原文链接:https://yejingtao.blog.csdn.net/article/details/78806902 。
最后此篇关于详谈ThreadLocal-单例模式下高并发线程安全的文章就讲到这里了,如果你想了解更多关于详谈ThreadLocal-单例模式下高并发线程安全的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我将 Bootstrap 与 css 和 java 脚本结合使用。在不影响前端代码的情况下,我真的很难在css中绘制这个背景。在许多问题中,人们将宽度和高度设置为 0%。但是由于我的导航栏,我不能使用
我正在用 c 编写一个程序来读取文件的内容。代码如下: #include void main() { char line[90]; while(scanf("%79[^\
我想使用 javascript 获取矩阵数组的所有对 Angular 线。假设输入输出如下: input = [ [1,2,3], [4,5,6], [7,8,9], ] output =
可以用pdfmake绘制lines,circles和other shapes吗?如果是,是否有documentation或样本?我想用jsPDF替换pdfmake。 最佳答案 是的,有可能。 pdfm
我有一个小svg小部件,其目的是显示角度列表(参见图片)。 现在,角度是线元素,仅具有笔触,没有填充。但是现在我想使用一种“内部填充”颜色和一种“笔触/边框”颜色。我猜想line元素不能解决这个问题,
我正在为带有三角对象的 3D 场景编写一个非常基本的光线转换器,一切都工作正常,直到我决定尝试从场景原点 (0/0/0) 以外的点转换光线。 但是,当我将光线原点更改为 (0/1/0) 时,相交测试突
这个问题已经有答案了: Why do people write "#!/usr/bin/env python" on the first line of a Python script? (22 个回
如何使用大约 50 个星号 * 并使用 for 循环绘制一条水平线?当我尝试这样做时,结果是垂直(而不是水平)列出 50 个星号。 public void drawAstline() { f
这是一个让球以对角线方式下降的 UI,但球保持静止;线程似乎无法正常工作。你能告诉我如何让球移动吗? 请下载一个球并更改目录,以便程序可以找到您的球的分配位置。没有必要下载足球场,但如果您愿意,也可以
我在我的一个项目中使用 Jmeter 和 Ant,当我们生成报告时,它会在报告中显示 URL、#Samples、失败、成功率、平均时间、最短时间、最长时间。 我也想在报告中包含 90% 的时间线。 现
我有一个不寻常的问题,希望有人能帮助我。我想用 Canvas (android) 画一条 Swing 或波浪线,但我不知道该怎么做。它将成为蝌蚪的尾部,所以理想情况下我希望它的形状更像三角形,一端更大
这个问题已经有答案了: Checking Collision of Shapes with JavaFX (1 个回答) 已关闭 8 年前。 我正在使用 JavaFx 8 库。 我的任务很简单:我想检
如何按编号的百分比拆分文件。行数? 假设我想将我的文件分成 3 个部分(60%/20%/20% 部分),我可以手动执行此操作,-_-: $ wc -l brown.txt 57339 brown.tx
我正在努力实现这样的目标: 但这就是我设法做到的。 你能帮我实现预期的结果吗? 更新: 如果我删除 bootstrap.css 依赖项,问题就会消失。我怎样才能让它与 Bootstrap 一起工作?
我目前正在构建一个网站,但遇到了 transform: scale 的问题。我有一个按钮,当用户将鼠标悬停在它上面时,会发生两件事: 背景以对 Angular 线“扫过” 按钮标签颜色改变 按钮稍微变
我需要使用直线和仿射变换绘制大量数据点的图形(缩放图形以适合 View )。 目前,我正在使用 NSBezierPath,但我认为它效率很低(因为点在绘制之前被复制到贝塞尔路径)。通过将我的数据切割成
我正在使用基于 SVM 分类的 HOG 特征检测器。我可以成功提取车牌,但提取的车牌除了车牌号外还有一些不必要的像素/线。我的图像处理流程如下: 在灰度图像上应用 HOG 检测器 裁剪检测到的区域 调
我有以下图片: 我想填充它的轮廓(即我想在这张图片中填充线条)。 我尝试了形态学闭合,但使用大小为 3x3 的矩形内核和 10 迭代并没有填满整个边界。我还尝试了一个 21x21 内核和 1 迭代,但
我必须找到一种算法,可以找到两组数组之间的交集总数,而其中一个数组已排序。 举个例子,我们有这两个数组,我们向相应的数字画直线。 这两个数组为我们提供了总共 7 个交集。 有什么样的算法可以帮助我解决
简单地说 - 我想使用透视投影从近裁剪平面绘制一条射线/线到远裁剪平面。我有我认为是使用各种 OpenGL/图形编程指南中描述的方法通过单击鼠标生成的正确标准化的世界坐标。 我遇到的问题是我的光线似乎
我是一名优秀的程序员,十分优秀!