欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java并发编程示例(九):本地线程变量的使用

程序员文章站 2024-03-02 13:52:58
共享数据是并发程序最关键的特性之一。对于无论是继承thread类的对象,还是实现runnable接口的对象,这都是一个非常周重要的方面。 如果创建了一个实现runnabl...

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

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

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

知其然

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

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

复制代码 代码如下:

public class unsafetask implements runnable {
    private date startdate;

2.实现unsafetask的run()方法,该方法实例化startdate属性,并将其值输出到控制台上。休眠随机一段时间,然后再次将startdate属性的值输出到控制台上。代码如下:

复制代码 代码如下:

@override
public void run() {
    startdate = new date();
    system.out.printf("starting thread: %s : %s\n",
            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()方法的类,unsafemain。在main()方法中,创建一个unsafetask对象,并使用该对象来创建10个thread对象,来启动10个线程。在每个线程中间,休眠2秒钟。代码如下:

复制代码 代码如下:

public class unsafemain {
    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.从上面的逻辑来看,每个线程都有一个不同的启动时间。但是,根据下面的输出日志来看,出现了好多相同的时间值。如下:

复制代码 代码如下:

starting thread: 9 : sun sep 29 23:31:08 cst 2013
starting thread: 10 : sun sep 29 23:31:10 cst 2013
starting thread: 11 : sun sep 29 23:31:12 cst 2013
starting thread: 12 : sun sep 29 23:31:14 cst 2013
thread finished: 9 : sun sep 29 23:31:14 cst 2013
starting thread: 13 : sun sep 29 23:31:16 cst 2013
thread finished: 10 : sun sep 29 23:31:16 cst 2013
starting thread: 14 : sun sep 29 23:31:18 cst 2013
thread finished: 11 : sun sep 29 23:31:18 cst 2013
starting thread: 15 : sun sep 29 23:31:20 cst 2013
thread finished: 12 : sun sep 29 23:31:20 cst 2013
starting thread: 16 : sun sep 29 23:31:22 cst 2013
starting thread: 17 : sun sep 29 23:31:24 cst 2013
thread finished: 17 : sun sep 29 23:31:24 cst 2013
thread finished: 15 : sun sep 29 23:31:24 cst 2013
thread finished: 13 : sun sep 29 23:31:24 cst 2013
starting thread: 18 : sun sep 29 23:31:26 cst 2013
thread finished: 14 : sun sep 29 23:31:26 cst 2013
thread finished: 18 : sun sep 29 23:31:26 cst 2013
thread finished: 16 : sun sep 29 23:31:26 cst 2013

5.如前文所示,我们准备使用本地线程变量(the thread-local variables)机制来解决这个问题。

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

复制代码 代码如下:

public class safetask implements runnable {

7.声明一个threadlocal<date>类型的对象,该对象实例化时,重写了initialvalue()方法,在该方法中返回实际的日期值。代码如下:
复制代码 代码如下:

private static threadlocal<date> startdate = new
        threadlocal<date>() {
            @override
            protected date initialvalue() {
                return new date();
            }
        };

8.实现safetask类的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.该安全示例的主类和非安全程序的主类基本相同,只是需要将unsafetask修改为safetask即可。具体代码如下:

复制代码 代码如下:

public class safemain {
    public static void main(string[] args) {
        safetask task = new safetask();
        for (int i = 0; i < 10; i++) {
            thread thread = new thread(task);
            thread.start();
            try {
                timeunit.seconds.sleep(2);
            } catch (interruptedexception e) {
                e.printstacktrace();
            }
        }
    }
}

10.运行程序,分析两次输入的不同之处。

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

知其所以然

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

复制代码 代码如下:

starting thread: 9 : sun sep 29 23:52:17 cst 2013
starting thread: 10 : sun sep 29 23:52:19 cst 2013
starting thread: 11 : sun sep 29 23:52:21 cst 2013
thread finished: 10 : sun sep 29 23:52:19 cst 2013
starting thread: 12 : sun sep 29 23:52:23 cst 2013
thread finished: 11 : sun sep 29 23:52:21 cst 2013
starting thread: 13 : sun sep 29 23:52:25 cst 2013
thread finished: 9 : sun sep 29 23:52:17 cst 2013
starting thread: 14 : sun sep 29 23:52:27 cst 2013
starting thread: 15 : sun sep 29 23:52:29 cst 2013
thread finished: 13 : sun sep 29 23:52:25 cst 2013
starting thread: 16 : sun sep 29 23:52:31 cst 2013
thread finished: 14 : sun sep 29 23:52:27 cst 2013
starting thread: 17 : sun sep 29 23:52:33 cst 2013
thread finished: 12 : sun sep 29 23:52:23 cst 2013
thread finished: 16 : sun sep 29 23:52:31 cst 2013
thread finished: 15 : sun sep 29 23:52:29 cst 2013
starting thread: 18 : sun sep 29 23:52:35 cst 2013
thread finished: 17 : sun sep 29 23:52:33 cst 2013
thread finished: 18 : sun sep 29 23:52:35 cst 2013

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

永无止境

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

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

拿来主义

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

小有所成

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

unsafetask类的完整代码:

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.date;
import java.util.concurrent.timeunit;

/**
 * 不能保证线程安全的例子
 * date: 2013-09-23
 * time: 23:58
 */
public class unsafetask implements runnable {
    private date startdate;

    @override
    public void run() {
        startdate = new date();
        system.out.printf("starting thread: %s : %s\n",
                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);
    }
}

unsafemain类的完整代码:

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.concurrent.timeunit;

/**
 * 不安全的线程示例
 * date: 2013-09-24
 * time: 00:04
 */
public class unsafemain {
    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();
            }
        }
    }
}

safetask类的完整代码:

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.date;
import java.util.concurrent.timeunit;

/**
 * 使用线程本地变量保证线程安全
 * date: 2013-09-29
 * time: 23:34
 */
public class safetask implements runnable {
    private static threadlocal<date> startdate = new
            threadlocal<date>() {
                @override
                protected date initialvalue() {
                    return new date();
                }
            };

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

safemain类的完整代码:

复制代码 代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe9;

import java.util.concurrent.timeunit;

/**
 * 安全的线程示例
 * date: 2013-09-24
 * time: 00:04
 */
public class safemain {
    public static void main(string[] args) {
        safetask task = new safetask();
        for (int i = 0; i < 10; i++) {
            thread thread = new thread(task);
            thread.start();
            try {
                timeunit.seconds.sleep(2);
            } catch (interruptedexception e) {
                e.printstacktrace();
            }
        }
    }
}