ThreadLocal使用及原理

1.ThreadLocal的简单使用

从ThreadLocal的名字上可以看到,这是个线程的局部变量。也就是说只有当前线程可以访问,既然是只有当前线程池可以访问的数据自然是线程安全的。

package thread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadLocalDemo {

	private static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>();

//如果不想要下面的t1.set则可以通过这种方式构造ThreadLocal.
//	private static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>() {
//		public SimpleDateFormat initialValue() {
//			return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//		}
//	};
	private static final SimpleDateFormat sm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private static ReentrantLock lock = new ReentrantLock();

	public static class ParseDate implements Runnable {

		int i = 0;

		public ParseDate(int i) {
			this.i = i;
		}

		// @Override
		// public void run() {
		// try {
		// lock.lock();
		// Date date = sm.parse("2018-8-12 17:30:" + i + "");
		// System.out.println(i + "--" + date);
		// lock.unlock();
		// Thread.sleep(1000);
		// } catch (ParseException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// }

		// @Override
		// public void run() {
		// try {
		// synchronized (ParseDate.class) {
		// Date date = sm.parse("2018-8-12 17:30:"+i+"");
		// System.out.println(i+"--"+date);
		// }
		// Thread.sleep(1000);
		// } catch (ParseException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// }

		@Override
		public void run() {
			if (t1.get() == null)
				t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

			try {
				Date date = t1.get().parse("2018-8-12 17:30:" + i + "");
				System.out.println(i + "--" + date);
				Thread.sleep(1000);
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	public static void main(String[] args) {
		ExecutorService es = Executors.newFixedThreadPool(10);

		for (int i = 0; i < 30; i++)
			es.execute(new ParseDate(i));
	}

}

可以看到输出结果也是正确地。这里我有三种方式保证线程的安全性,锁和重入锁还有就是ThreadLocal。在线程ThreadLocal中if (t1.get() == null)
                t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

这两行代码的含义是,如果当前线程不持有SimpleDateFormat对象的实例则新建一个并将其设置到当前线程中。如果有则直接使用。从这里也可以看到为每个线程人手分配一个对象的工作并不是由ThreadLocal来完成的而是在应用层保证的。如果在应用上为每一个线程分配相同的对象实例,那么ThreadLocal也不能保证线程安全。(注意:为每个线程分配不同的对象,需要在应用层保证。ThreadLocal只是起到简单容器的作用。)

2.ThreadLocal的实现原理

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先它会获取当前线程对象,然后通过getMap()拿到线程ThreadLocalMap,然后将值设入ThreadLocalMap中。(ThreadLocalMap可以简单的理解为一个Map,它是定义在Thread内部的成员。)

设置到ThreadLocal中的数据,也是写入了threadLocals这个Map中。其中key为ThreadLocal当前对象,value则是我们需要的值这里是:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")。而threadLocals本身就保存了当前自己所在线程的所有局部变量,也就是一个ThreadLocal变量的集合。

get()操作:

 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();
    }

它也是先获取当前线程的ThreadLocalMap对象,然后通过将自己作为key取得内部的实际数据。

因为这些变量是维护在Thread类内部的,所以这意味着只要线程不退出,对象的引用将一直存在。而当线程退出时,Thread类会进行清理工作:

因此如果使用线程池,这就意味着当前线程未必会退出(比如固定大小的线程池。)如果这样将一些太大的对象设置到ThreadLocal中(实际会保存在线程持有的threadLocals Map中)。可能会使系统出现内存泄露的可能。

此时如果希望及时的回收对象,最好使用ThreadLocal.remove()方法将这个变量移除。或者可以直接通过将ThreadLocal对象设为null的方式,让系统自行垃圾回收。

ThreadLocal的使用场景一般是共享对象对于竞争的处理容易引起性能的损失,这时候可以考虑使用ThreadLocal为每个线程分配单独的对象。

ThreadLocalJava中的一个线程级别的变量,它提供了一种简单的方式来在多线程环境中维护变量的值。每个线程都拥有自己独立的ThreadLocal实例,并且可以通过该实例来获取和设置其对应的变量的值。 ThreadLocal原理是通过在每个线程中创建一个独立的副本来存储变量的值。这样,每个线程都可以独立地访问和修改自己的副本,而不会对其他线程产生影响。 ThreadLocal使用非常简单。首先,我们需要创建一个ThreadLocal对象,并指定要存储的变量类型。然后,我们可以通过调用ThreadLocal的get方法来获取当前线程中与该ThreadLocal对象关联的变量值,如果当前线程还没有设置过该变量,get方法会返回null。类似地,我们可以通过调用ThreadLocal的set方法来设置当前线程中与该ThreadLocal对象关联的变量值。 下面是一个简单的示例代码: ``` public class ThreadLocalExample { private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>(); public static void main(String[] args) { THREAD_LOCAL.set("Hello, world!"); Thread thread1 = new Thread(() -> { THREAD_LOCAL.set("Hello from thread 1!"); System.out.println(THREAD_LOCAL.get()); }); Thread thread2 = new Thread(() -> { THREAD_LOCAL.set("Hello from thread 2!"); System.out.println(THREAD_LOCAL.get()); }); thread1.start(); thread2.start(); System.out.println(THREAD_LOCAL.get()); } } ``` 在上面的示例中,我们创建了一个ThreadLocal对象`THREAD_LOCAL`用于存储String类型的变量。首先我们通过调用`THREAD_LOCAL.set("Hello, world!")`方法在主线程中设置了变量的值。然后,我们创建了两个新的线程并分别在其中设置了不同的值,并打印出来。最后,在主线程中我们也打印了变量的值。 运行上面的代码,你会看到输出结果类似于: ``` Hello from thread 1! Hello from thread 2! Hello, world! ``` 可以看到,每个线程都可以独立地访问和修改自己的变量副本,不会对其他线程产生影响。这样就确保了在多线程环境中,每个线程都可以维护自己的变量状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值