九、使用线程本地变量

  一个并发程序的最关键特征就是共享数据。这个特性在那些继承了 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. IIS------IIS上部署MVC网站,打开后ExtensionlessUrlHandler-Integrated-4.0解决办法

    链接: http://www.cnblogs.com/mrma/p/3529859.html

  2. electron打包

    1.全局安装electron-packager npm install -g electron-packager 2.在项目目录下执行命令 electron-packager ./ --platfor ...

  3. 自然语言16_Chunking with NLTK

    Chunking with NLTK 对chunk分类数据结构可以图形化输出,用于分析英语句子主干结构 # -*- coding: utf-8 -*-"""Created ...

  4. Zipf定律

    http://www.360doc.com/content/10/0811/00/84590_45147637.shtml 英美在互联网具有绝对霸权 Zipf定律是美国学者G.K.齐普夫提出的.可以表 ...

  5. Json数据可视化

    主要借助JSON.stringfy( value [, replacer] [, space] ). 一.参考文献 1.json数据可视化: http://www.cnblogs.com/lvdaba ...

  6. AgularJS中Unknown provider: $routeProvider解决方案

    最近在学习AgularJS, 做到http://angularjs.cn/A00a这一步时发现没有办法执行路由功能, 后来翻看控制台日志,发现提示Unknown provider: $routePro ...

  7. ecshop如何判断缓存文件是否能更新

    1.打开temp->static_caches文件夹 2.点击后台“清楚缓存” 3.看static_caches文件夹里是否有文件删除 删除->可以更新缓存 否-> 不能更新缓存

  8. IsPostBack--Asp.net

    .net程序员首先需要了解什么是IsPostBack.msdn上边有IsPostBack的定义:获取一个值,该值指示该页是否正为响应客户端回发而加载,或者它是否正被首次加载和访问.如果是为响应客户端回 ...

  9. Win7系统中提示:本地无法启动MySQL服务,报的错误:1067,进程意外终止的解决方法。

    Win7系统中提示:本地无法启动MySQL服务,报的错误:1067,进程意外终止的解决方法. 在本地计算机无法启动MYSQL服务错误1067进程意外终止.这种情况一般是my.ini文件配置出错了1.首 ...

  10. js中,还真不了解 console

    参考链接: https://segmentfault.com/a/1190000000481884