九、使用线程本地变量

  一个并发程序的最关键特征就是共享数据。这个特性在那些继承了 Thread 类或者 实现了 Runnable 接口的对象上显得更加重要。

  如果你创建一个实现了 Runnable 接口的对象,然后再使用多个 Thread 对象去运行这个相同 Runnable 对象,这样所有的 Thread 对象就会共享相同的属性。这意味着你在其中一个 Thread 对象中对属性做的修改会影响其他的 Thread 对象。

  有时,你需要运行同一个 Runnable 对象的多个 Thread 对象都有自己的独立的属性,而不是共享一个。Java的并发 API 中提供了一个高效且清晰的机制叫做线程本地变量来实现此特性。

  在本秘诀中,我们首先开发一个程序来演示问题,然后使用线程本地变量的机制在另一个程序中来解决这个问题。

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

}

public class Core {
    public static void main(String[] args) {
        SafeTask task = new SafeTask();
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(task);
            thread.start();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class SafeTask implements Runnable {
    private static ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
        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());
    }

}

  在没有使用线程本地变量之前,在程序执行结束后,所有线程的时间都相等,在使用了线程本地变量之后,线程的时间就不相等了。

  线程本地变量针对每一个属性为每一个线程保存有自己的值。你可以使用 get() 和 set() 方法来分别获取和设置属性的值。线程本地变量的 initialValue() 方法会在该变量第一次被访问的时候执行,可以用来设置初始值。

  注意:线程本地变量还有一个 remove() 方法可以方便地用来溢出线程线程本地变量。

  Java并发API中的 inheritableThreadLocal 类提供了子线程对父线程线程本地变量的继承性。举例来说就是:线程A有一个本地变量的值是X,如果线程A产生了线程B,那么线程B也具有本地变量且值也是X。当然,你可以通过覆写该类的 childValue() 方法来定制行为。

十、聚集多个线程成线程组

  Java并发API提高了一个有趣的功能:聚集线程成线程组。线程组使得我们可以以一个整体的形式来操控线程组内的所有线程。举例来说,你想控制一些执行相同任务的线程,这时把它们聚集成线程组,你对线程组使用一次调用就对它们每一个发出了相同的调用。

  Java中线程组的对应类就是 ThreadGroup 类。ThreadGroup 对象可以从 Thread 对象或者 ThreadGroup 对象构造而来,相互组合可以形成树状结构。

  在本秘诀中,我们使用 ThreadGroup 来达到如下场景:线程组内的10个线程执行耗时不同的任务,一旦一个执行完毕,终结组内所有线程。

   

public class Main {
    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("Searcher");
        Result result = new Result();
        SearchTask searchTask = new SearchTask(result);

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(threadGroup, searchTask);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.printf("Number of Threads: %d\n", threadGroup.activeCount());
        System.out.printf("Infomation about the Thread Group\n");
        threadGroup.list();

        Thread[] threads = new Thread[threadGroup.activeCount()];
        threadGroup.enumerate(threads);
        for (int i = 0; i < threadGroup.activeCount(); i++) {
            System.out.printf("Thread %s: %s\n", threads[i].getName(), threads[i].getState());
        }

        waitFinish(threadGroup);
        threadGroup.interrupt();
    }

    private static void waitFinish(ThreadGroup threadGroup) {
        while (threadGroup.activeCount() > 4) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Result {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

public class SearchTask implements Runnable {
    private Result result;
    public SearchTask(Result result) {
        this.result = result;
    }
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.printf("Thread %s: Start\n", name);
        try {
            doTask();
            result.setName(name);
        } catch (InterruptedException e) {
            System.out.printf("Thread %s: Interrupted\n", name);
            return;
        }
        System.out.printf("Thread %s: End\n", name);
    }

    private void doTask() throws InterruptedException {
        Random random = new Random((new Date()).getTime());
        int value = (int) (random.nextDouble()*100);
        System.out.printf("Thread %s: %d\n", Thread.currentThread().getName(), value);
        TimeUnit.SECONDS.sleep(value);
    }
}

  ThreadGroup 类存储了与其相关联的 Thread 对象和 ThreadGroup 对象,所以其可以很方便地访问它们的状态和控制它们。

重要:本系列翻译文档也会在本人的微信公众号(此山是我开)第一时间发布,欢迎大家关注。

    

Java 7 Concurrency Cookbook 翻译 第一章 线程管理之五的更多相关文章

  1. Java 7 Concurrency Cookbook 翻译 第一章 线程管理之一

    一.简介 在计算机的世界里,当我们谈论并发时,我们指的是一系列的任务同时运行于一个计算机中.这里说的同时运行,在计算机拥有多于一个处理器或者是一个多核处理器的时候才是真正的同时,在计算机只拥有单核处理 ...

  2. Java 7 Concurrency Cookbook 翻译 第一章 线程管理之六

    十一.处理线程组中的未控制异常 每种编程语言一个很重要的特性就是其所提供的用来处理程序中错误情况的机制.Java语言和其他的现代语言一样,是提供了异常机制来处理对象程序中的错误.Java提供了很多的类 ...

  3. Java 7 Concurrency Cookbook 翻译 第一章 线程管理之四

    七.创建和运行一个后台线程 Java中有一种特别的线程叫做 deamon(后台) 线程.这类线程具有非常低的权限,并且只有在同一个程序中没有其他的正常线程在运行时才会运行.注意:当一个程序中只剩下后台 ...

  4. Java 7 Concurrency Cookbook 翻译 第一章 线程管理之二

    三.中断一个线程 一个拥有多个线程的Java程序要结束,需要满足两个条件之一:一是所有的非后台线程都执行结束了:二是某个线程执行了 System.exit() 方法.当你想要终结一个运行中的Java程 ...

  5. Java 7 Concurrency Cookbook 翻译 第一章 线程管理之三

    五.睡眠和唤醒一个线程 有时,你会想要在一段特定的时间后再去中断线程的运行.举个例子,程序中的一个线程每一分钟检查一次传感器的状态,剩余的时间,线程应该处于空闲的状态.在这段空闲时间里,线程不会使用计 ...

  6. Java 7 Concurrency Cookbook 翻译 序言

    在日常的Java代码开发过程中,很难免地有对多线程的需求,掌握java多线程和并发的机制也是Java程序员写出更健壮和高效代码的基础.笔者找寻国内已出版的关于Java多线程和并发的的中文书籍和翻译书籍 ...

  7. java的优点和误解 《java核心技术卷i》第一章

    <java核心技术卷i>第一章主要内容包括三点: 1:Java白皮书的关键术语:描述Java的十一个关键字: 2:Java applet 3 :关于Java的常见误解   1:第一章:Ja ...

  8. java JDK8 学习笔记——第11章 线程和并行API

    第11章 线程与并行API 11.1 线程 11.1.1 线程 在java中,如果想在main()以外独立设计流程,可以撰写类操作java.lang.Runnable接口,流程的进入点是操作在run( ...

  9. Java 螺纹第三版 第一章Thread介绍、 第二章Thread创建和管理学习笔记

    第一章 Thread导论 为何要用Thread ? 非堵塞I/O      I/O多路技术      轮询(polling)      信号 警告(Alarm)和定时器(Timer) 独立的任务(Ta ...

随机推荐

  1. POJ2635The Embarrassed Cryptographer(大数取余+素数筛选+好题)

    题目链接 题意:K是由两个素数乘积,如果最小的素数小于L,输出BAD最小的素数,否则输出GOOD 分析 素数打表将 L 大点的素数打出来,一定要比L大,然后就开始枚举,只需K对 素数 取余 看看是否为 ...

  2. LINUX 下chmod|chown|chgrp和用法和区别

    1.chgrp(转变文件所属用户组) chgrp 用户组 文件名 ###便是这个格了.若是整个目次下的都改,则加-R参数用于递归. 如:chgrp -R user smb.conf 2.chown(转 ...

  3. 用arp-scan扫描局域网IP地址

    1,在安装之前需要安装yum install -y libpcap libpcap-devel如果没有安装yum工具需要用rpm安装如下软件包[root@oradba arp-scan-1.8]# y ...

  4. Flask-WTF form doesn't have attribute 'validate_on_submit'问题

    今天在学习WTF表单的时候遇到了这个问题,在stackoverflow上搜索查到了解决方案 from flask.ext.wtf import Form from wtforms import Tex ...

  5. Java——其他容器

    除了JFrame表示之外,还有其他几种常见的窗体:JPanel.JSplitPane.JTabbedPane.JScrollPane.JDesktopPane.JInternalFrame等. imp ...

  6. LaTex 使用 - 配置

    Reference Link: http://www.howtotex.com/howto/installing-latex-on-windows/ MikTeX:http://miktex.org/ ...

  7. ecshop 变量表

    get_goods_info($goods_id) 商品详情 get_sales_count($goods_id)  商品销量 get_promote_goods()   参与促销商品  Mobile

  8. dedecms笔记

    截取字符串 方法一: [field:title function="cn_substr(@me,10)"/] 方法二: {dede:arclist typeid=’9′ title ...

  9. 如何让Chrome浏览器可以加载本地XML文件?

    Chrome浏览器的安全限制,禁止本地加载XML等外部文件,如何设置让其可以加载呢? 有两种方法,第一种是在本地服务器环境下浏览,采用 http://localhost/ 的方式浏览你的网页和文件,就 ...

  10. CentOS下nginx简单安装

    说明:环境 系统:Centos 6 软件包:nginx-1.2.4 配置系统yum源 #/etc/yum.repos.d/ #rm -rf ./* vi localhost.repos.d [yumy ...