首页 > Java, 挨踢(IT) > Java7并发示例集109:本地线程变量的使用

Java7并发示例集109:本地线程变量的使用

2013年10月10日 发表评论 阅读评论 440 人阅读    

共享数据是并发程序最关键的特性之一。对于无论是继承Thread类的对象,还是实现Runnable接口的对象,这都是一个非常周重要的方面。

如果创建了一个实现Runnable接口的类的对象,并使用该对象启动了一系列的线程,则所有这些线程共享相同的属性。换句话说,如果一个线程修改了一个属性,则其余所有线程都会受此改变的影响。

有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享。Java并发接口提供了一种很清晰的机制来满足此需求,该机制称为本地线程变量。该机制的性能也非常可观。

知其然

按照下面所示步骤,完成示例程序。

  1. 首先,实现一个有上述问题的程序。创建一个名为UnsafeTask的类,并且实现Runnable接口。在类中声明一个java.util.Date类型的私有属性。代码如下:

    1public class UnsafeTask implements Runnable {
    2    private Date startDate;
  2. 实现UnsafeTaskrun()方法,该方法实例化startDate属性,并将其值输出到控制台上。休眠随机一段时间,然后再次将startDate属性的值输出到控制台上。代码如下:

    01@Override
    02public void run() {
    03    startDate = new Date();
    04    System.out.printf("Starting Thread: %s : %s\n",
    05            Thread.currentThread().getId(), startDate);
    06 
    07    try {
    08        TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
    09    } catch (InterruptedException e) {
    10        e.printStackTrace();
    11    }
    12 
    13    System.out.printf("Thread Finished: %s : %s\n",
    14            Thread.currentThread().getId(), startDate);
    15}
  3. 实现问题程序的主类。创建一个带有main()方法的类,UnsafeMain。在main()方法中,创建一个UnsafeTask对象,并使用该对象来创建10个Thread对象,来启动10个线程。在每个线程中间,休眠2秒钟。代码如下:

    01public class UnsafeMain {
    02    public static void main(String[] args) {
    03        UnsafeTask task = new UnsafeTask();
    04        for (int i = 0; i < 10; i++) {
    05            Thread thread = new Thread(task);
    06            thread.start();
    07            try {
    08                TimeUnit.SECONDS.sleep(2);
    09            } catch (InterruptedException e) {
    10                e.printStackTrace();
    11            }
    12        }
    13    }
    14}
  4. 从上面的逻辑来看,每个线程都有一个不同的启动时间。但是,根据下面的输出日志来看,出现了好多相同的时间值。如下:

    01Starting Thread: 9 : Sun Sep 29 23:31:08 CST 2013
    02Starting Thread: 10 : Sun Sep 29 23:31:10 CST 2013
    03Starting Thread: 11 : Sun Sep 29 23:31:12 CST 2013
    04Starting Thread: 12 : Sun Sep 29 23:31:14 CST 2013
    05Thread Finished: 9 : Sun Sep 29 23:31:14 CST 2013
    06Starting Thread: 13 : Sun Sep 29 23:31:16 CST 2013
    07Thread Finished: 10 : Sun Sep 29 23:31:16 CST 2013
    08Starting Thread: 14 : Sun Sep 29 23:31:18 CST 2013
    09Thread Finished: 11 : Sun Sep 29 23:31:18 CST 2013
    10Starting Thread: 15 : Sun Sep 29 23:31:20 CST 2013
    11Thread Finished: 12 : Sun Sep 29 23:31:20 CST 2013
    12Starting Thread: 16 : Sun Sep 29 23:31:22 CST 2013
    13Starting Thread: 17 : Sun Sep 29 23:31:24 CST 2013
    14Thread Finished: 17 : Sun Sep 29 23:31:24 CST 2013
    15Thread Finished: 15 : Sun Sep 29 23:31:24 CST 2013
    16Thread Finished: 13 : Sun Sep 29 23:31:24 CST 2013
    17Starting Thread: 18 : Sun Sep 29 23:31:26 CST 2013
    18Thread Finished: 14 : Sun Sep 29 23:31:26 CST 2013
    19Thread Finished: 18 : Sun Sep 29 23:31:26 CST 2013
    20Thread Finished: 16 : Sun Sep 29 23:31:26 CST 2013
  5. 如前文所示,我们准备使用本地线程变量(the thread-local variables)机制来解决这个问题。

  6. 创建一个名为SafeTask的类,并且实现Runnable接口。代码如下:

    1public class SafeTask implements Runnable {
  7. 声明一个ThreadLocal<Date>类型的对象,该对象实例化时,重写了initialValue()方法,在该方法中返回实际的日期值。代码如下:

    1private static ThreadLocal<Date> startDate = new
    2        ThreadLocal<Date>() {
    3            @Override
    4            protected Date initialValue() {
    5                return new Date();
    6            }
    7        };
  8. 实现SafeTask类的run()方法。该方法和UnsafeTaskrun()方法一样,只是startDate属性的方法方式稍微调整一下。代码如下:

    01@Override
    02public void run() {
    03    System.out.printf("Starting Thread: %s : %s\n",
    04            Thread.currentThread().getId(), startDate.get());
    05 
    06    try {
    07        TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
    08    } catch (InterruptedException e) {
    09        e.printStackTrace();
    10    }
    11 
    12    System.out.printf("Thread Finished: %s : %s\n",
    13            Thread.currentThread().getId(), startDate.get());
    14}
  9. 该安全示例的主类和非安全程序的主类基本相同,只是需要将UnsafeTask修改为SafeTask即可。具体代码如下:

    01public class SafeMain {
    02    public static void main(String[] args) {
    03        SafeTask task = new SafeTask();
    04        for (int i = 0; i < 10; i++) {
    05            Thread thread = new Thread(task);
    06            thread.start();
    07            try {
    08                TimeUnit.SECONDS.sleep(2);
    09            } catch (InterruptedException e) {
    10                e.printStackTrace();
    11            }
    12        }
    13    }
    14}
  10. 运行程序,分析两次输入的不同之处。

为了规范类的命名,本文中主类的命名和原文稍有不同。另外,原文程序和文字叙述不一致。应该是一个笔误。

知其所以然

下面是安全示例的执行结果。从结果中,可以很容易地看出,每个线程都有一个属于各自线程的startDate属性值。程序输入如下:

01Starting Thread: 9 : Sun Sep 29 23:52:17 CST 2013
02Starting Thread: 10 : Sun Sep 29 23:52:19 CST 2013
03Starting Thread: 11 : Sun Sep 29 23:52:21 CST 2013
04Thread Finished: 10 : Sun Sep 29 23:52:19 CST 2013
05Starting Thread: 12 : Sun Sep 29 23:52:23 CST 2013
06Thread Finished: 11 : Sun Sep 29 23:52:21 CST 2013
07Starting Thread: 13 : Sun Sep 29 23:52:25 CST 2013
08Thread Finished: 9 : Sun Sep 29 23:52:17 CST 2013
09Starting Thread: 14 : Sun Sep 29 23:52:27 CST 2013
10Starting Thread: 15 : Sun Sep 29 23:52:29 CST 2013
11Thread Finished: 13 : Sun Sep 29 23:52:25 CST 2013
12Starting Thread: 16 : Sun Sep 29 23:52:31 CST 2013
13Thread Finished: 14 : Sun Sep 29 23:52:27 CST 2013
14Starting Thread: 17 : Sun Sep 29 23:52:33 CST 2013
15Thread Finished: 12 : Sun Sep 29 23:52:23 CST 2013
16Thread Finished: 16 : Sun Sep 29 23:52:31 CST 2013
17Thread Finished: 15 : Sun Sep 29 23:52:29 CST 2013
18Starting Thread: 18 : Sun Sep 29 23:52:35 CST 2013
19Thread Finished: 17 : Sun Sep 29 23:52:33 CST 2013
20Thread Finished: 18 : Sun Sep 29 23:52:35 CST 2013

线程本地变量为每个线程存储了一个属性的副本。可以使用ThreadLocalget()方法获取变量的值,使用set()方法设置变量的值。如果第一次访问线程本地变量,并且该变量还没有赋值,则调用initialValue()方法为每个线程初始化一个值。

永无止境

ThreadLocal类还提供了remove()方法,来删掉调用该方法的线程中存储的本地变量值。

另外,Java并发API还提供了InheritableThreadLocal类,让子线程可以接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。如果线程A有一个线程本地变量,当线程A创建线程B时,则线程B将拥有和线程A一样的线程本地变量。还可以重写childValue(),来初始化子线程的线程本地变量。该方法将接受从父线程以参数形式传递过来的线程本地变量的值。

拿来主义

本文是从 《Java 7 Concurrency Cookbook》 (D瓜哥窃译为 《Java7并发示例集》 )翻译而来,仅作为学习资料使用。没有授权,不得用于任何商业行为。

小有所成

下面是本节示例所包含的所有代码的完整版。

UnsafeTask类的完整代码

01package com.diguage.books.concurrencycookbook.chapter1.recipe9;
02 
03import java.util.Date;
04import java.util.concurrent.TimeUnit;
05 
06/**
07 * 不能保证线程安全的例子
08 * <p/>
09 * Coder: D瓜哥,http://www.diguage.com/
10 * Date: 2013-09-23
11 * Time: 23:58
12 */
13public class UnsafeTask implements Runnable {
14    private Date startDate;
15 
16    @Override
17    public void run() {
18        startDate = new Date();
19        System.out.printf("Starting Thread: %s : %s\n",
20                Thread.currentThread().getId(), startDate);
21 
22        try {
23            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
24        } catch (InterruptedException e) {
25            e.printStackTrace();
26        }
27 
28        System.out.printf("Thread Finished: %s : %s\n",
29                Thread.currentThread().getId(), startDate);
30    }
31}

UnsafeMain类的完整代码

01package com.diguage.books.concurrencycookbook.chapter1.recipe9;
02 
03import java.util.concurrent.TimeUnit;
04 
05/**
06 * 不安全的线程示例
07 * <p/>
08 * Coder: D瓜哥,http://www.diguage.com/
09 * Date: 2013-09-24
10 * Time: 00:04
11 */
12public class UnsafeMain {
13    public static void main(String[] args) {
14        UnsafeTask task = new UnsafeTask();
15        for (int i = 0; i < 10; i++) {
16            Thread thread = new Thread(task);
17            thread.start();
18            try {
19                TimeUnit.SECONDS.sleep(2);
20            } catch (InterruptedException e) {
21                e.printStackTrace();
22            }
23        }
24    }
25}

SafeTask类的完整代码

01package com.diguage.books.concurrencycookbook.chapter1.recipe9;
02 
03import java.util.Date;
04import java.util.concurrent.TimeUnit;
05 
06/**
07 * 使用线程本地变量保证线程安全
08 * <p/>
09 * Coder: D瓜哥,http://www.diguage.com/
10 * Date: 2013-09-29
11 * Time: 23:34
12 */
13public class SafeTask implements Runnable {
14    private static ThreadLocal<Date> startDate = new
15            ThreadLocal<Date>() {
16                @Override
17                protected Date initialValue() {
18                    return new Date();
19                }
20            };
21 
22    @Override
23    public void run() {
24        System.out.printf("Starting Thread: %s : %s\n",
25                Thread.currentThread().getId(), startDate.get());
26 
27        try {
28            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
29        } catch (InterruptedException e) {
30            e.printStackTrace();
31        }
32 
33        System.out.printf("Thread Finished: %s : %s\n",
34                Thread.currentThread().getId(), startDate.get());
35    }
36}

SafeMain类的完整代码

01package com.diguage.books.concurrencycookbook.chapter1.recipe9;
02 
03import java.util.concurrent.TimeUnit;
04 
05/**
06 * 安全的线程示例
07 * <p/>
08 * Coder: D瓜哥,http://www.diguage.com/
09 * Date: 2013-09-24
10 * Time: 00:04
11 */
12public class SafeMain {
13    public static void main(String[] args) {
14        SafeTask task = new SafeTask();
15        for (int i = 0; i < 10; i++) {
16            Thread thread = new Thread(task);
17            thread.start();
18            try {
19                TimeUnit.SECONDS.sleep(2);
20            } catch (InterruptedException e) {
21                e.printStackTrace();
22            }
23        }
24    }
25}

 



作 者: D瓜哥,https://www.diguage.com/
原文链接:https://wordpress.diguage.com/archives/58.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

分类: Java, 挨踢(IT) 标签: ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.