当前位置: 首页 > news >正文

包头市网站建设_网站建设公司_门户网站_seo优化

能不能自己做网站推广,o2o网站建设包括哪些,银川手机网站建设,品牌网站都有哪些作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;在 Java 中#xff0c;如果要问哪个类使用简单#xff0c;但用好最不简单#xff1f;我想你的脑海中一定会浮现出一次词—… 作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stone在 Java 中如果要问哪个类使用简单但用好最不简单我想你的脑海中一定会浮现出一次词——“ThreadLocal”。确实如此ThreadLocal 原本设计是为了解决并发时线程共享变量的问题但由于过度设计如弱引用和哈希碰撞从而导致它的理解难度大和使用成本高等问题。当然如果稍有不慎还是导致脏数据、内存溢出、共享变量更新等问题但即便如此ThreadLocal 依旧有适合自己的使用场景以及无可取代的价值比如本文要介绍了这两种使用场景除了 ThreadLocal 之外还真没有合适的替代方案。使用场景1本地变量我们以多线程格式化时间为例来演示 ThreadLocal 的价值和作用当我们在多个线程中格式化时间时通常会这样操作。① 2个线程格式化当有 2 个线程进行时间格式化时我们可以这样写import java.text.SimpleDateFormat; import java.util.Date;public class Test {public static void main(String[] args) throws InterruptedException {// 创建并启动线程1Thread t1 new Thread(new Runnable() {Overridepublic void run() {// 得到时间对象Date date new Date(1 * 1000);// 执行时间格式化formatAndPrint(date);}});t1.start();// 创建并启动线程2Thread t2 new Thread(new Runnable() {Overridepublic void run() {// 得到时间对象Date date new Date(2 * 1000);// 执行时间格式化formatAndPrint(date);}});t2.start();}/*** 格式化并打印结果* param date 时间对象*/private static void formatAndPrint(Date date) {// 格式化时间对象SimpleDateFormat simpleDateFormat new SimpleDateFormat(mm:ss);// 执行格式化String result simpleDateFormat.format(date);// 打印最终结果System.out.println(时间 result);} } 以上程序的执行结果为上面的代码因为创建的线程数量并不多所以我们可以给每个线程创建一个私有对象 SimpleDateFormat 来进行时间格式化。② 10个线程格式化当线程的数量从 2 个升级为 10 个时我们可以使用 for 循环来创建多个线程执行时间格式化具体实现代码如下import java.text.SimpleDateFormat; import java.util.Date;public class Test {public static void main(String[] args) throws InterruptedException {for (int i 0; i 10; i) {int finalI i;// 创建线程Thread thread new Thread(new Runnable() {Overridepublic void run() {// 得到时间对象Date date new Date(finalI * 1000);// 执行时间格式化formatAndPrint(date);}});// 启动线程thread.start();}}/*** 格式化并打印时间* param date 时间对象*/private static void formatAndPrint(Date date) {// 格式化时间对象SimpleDateFormat simpleDateFormat new SimpleDateFormat(mm:ss);// 执行格式化String result simpleDateFormat.format(date);// 打印最终结果System.out.println(时间 result);} } 以上程序的执行结果为从上述结果可以看出虽然此时创建的线程数和 SimpleDateFormat 的数量不算少但程序还是可以正常运行的。③ 1000个线程格式化然而当我们将线程的数量从 10 个变成 1000 个的时候我们就不能单纯的使用 for 循环来创建 1000 个线程的方式来解决问题了因为这样频繁的新建和销毁线程会造成大量的系统开销和线程过度争抢 CPU 资源的问题。所以经过一番思考后我们决定使用线程池来执行这 1000 次的任务因为线程池可以复用线程资源无需频繁的新建和销毁线程也可以通过控制线程池中线程的数量来避免过多线程所导致的 CPU 资源过度争抢和线程频繁切换所造成的性能问题而且我们可以将 SimpleDateFormat 提升为全局变量从而避免每次执行都要新建 SimpleDateFormat 的问题于是我们写下了这样的代码import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;public class App {// 时间格式化对象private static SimpleDateFormat simpleDateFormat new SimpleDateFormat(mm:ss);public static void main(String[] args) throws InterruptedException {// 创建线程池执行任务ThreadPoolExecutor threadPool new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue(1000));for (int i 0; i 1000; i) {int finalI i;// 执行任务threadPool.execute(new Runnable() {Overridepublic void run() {// 得到时间对象Date date new Date(finalI * 1000);// 执行时间格式化formatAndPrint(date);}});}// 线程池执行完任务之后关闭threadPool.shutdown();}/*** 格式化并打印时间* param date 时间对象*/private static void formatAndPrint(Date date) {// 执行格式化String result simpleDateFormat.format(date);// 打印最终结果System.out.println(时间 result);} } 以上程序的执行结果为当我们怀着无比喜悦的心情去运行程序的时候却发现意外发生了这样写代码竟然会出现线程安全的问题。从上述结果可以看出程序的打印结果竟然有重复内容的正确的情况应该是没有重复的时间才对。PS所谓的线程安全问题是指在多线程的执行中程序的执行结果与预期结果不相符的情况。a) 线程安全问题分析为了找到问题所在我们尝试查看 SimpleDateFormat 中 format 方法的源码来排查一下问题format 源码如下private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) {// 注意此行代码calendar.setTime(date);boolean useDateFormatSymbols useDateFormatSymbols();for (int i 0; i compiledPattern.length; ) {int tag compiledPattern[i] 8;int count compiledPattern[i] 0xff;if (count 255) {count compiledPattern[i] 16;count | compiledPattern[i];}switch (tag) {case TAG_QUOTE_ASCII_CHAR:toAppendTo.append((char)count);break;case TAG_QUOTE_CHARS:toAppendTo.append(compiledPattern, i, count);i count;break;default:subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);break;}}return toAppendTo; } 从上述源码可以看出在执行 SimpleDateFormat.format 方法时会使用 calendar.setTime 方法将输入的时间进行转换那么我们想想一下这样的场景线程 1 执行了 calendar.setTime(date) 方法将用户输入的时间转换成了后面格式化时所需要的时间线程 1 暂停执行线程 2 得到 CPU 时间片开始执行线程 2 执行了 calendar.setTime(date) 方法对时间进行了修改线程 2 暂停执行线程 1 得出 CPU 时间片继续执行因为线程 1 和线程 2 使用的是同一对象而时间已经被线程 2 修改了所以此时当线程 1 继续执行的时候就会出现线程安全的问题了。正常的情况下程序的执行是这样的非线程安全的执行流程是这样的b) 解决线程安全问题加锁当出现线程安全问题时我们想到的第一解决方案就是加锁具体的实现代码如下import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;public class App {// 时间格式化对象private static SimpleDateFormat simpleDateFormat new SimpleDateFormat(mm:ss);public static void main(String[] args) throws InterruptedException {// 创建线程池执行任务ThreadPoolExecutor threadPool new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue(1000));for (int i 0; i 1000; i) {int finalI i;// 执行任务threadPool.execute(new Runnable() {Overridepublic void run() {// 得到时间对象Date date new Date(finalI * 1000);// 执行时间格式化formatAndPrint(date);}});}// 线程池执行完任务之后关闭threadPool.shutdown();}/*** 格式化并打印时间* param date 时间对象*/private static void formatAndPrint(Date date) {// 执行格式化String result null;// 加锁synchronized (App.class) {result simpleDateFormat.format(date);}// 打印最终结果System.out.println(时间 result);} } 以上程序的执行结果为从上述结果可以看出使用了 synchronized 加锁之后程序就可以正常的执行了。加锁的缺点加锁的方式虽然可以解决线程安全的问题但同时也带来了新的问题当程序加锁之后所有的线程必须排队执行某些业务才行这样无形中就降低了程序的运行效率了。有没有既能解决线程安全问题又能提高程序的执行速度的解决方案呢答案是有的这个时候 ThreadLocal就要上场了。c) 解决线程安全问题ThreadLocal1.ThreadLocal 介绍ThreadLocal 从字面的意思来理解是线程本地变量的意思也就是说它是线程中的私有变量每个线程只能使用自己的变量。以上面线程池格式化时间为例当线程池中有 10 个线程时SimpleDateFormat 会存入 ThreadLocal 中它也只会创建 10 个对象即使要执行 1000 次时间格式化任务依然只会新建 10 个 SimpleDateFormat 对象每个线程调用自己的 ThreadLocal 变量。2.ThreadLocal 基础使用ThreadLocal 常用的核心方法有三个set 方法用于设置线程独立变量副本。没有 set 操作的 ThreadLocal 容易引起脏数据。get 方法用于获取线程独立变量副本。没有 get 操作的 ThreadLocal 对象没有意义。remove 方法用于移除线程独立变量副本。没有 remove 操作容易引起内存泄漏。ThreadLocal 所有方法如下图所示官方说明文档https://docs.oracle.com/javase/8/docs/api/ThreadLocal 基础用法如下/*** 公众号Java中文社群*/ public class ThreadLocalExample {// 创建一个 ThreadLocal 对象private static ThreadLocalString threadLocal new ThreadLocal();public static void main(String[] args) {// 线程执行任务Runnable runnable new Runnable() {Overridepublic void run() {String threadName Thread.currentThread().getName();System.out.println(threadName 存入值 threadName);// 在 ThreadLocal 中设置值threadLocal.set(threadName);// 执行方法打印线程中设置的值print(threadName);}};// 创建并启动线程 1new Thread(runnable, MyThread-1).start();// 创建并启动线程 2new Thread(runnable, MyThread-2).start();}/*** 打印线程中的 ThreadLocal 值* param threadName 线程名称*/private static void print(String threadName) {try {// 得到 ThreadLocal 中的值String result threadLocal.get();// 打印结果System.out.println(threadName 取出值 result);} finally {// 移除 ThreadLocal 中的值防止内存溢出threadLocal.remove();}} } 以上程序的执行结果为从上述结果可以看出每个线程只会读取到属于自己的 ThreadLocal 值。3.ThreadLocal 高级用法① 初始化initialValuepublic class ThreadLocalByInitExample {// 定义 ThreadLocalprivate static ThreadLocalString threadLocal new ThreadLocal(){Overrideprotected String initialValue() {System.out.println(执行 initialValue() 方法);return 默认值;}};public static void main(String[] args) {// 线程执行任务Runnable runnable new Runnable() {Overridepublic void run() {// 执行方法打印线程中数据未设置值打印print(threadName);}};// 创建并启动线程 1new Thread(runnable, MyThread-1).start();// 创建并启动线程 2new Thread(runnable, MyThread-2).start();}/*** 打印线程中的 ThreadLocal 值* param threadName 线程名称*/private static void print(String threadName) {// 得到 ThreadLocal 中的值String result threadLocal.get();// 打印结果System.out.println(threadName 得到值 result);} } 以上程序的执行结果为当使用了 #threadLocal.set 方法之后initialValue 方法就不会被执行了如下代码所示public class ThreadLocalByInitExample {// 定义 ThreadLocalprivate static ThreadLocalString threadLocal new ThreadLocal() {Overrideprotected String initialValue() {System.out.println(执行 initialValue() 方法);return 默认值;}};public static void main(String[] args) {// 线程执行任务Runnable runnable new Runnable() {Overridepublic void run() {String threadName Thread.currentThread().getName();System.out.println(threadName 存入值 threadName);// 在 ThreadLocal 中设置值threadLocal.set(threadName);// 执行方法打印线程中设置的值print(threadName);}};// 创建并启动线程 1new Thread(runnable, MyThread-1).start();// 创建并启动线程 2new Thread(runnable, MyThread-2).start();}/*** 打印线程中的 ThreadLocal 值* param threadName 线程名称*/private static void print(String threadName) {try {// 得到 ThreadLocal 中的值String result threadLocal.get();// 打印结果System.out.println(threadName 取出值 result);} finally {// 移除 ThreadLocal 中的值防止内存溢出threadLocal.remove();}} } 以上程序的执行结果为为什么 set 之后初始化代码就不执行了要理解这个问题需要从 ThreadLocal.get() 方法的源码中得到答案因为初始化方法 initialValue 在 ThreadLocal 创建时并不会立即执行而是在调用了 get 方法只会才会执行测试代码如下import java.util.Date;public class ThreadLocalByInitExample {// 定义 ThreadLocalprivate static ThreadLocalString threadLocal new ThreadLocal() {Overrideprotected String initialValue() {System.out.println(执行 initialValue() 方法 new Date());return 默认值;}};public static void main(String[] args) {// 线程执行任务Runnable runnable new Runnable() {Overridepublic void run() {// 得到当前线程名称String threadName Thread.currentThread().getName();// 执行方法打印线程中设置的值print(threadName);}};// 创建并启动线程 1new Thread(runnable, MyThread-1).start();// 创建并启动线程 2new Thread(runnable, MyThread-2).start();}/*** 打印线程中的 ThreadLocal 值* param threadName 线程名称*/private static void print(String threadName) {System.out.println(进入 print() 方法 new Date());try {// 休眠 1sThread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 得到 ThreadLocal 中的值String result threadLocal.get();// 打印结果System.out.println(String.format(%s 取得值%s %s,threadName, result, new Date()));} } 以上程序的执行结果为从上述打印的时间可以看出initialValue 方法并不是在 ThreadLocal 创建时执行的而是在调用 Thread.get 方法时才执行的。接下来来看 Threadlocal.get 源码的实现public T get() {// 得到当前的线程Thread t Thread.currentThread();ThreadLocalMap map getMap(t);// 判断 ThreadLocal 中是否有数据if (map ! null) {ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;// 有 set 值直接返回数据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);elsecreateMap(t, value);return value; } 从上述源码可以看出当 ThreadLocal 中有值时会直接返回值 e.value只有 Threadlocal 中没有任何值时才会执行初始化方法 initialValue。注意事项—类型必须保持一致注意在使用 initialValue 时返回值的类型要和 ThreadLoca 定义的数据类型保持一致如下图所示如果数据不一致就会造成 ClassCaseException 类型转换异常如下图所示② 初始化2withInitialimport java.util.function.Supplier;public class ThreadLocalByInitExample {// 定义 ThreadLocalprivate static ThreadLocalString threadLocal ThreadLocal.withInitial(new SupplierString() {Overridepublic String get() {System.out.println(执行 withInitial() 方法);return 默认值;}});public static void main(String[] args) {// 线程执行任务Runnable runnable new Runnable() {Overridepublic void run() {String threadName Thread.currentThread().getName();// 执行方法打印线程中设置的值print(threadName);}};// 创建并启动线程 1new Thread(runnable, MyThread-1).start();// 创建并启动线程 2new Thread(runnable, MyThread-2).start();}/*** 打印线程中的 ThreadLocal 值* param threadName 线程名称*/private static void print(String threadName) {// 得到 ThreadLocal 中的值String result threadLocal.get();// 打印结果System.out.println(threadName 得到值 result);} } 以上程序的执行结果为通过上述的代码发现withInitial 方法的使用好和 initialValue 好像没啥区别那为啥还要造出两个类似的方法呢客官莫着急继续往下看。③ 更简洁的 withInitial 使用withInitial 方法的优势在于可以更简单的实现变量初始化如下代码所示public class ThreadLocalByInitExample {// 定义 ThreadLocalprivate static ThreadLocalString threadLocal ThreadLocal.withInitial(() - 默认值);public static void main(String[] args) {// 线程执行任务Runnable runnable new Runnable() {Overridepublic void run() {String threadName Thread.currentThread().getName();// 执行方法打印线程中设置的值print(threadName);}};// 创建并启动线程 1new Thread(runnable, MyThread-1).start();// 创建并启动线程 2new Thread(runnable, MyThread-2).start();}/*** 打印线程中的 ThreadLocal 值* param threadName 线程名称*/private static void print(String threadName) {// 得到 ThreadLocal 中的值String result threadLocal.get();// 打印结果System.out.println(threadName 得到值 result);} } 以上程序的执行结果为4.ThreadLocal 版时间格式化了解了 ThreadLocal 的使用之后我们回到本文的主题接下来我们将使用 ThreadLocal 来实现 1000 个时间的格式化具体实现代码如下import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;public class MyThreadLocalByDateFormat {// 创建 ThreadLocal 并设置默认值private static ThreadLocalSimpleDateFormat dateFormatThreadLocal ThreadLocal.withInitial(() - new SimpleDateFormat(mm:ss));public static void main(String[] args) {// 创建线程池执行任务ThreadPoolExecutor threadPool new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue(1000));// 执行任务for (int i 0; i 1000; i) {int finalI i;// 执行任务threadPool.execute(new Runnable() {Overridepublic void run() {// 得到时间对象Date date new Date(finalI * 1000);// 执行时间格式化formatAndPrint(date);}});}// 线程池执行完任务之后关闭threadPool.shutdown();// 线程池执行完任务之后关闭threadPool.shutdown();}/*** 格式化并打印时间* param date 时间对象*/private static void formatAndPrint(Date date) {// 执行格式化String result dateFormatThreadLocal.get().format(date);// 打印最终结果System.out.println(时间 result);} } 以上程序的执行结果为从上述结果可以看出使用 ThreadLocal 也可以解决线程并发问题并且避免了代码加锁排队执行的问题。使用场景2跨类传递数据除了上面的使用场景之外我们还可以使用 ThreadLocal 来实现线程中跨类、跨方法的数据传递。比如登录用户的 User 对象信息我们需要在不同的子系统中多次使用如果使用传统的方式我们需要使用方法传参和返回值的方式来传递 User 对象然而这样就无形中造成了类和类之间甚至是系统和系统之间的相互耦合了所以此时我们可以使用 ThreadLocal 来实现 User 对象的传递。确定了方案之后接下来我们来实现具体的业务代码。我们可以先在主线程中构造并初始化一个 User 对象并将此 User 对象存储在 ThreadLocal 中存储完成之后我们就可以在同一个线程的其他类中如仓储类或订单类中直接获取并使用 User 对象了具体实现代码如下。主线程中的业务代码public class ThreadLocalByUser {public static void main(String[] args) {// 初始化用户信息User user new User(Java);// 将 User 对象存储在 ThreadLocal 中UserStorage.setUser(user);// 调用订单系统OrderSystem orderSystem new OrderSystem();// 添加订单方法内获取用户信息orderSystem.add();// 调用仓储系统RepertorySystem repertory new RepertorySystem();// 减库存方法内获取用户信息repertory.decrement();} } User 实体类/*** 用户实体类*/ class User {public User(String name) {this.name name;}private String name;public String getName() {return name;}public void setName(String name) {this.name name;} } ThreadLocal 操作类/*** 用户信息存储类*/ class UserStorage {// 用户信息public static ThreadLocalUser USER new ThreadLocal();/*** 存储用户信息* param user 用户数据*/public static void setUser(User user) {USER.set(user);} } 订单类/*** 订单类*/ class OrderSystem {/*** 订单添加方法*/public void add() {// 得到用户信息User user UserStorage.USER.get();// 业务处理代码忽略...System.out.println(String.format(订单系统收到用户%s 的请求。,user.getName()));} } 仓储类/*** 仓储类*/ class RepertorySystem {/*** 减库存方法*/public void decrement() {// 得到用户信息User user UserStorage.USER.get();// 业务处理代码忽略...System.out.println(String.format(仓储系统收到用户%s 的请求。,user.getName()));} } 以上程序的最终执行结果从上述结果可以看出当我们在主线程中先初始化了 User 对象之后订单类和仓储类无需进行任何的参数传递也可以正常获得 User 对象了从而实现了一个线程中跨类和跨方法的数据传递。总结使用 ThreadLocal 可以创建线程私有变量所以不会导致线程安全问题同时使用 ThreadLocal 还可以避免因为引入锁而造成线程排队执行所带来的性能消耗再者使用 ThreadLocal 还可以实现一个线程内跨类、跨方法的数据传递。参考 鸣谢《码出高效Java开发手册》《Java 并发编程 78 讲》 往期推荐 ThreadLocal中的3个大坑内存泄露都是小儿科额Java中用户线程和守护线程区别这么大线程的故事我的3位母亲成就了优秀的我
http://www.lebaoying.cn/news/11163.html

相关文章:

  • 做网站找浩森宇特电子商务网站开发教程课后习题
  • 摄影 网站 源码j江苏省建设工程招投标网站
  • 织梦个人网站模板爱站网在线全集私人影视
  • 3000元建设个人网站如何做网站规范
  • 用word做网站相关论文wordpress多域名不稳定
  • 可以做超大海报的网站响应式网站公司
  • php 网站开发 视频代码做网站图片怎么插
  • 网站版面做的很好的公司企业咨询公司收费标准
  • 网站分为哪几个部分河南郑州网站制作公司
  • 深圳网站建设网站推广方案仿70网站分类目录源码
  • 重庆网站建设就选承越山东专业企业网站建设
  • gis做图网站90设计网站会员全站通与电商模板的区别
  • 列举网站建设的SEO策略深圳小程序开发推荐
  • 珠海做网站公司模板建站可以做优化吗
  • 专业的外贸网站建设有站点网络营销平台
  • 嘉兴南湖区优秀营销型网站建设福田庆三
  • 彩票网站建设柏网站怎么做移动图片不显示不出来吗
  • 海纳企业网站建设免费奖励代码网站
  • 营销型网站首页模板wordpress+widget+开发
  • 村级网站建设系统wordpress 精简优化
  • 成都设计网站的公司神童预言新冠2023结束
  • 适合做网站服务器的主机昔阳做网站公司
  • 业余学做衣服上哪个网站长春网站建设的公司
  • 网站怎么做json数据简单网站 快速建设
  • 建行网站用户名是什么网站建设实习每天内容
  • 廊坊网站建设佛山厂商备案个人网站名称
  • 如何上传模板到网站游戏开发需要具备哪些技术
  • 技术提供微信网站开发临沧市住房和城乡建设局门户网站
  • 免费发广告的网站大全a站在线观看人数在哪
  • 品牌型网站的特点商城网站建设制作