标签:之间 out 内存 技术 info 实现 lse set 假设
ThreadLocal让线程有自己的局部变量,其中重要的方法有:set(),get(),remove()
ThreadLocal的使用
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal();
Thread thread1 = new Thread( () -> {
local.set("我是线程1的消息");
System.out.println("线程1的输出:" + local.get());
});
Thread thread2 = new Thread( () -> {
System.out.println("线程2的输出:" + local.get());
}) ;
thread1.start();
thread2.start();
}
线程1的输出:我是线程1的消息
线程2的输出:null
线程1存的内容,只有线程1能使用,其他线程拿不到
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取ThreadLocalMap,下面会说明
if (map != null) //map非空就设值
map.set(this, value); //键为threadloacl
else //空了就创建,map的键为当前线程,值为存入的值
createMap(t, value);
}
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用,想要避免内存泄露就要手动remove(),key是弱引用,值不是
避免死锁:
public class Synchronizedtest implements Runnable {
int i = 0;
@Override
public void run() {
i++;
System.out.println("i :" + i);
}
public static void main(String[] args) throws InterruptedException {
Synchronizedtest st = new Synchronizedtest();
for (int i = 1; i <= 1000; i++) {
new Thread(st).start();
}
}
}
多试几次会发现最后结果不是1000,为什么会这样呢?上面操作是线程不安全的,因为 i++ 不是原子操作,要分为三步:
但如果用synchronized方法未免太浪费了,一个加法就用上了锁
public synchronized void run() {
i++;
System.out.println("i :" + i);
}
那么还有一种方法:java.util.concurrent.atomic,原子类,通过自旋CAS操作volatile变量实现
下面以AtomicInteger为例
public class Synchronizedtest implements Runnable {
AtomicInteger i = new AtomicInteger(0);
@Override
public void run() {
i.addAndGet(1);
System.out.println("i :" + i);
}
public static void main(String[] args) throws InterruptedException {
Synchronizedtest st = new Synchronizedtest();
for (int i = 1; i <= 1000; i++) {
new Thread(st).start();
}
}
}
创建原子类后,要使用该类特有方法来实现加减乘除,而不是直接 i++;
之后的结果一直为1000,实现了原子性
下面列出几个常见方法:
set(int newValue) 设置新值
compareAndSet(int expect, int update): CAS操作
ABA问题
下面也举了个例子
public class ABADemo {
private static AtomicReference atomicReference = new AtomicReference(0);
public static void main(String[] args) {
new Thread( () -> {
atomicReference.compareAndSet(0, 10);
atomicReference.compareAndSet(10, 0);
},"t1").start();
new Thread( () -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet(100, 2019);
},"t2").start();
}
}
可以发现t2可以修改t1改变之后的值
解决方法:增加一个版本号
这件事如果发生在链表中就是不安全的,所以JDK提供了类来解决
AtomicStampedReference
AtomicMarkableReference
public class ABADemo {
private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
public static void main(String[] args) {
new Thread(() -> {
//t1的初始版本号
System.out.println(atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(0, 10,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
atomicStampedReference.compareAndSet(10, 0,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
},"t1").start();
new Thread(() -> {
//t2初始版本号
System.out.println(atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//最新版本号
System.out.println(atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(0, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
},"t2").start();
}
}
可以看到t2因为版本号对不上而不能操作成功
标签:之间 out 内存 技术 info 实现 lse set 假设
原文地址:https://www.cnblogs.com/Howlet/p/12234203.html