提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
ThreadLocal
Threadlocal使用场景一、故事从两个线程开始二、线程池来帮忙,却好心帮坏事1. 10个线程打印日期2、1000个线程打印日期3.使用Synchronized解决线程安全问题4.使用ThreadLocal解决线程安全问题三、 ThreadLocal第二种用法四.总结两个作用两个场景使用ThreadLocal的好处Threadlocal使用场景
一、故事从两个线程开始
ThreadLocal与synchronized同步机制的比较
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
ThreadLocal是线程局部变量,是一种多线程间并发访问变量的解决方案。和synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而使用以空间换时间的方式,为每个线程提供变量的独立副本,以保证线程的安全。
代码如下(示例):
package threadlocal;import java.text.SimpleDateFormat;import java.util.Date;/*** 描述:两个线程打印日期*/public class ThreadLocalNormalUsage00 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage00().date(10);System.out.println(date);}}).start();new Thread(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage00().date(1047);System.out.println(date);}}).start();}public String date(int seconds) {//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时Date date = new Date(1000 * seconds);SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return dateFormat.format(date);}}
二、线程池来帮忙,却好心帮坏事
1. 10个线程打印日期
代码如下(示例):
10个线程打印日期
package threadlocal;import java.text.SimpleDateFormat;import java.util.Date;/*** 描述:10个线程打印日期*/public class ThreadLocalNormalUsage01 {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 30; i++) {int finalI = i;new Thread(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage01().date(finalI);System.out.println(date);}}).start();Thread.sleep(100);}}public String date(int seconds) {//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时Date date = new Date(1000 * seconds);SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return dateFormat.format(date);}}
结果:
2、1000个线程打印日期
package threadlocal;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** 描述:1000个打印日期的任务,用线程池来执行*/public class ThreadLocalNormalUsage02 {public static ExecutorService threadPool = Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000; i++) {int finalI = i;threadPool.submit(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage02().date(finalI);System.out.println(date);}});}threadPool.shutdown();}public String date(int seconds) {//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时Date date = new Date(1000 * seconds);SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return dateFormat.format(date);}}
结果:
将ThreadLocalNormalUsage02改进后,1000个线程共享DateFormat对象
package threadlocal;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** 描述:1000个打印日期的任务,用线程池来执行*/public class ThreadLocalNormalUsage03 {public static ExecutorService threadPool = Executors.newFixedThreadPool(10);static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000; i++) {int finalI = i;threadPool.submit(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage03().date(finalI);System.out.println(date);}});}threadPool.shutdown();}public String date(int seconds) {//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时Date date = new Date(1000 * seconds);return dateFormat.format(date);}}
结果:
发现发生了线程安全问题,所有线程共用一个SimpleDateFormat,有些时间是一样的
3.使用Synchronized解决线程安全问题
使用Synchronized解决线程安全问题
package threadlocal;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** 描述:加锁来解决线程安全问题*/public class ThreadLocalNormalUsage04 {public static ExecutorService threadPool = Executors.newFixedThreadPool(10);static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000; i++) {int finalI = i;threadPool.submit(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage04().date(finalI);System.out.println(date);}});}threadPool.shutdown();}public String date(int seconds) {//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时Date date = new Date(1000 * seconds);String s = null;synchronized (ThreadLocalNormalUsage04.class) {s = dateFormat.format(date);}return s;}}
结果:
虽然解决了并发线程安全问题,但是效率太低了。
4.使用ThreadLocal解决线程安全问题
提示:这里对文章进行总结:
由于有10个线程执行任务(打印日期),创建10个dateformat对象,这样既没有性能损耗,保证线程安全(每个dateformat独享独有),也避免了synchornized带来的一直等待排队的问题。
利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
package threadlocal;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** 描述:利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存*/public class ThreadLocalNormalUsage05 {public static ExecutorService threadPool = Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000; i++) {int finalI = i;threadPool.submit(new Runnable() {@Overridepublic void run() {String date = new ThreadLocalNormalUsage05().date(finalI);System.out.println(date);}});}threadPool.shutdown();}public String date(int seconds) {//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时Date date = new Date(1000 * seconds);// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();return dateFormat.format(date);}}class ThreadSafeFormatter {public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}};public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));}
运行结果:
三、 ThreadLocal第二种用法
这里每个线程内保存全局变量,可以让不同方法直接使用,避免参数传递麻烦
## 更好的方法是用Threadlocal,这样无需synchronized,可以在不影响性能的情况下,无需层层传递参数,就可达到保存当前线程对应的用户信息的目的。
package threadlocal;/*** 描述:演示ThreadLocal用法2:避免传递参数的麻烦*/public class ThreadLocalNormalUsage06 {public static void main(String[] args) {new Service1().process("");}}class Service1 {public void process(String name) {User user = new User("超哥");UserContextHolder.holder.set(user);new Service2().process();}}class Service2 {public void process() {User user = UserContextHolder.holder.get();ThreadSafeFormatter.dateFormatThreadLocal.get();System.out.println("Service2拿到用户名:" + user.name);new Service3().process();}}class Service3 {public void process() {User user = UserContextHolder.holder.get();System.out.println("Service3拿到用户名:" + user.name);UserContextHolder.holder.remove();}}class UserContextHolder {public static ThreadLocal<User> holder = new ThreadLocal<>();}class User {String name;public User(String name) {this.name = name;}}
运行结果: