ThreadLocal的介绍与使用

本篇是《图解Java多线程设计模式》第十一章的读书笔记。

有一个储物室,里面有很多储物柜。每个人拿着自己的钥匙去开自己的储物柜,虽然进同一个储物室,但彼此互不干扰。这就是 Thread-Local Storage 线程中的局部存储空间。

来看一个例子:每个线程将信息打印到自己的日志文件中。

TSLog :打印类,每个线程都有自己的 TSLog 对象,向自己的日志文件中打印信息。

public class TSLog {
    private PrintWriter writer;

    public TSLog(String filename) {
        try {
            writer = new PrintWriter(new FileWriter(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void println(String s) {
        writer.println(s);
    }

    public void close() {
        System.out.println("=====End of log======");
        writer.close();
    }
}

Log:利用 ThreadLocal 获取请求对应的 TSLog 对象。

public class Log {

    private static final ThreadLocal<TSLog> tsLogCollection = new ThreadLocal<>();

    private static TSLog getTSLog() {
        TSLog tsLog = tsLogCollection.get();
        if (tsLog == null) {
            tsLog = new TSLog(Thread.currentThread().getName()+"-log.txt");
            tsLogCollection.set(tsLog);
        }
        return tsLog;
    }

    public static void println(String s) {
        getTSLog().println(s);
    }

    public static void close() {
        getTSLog().close();
    }
}

ClientThread:请求线程

public class ClientThread extends Thread {

    public ClientThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName()+" BEGIN");
        for (int i = 0; i < 10; i++) {
            Log.println("i = "+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Log.close();
        System.out.println(getName()+" END");
    }
}

Main:测试

public class Main {
    public static void main(String[] args) {
        new ClientThread("Zhao").start();
        new ClientThread("Qian").start();
        new ClientThread("Sun").start();
    }
}

类的关系图:
在这里插入图片描述

在这里插入图片描述
ThreadLocal 是线程特有的存储空间的意思,也就是只属于该线程,各个线程各自专用。

虽然 Thread-local storage 模式没有使用到锁,但这并不意味着吞吐量的提高,因为 ThreadLocal 存储/获取操作会产生额外的性能开销,该模式的重点在于没有显示的执行互斥处理,导致编程时犯错的可能性减小。

由于 ThreadLocal 会自动判断当前的线程,这相当于在程序中引入了上下文,这有一定危险,因为我们看不到处理中所用到的信息。例如在找Bug时,我们关注信息的流向,但上下文的引入导致当前Bug的产生可能是程序之前的行为导致的,也就导致定位问题的难度。

基于角色与基于任务

关于线程与线程使用的信息之间的关系,由基于角色和基于任务两种思考方式。

主体与客体

要想组装塑料模型,以下二者缺一不可。

  • 组装塑料模型的人
  • 塑料模型套件

同理假设我们需要让线程去完成一项工作,那么以下二者缺一不可:

  • 工作的线程
  • 工作所需的信息

总结以下就是:

  • 用于做某事的主体
  • 用于做某事的客体

在设计多线程程序时,根据以 主体 为主还是以 客体 为主的不同产生了以下两种方式

  • 基于角色:以主体为主
  • 基于任务:以客体为主
基于角色的考虑方式

即”线程最伟大"的方式。线程的实例来保存进行工作所必须的信息(上下文,状态)。这样减少了线程之间的交互信息量。一个线程使用从其它线程接收到的信息来执行处理(指的是线程间的通信),改变自己的内部状态。我们将这种线程称为角色。用代码来表示大致如下:

class Actor extends Thread {
	// 角色的内部状态
	public void run() {
		// 循环的从外部接收并执行任务,改变内部状态
	}
}

该书的第12章介绍的 Active Object 模式就是一种角色。

基于任务的考虑方式

即“任务最伟大”的方式。信息保存在线程之间交互的实例中。不仅是数据还有请求的方法都在该实例中。我们称该实例为信息请求或是命令。这里暂称为任务,任务储存了足够的信息,可以说这是一种 富任务 往来于 轻线程之间的方式。典型就是书的第8章介绍的 Worker Thread 模式。
用代码表示大致如下:

class Task implements Runnable {
	// 进行工作所必须的信息
	public void run() {
		// 工作的处理内容
	}
} 

java.util.TimerTask 类就是一个基于任务的类。该类实现了 Runnable 接口,它会被 java.util.Timer 类调用。如果要定义一项在一定时间后进行的工作或是顶起进行的工作,可以使用该类。
java.util.concurrent.FutureTask 类也是一个基于任务的类。

实际上两种方式是综合在一起的

我们通常采用的是 角色之间通过任务交互 的形式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值