《Java程序员面试秘笈》—— 1.10 线程局部变量的使用

简介: 线程局部变量分别为每个线程存储了各自的属性值,并提供给每个线程使用。你可以使用get()方法读取这个值,并用set()方法设置这个值。如果线程是第一次访问线程局部变量,线程局部变量可能还没有为它存储值,这个时候initialValue()方法就会被调用,并且返回当前的时间值。

本节书摘来异步社区《Java 7并发编程实战手册》一书中的第1章,第1.10节,作者:【西】Javier Fernández González,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.10 线程局部变量的使用

共享数据是并发程序最核心的问题之一,对于继承了Thread类或者实现了Runnable接口的对象来说尤其重要。

如果创建的对象是实现了Runnable接口的类的实例,用它作为传入参数创建多个线程对象并启动这些线程,那么所有的线程将共享相同的属性。也就是说,如果你在一个线程中改变了一个属性,所有线程都会被这个改变影响。

在某种情况下,这个对象的属性不需要被所有线程共享。Java并发API提供了一个干净的机制,即线程局部变量(Thread-Local Variable),其具有很好的性能。

本节中,我们将创建两个程序:第一个具有刚才提到的问题,另一个使用线程局部变量机制解决了这个问题。

准备工作
本节的范例是在Eclipse IDE里完成的。无论你使用Eclipse还是其他的IDE(比如NetBeans),都可以打开这个IDE并且创建一个新的Java工程。

范例实现
按照接下来的步骤实现本节的范例

1.使要实现的范例具有之前提到的共享问题。创建一个名为UnsafeTask的类,它实现了Runnable接口。声明一个私有的java.util.Date属性。

public class UnsafeTask implements Runnable{
 private Date startDate;```
2.实现run()方法,这个方法将初始化startDate属性,并且将值打印到控制台,让线程休眠一个随机时间,然后再次将startDate的值打印到控制台。

@Override
public void run() {
startDate=new Date();
System.out.printf("Starting Thread: %s : %sn",Thread. currentThread().getId(),startDate);
try {

TimeUnit.SECONDS.sleep( (int)Math.rint(Math.random()*10));
} catch (InterruptedException e) {
  e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n",Thread. currentThread().getId(),startDate);

}`
3.实现这个有问题的应用程序的主程序。创建一个包含main()方法的Main类。这个方法将创建一个UnsafeTask类对象,用它作为传入参数创建10个线程对象并启动这10个线程,每个线程的启动间隔2秒。

  public class Core {
    public static void main(String[] args) {
      UnsafeTask task=new UnsafeTask();
      for (int i=0; i<10; i++){
       Thread thread=new Thread(task);
       thread.start();
       try {
          TimeUnit.SECONDS.sleep(2);
       } catch (InterruptedException e) {
         e.printStackTrace();
      }
    }
  }
}```
4.在下面的截屏中,你将看到这个程序执行的结果。每个线程有一个不同的开始时间,但是当它们结束时,三个线程都有相同的startDate属性值。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/99c4b18a096f0ba73d8b725a51b90efe41ab1aee.png" width="" height="">
</div>


5.如之前提到的,我们将使用线程局部变量机制来解决这个问题。

6.创建一个SafeTask类,用以实现Runnable接口

public class SafeTask implements Runnable {
7.声明一个ThreadLocal对象。这个对象是在initialValue()方法中隐式实现的。这个方法将返回当前日期。

private static ThreadLocal startDate= new

ThreadLocal<Date>() {
    protected Date initialValue(){
        return new Date();
    }
};```

8.实现run()方法。它跟UnsafeTask类的run()方法实现了一样的功能,但是访问startDate属性的方式改变了。

@Override
public void run() {
  System.out.printf("Starting Thread: %s : %s\n",Thread. currentThread().getId(),startDate.get());
  try {
    TimeUnit.SECONDS.sleep((int)Math.rint(Math.random()*10));
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  System.out.printf("Thread Finished: %s : %s\n",Thread. currentThread().getId(),startDate.get());
  }```
9.这个范例的入口类与第一个范例一样,只是创建并作为参数参入的Runnable类对象不同而已。

10.运行范例,并分析两个范例之间的不同。

工作原理
在下面的截屏中,你将看到安全线程类的执行结果。现在,这3个线程对象都有它们自己的startDate属性值。

线程局部变量分别为每个线程存储了各自的属性值,并提供给每个线程使用。你可以使用get()方法读取这个值,并用set()方法设置这个值。如果线程是第一次访问线程局部变量,线程局部变量可能还没有为它存储值,这个时候initialValue()方法就会被调用,并且返回当前的时间值。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/c0e418d3668796c2de1d761bbc5873ebbf84c340.png" width="" height="">
</div>

更多信息
相关文章
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
40 2
|
27天前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
49 9
|
17天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
95 53
|
10天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
16天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
22天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
3天前
|
存储 安全 Java
深入理解ThreadLocal:线程局部变量的机制与应用
在多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将详细介绍`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
13 3
|
12天前
|
SQL 存储 Java
面向 Java 程序员的 SQLite 替代品
SQLite 是轻量级数据库,适用于小微型应用,但其对外部数据源支持较弱、无存储过程等问题影响了开发效率。esProc SPL 是一个纯 Java 开发的免费开源工具,支持标准 JDBC 接口,提供丰富的数据源访问、强大的流程控制和高效的数据处理能力,尤其适合 Java 和安卓开发。SPL 代码简洁易懂,支持热切换,可大幅提高开发效率。
|
18天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
41 4
|
19天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
68 4

热门文章

最新文章

下一篇
无影云桌面