`
xyheqhd888
  • 浏览: 404115 次
  • 性别: Icon_minigender_1
  • 来自: 秦皇岛
社区版块
存档分类
最新评论

容器类的线程安全及ThreadLocal类

阅读更多

1. 容器类默认没有考虑线程安全问题,您必须自行实现同步以确保共享数据在多线程存取下不会出错。例如使用Arraylist时,可以这样实现:

//arraylist参考至一个ArrayList的一个实例 
synchronized(arrayList)
{
  arrayList.add(new SomeClass());
}

 2. 事实上,也可以使用java.util.Collections的synchronizedXXX()等方法来返回一个同步化的容器对象。例如返回一个同步化的List: 

List list = collections.SynchronizedList(new ArrayList());

以这种方式返回的List对象,在存取数据时,会进行同步化的工作,不过在使用Iterator遍历对象时,仍必须实现同步化。因为这样的List使用iterator()方法返回的Iterator对象,并没有保证线程安全。一个实现遍历的例子如下:

List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list)
{
	Iterator i = list.iterator();
	     while(i.hasNext())
	     {
	          foo(i.next());
	     }
}

J2SE5.0之后,新增了java.util.concurrent这个包,其中包括一些确保线程安全的Collection类,如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet等。这些对象与先前介绍的Map、List、Set等对象是相同的,不同的是增加了同步的功能,而且依对象存取时的需求不同而有不同的同步化实现,以同时确保效率和安全性。例如ConcurrentHashMap对Hash Table中不同的区段进行同步化。

3. ThreadLocal类:

   编写多线程安全的程序带来两个问题:(1) 对共享资源的同步会带来一定的效能延迟;(2) 在处理同步的时候,要注意对象的锁定与释放,避免产生死结。尝试从另一个角度思考多线程共享资源的问题。既然共享资源这么困难,那么何不为每个线程创造一个资源的副本,将每个线程存取数据的行为加以隔离,拒绝使用共享呢。实现的方法就是给予每个线程一个特定空间来保管该线程所独享的资源,可以通过java.lang.ThreadLocal类来实现这个功能。这个类是从JDK 1.2之后开始提供的。下面看看如何自行实现一个简单的ThreadLocal类。

package ysu.hxy;
import java.util.*;
public class ThreadLocal<T>
{
	//取得一个同步化的Map对象
	private Map<Thread,T> storage = 
		  Collections.synchronizedMap(new HashMap<Thread,T>());

	public T get()
	{
		//取得当前执行get()方法的线程
		Thread current = Thread.currentThread();
		//根据线程取得线程自有的资源
        T t = storage.get(current);

		//如果还没有线程专用的资源空间
		//则建立一个新的空间
		if(t == null && !storage.containsKey(current))
		{
			t = initialValue();
			storage.put(current,t);
		}
		return t;
	}

	public void set(T t)
	{
		storage.put(Thread.currentThread(),t);
	}

	public T initialValue()
	{
		return null;
	}
}

   范例中使用线程做为键,并将所获得的资源对象放在Map对象中。如果第一次使用get(),也配置一个空间给线程,而initialValue()可以用来设定什么样的初值要先储存在这个空间中,在范例中先简单地设定为null.

    假设有一个原先在单线程环境下的资源SomeResource,现在考虑要在多线程环境下使用,不想考虑复杂的线程共享互斥问题,此时可以通过ThreadLocal类来使用SomeResource。例如:

package ysu.hxy;

public class Resource
{
	ThreadLocal<SomeResource> threadLocal = new ThreadLocal<SomeResource>();

	public static SomeResource getResource()
	{
		//根据当前线程取得专属资源
		SomeResource resource = threadLocal.get();

		//如果没有取得当前专属资源
		if(resource == null)
		{
			//建立一个新的资源并存入ThreadLocal中
			resource = new SomeResource();
			threadLocal.set(resource);
		}
		return resource;
	}
}

 以上所实现的ThreadLocal类只是一个简单示范。在Java中,可以直接使用java.lang.ThreadLocal类。

 在这里简单的示范一个记录(Log)程序,它可以记录每个线程的活动,所使用的是java.util.logging中的类。

package ysu.hxy;
import java.io.*;
import java.util.logging.*;

public class SimpleThreadLogger
{
	private static final java.lang.ThreadLocal<Logger> threadLocal = 
		new java.lang.ThreadLocal<Logger>();

	//输出信息
	public static void log(String msg)
	{
		getThreadLogger().log(Level.INFO,msg);
	}

	//根据线程取得专属Logger
	private static Logger getThreadLogger()
	{
		Logger logger = threadLocal.get();

		if(logger == null)
		{
			try
			{               // 为指定子系统查找或创建一个 logger
				logger = Logger.getLogger(Thread.currentThread().getName());

				//Logger默认是在控制台输出
				//加入一个文件输出的Handler
				//它会输出XML的记录文件
				logger.addHandler(
					new FileHandler(
					      Thread.currentThread().getName()+".log"));
			}
			catch(IOException e)
			{
			}

			threadLocal.set(logger);
		}
		return logger;
	}
}

  ThreadLocal对象会为每个线程提供专属的Logger对象。由于对象属于每个线程,所以就不用担心Logger对象被共享的问题,可以使用如下范例测试:

package ysu.hxy;

public class LoggerTest 
{
	public static void main(String[] args) 
	{
		new TestThread("thread1").start();
		new TestThread("thread2").start();
		new TestThread("thread3").start();
	}
}

class TestThread extends Thread
{
	public TestThread(String name)
	{
		super(name);
	}

	public void run()
	{
		for(int i=0; i<10;i++)
		{                //输出信息
			SimpleThreadLogger.log(getName() + ": message " + i);

			try
			{
				Thread.sleep(1000);
			}
            catch(Exception e)
			{
				SimpleThreadLogger.log(e.toString());
			}
		}
	}
};

 

执行此范例后,可以在控制台上看到输出,也可以在同一目录下看到3个.log文件,它们分别记录了3个线程的活动。通过ThreadLocal,不用编写复杂的线程共享互斥逻辑,其意义在于:有时不共享是好的。如果共享会产生危险,那就不要共享。当然这种方式所牺牲的就是空间,您必须为每个线程保留它们独立的空间。这是一种以空间换取时间与安全性的方法

分享到:
评论

相关推荐

    Java多线程编程中ThreadLocal类的用法及深入

    其实,它就是一个容器,用于存放线程的局部变量,我认为应该叫做 ThreadLocalVariable(线程局部变量)才对,真不理解为什么当初 Sun 公司的工程师这样命名。 早在 JDK 1.2 的时代,java.lang.ThreadLocal 就诞生了...

    【2018最新最详细】并发多线程教程

    17.并发容器之ThreadLocal 18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题 19.并发容器之BlockingQueue 20.并发容器之ArrayBlockingQueue和LinkedBlockingQueue实现原理详解 21.线程池ThreadPoolExecutor实现...

    Java并发编程实战

    4.4 在现有的线程安全类中添加功能 4.4.1 客户端加锁机制 4.4.2 组合 4.5 将同步策略文档化 第5章 基础构建模块 5.1 同步容器类 5.1.1 同步容器类的问题 5.1.2 迭代器与Concurrent-ModificationException ...

    Java 并发编程实战

    4.4 在现有的线程安全类中添加功能 4.4.1 客户端加锁机制 4.4.2 组合 4.5 将同步策略文档化 第5章 基础构建模块 5.1 同步容器类 5.1.1 同步容器类的问题 5.1.2 迭代器与Concurrent-ModificationException ...

    Java并发编程(学习笔记).xmind

    委托是创建线程安全类的最有效策略,只需要让现有的线程安全类管理所有的状态 在现有线程安全类中添加功能 将同步策略文档化 基础构建模块 同步容器类 分类 Vector Hashtable 实现...

    Java并发编程原理与实战

    ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析....

    龙果 java并发编程原理实战

    第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节...

    Java 并发编程原理与实战视频

    第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节...

    龙果java并发编程完整视频

    第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节...

    java并发编程

    第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节...

    并发编程总结.xmind

    java并发编程总结,包括多线程安全机制分析总结,Unsafe源码分析总结,并发工具类总结,ThreadLocal原理和使用,Fork/Join框架使用总结,同步容器和并发容器源码分析

    并发编程笔记20190526.docx

    第二章 线程的并发工具类 21 一、 Fork/Join框架的介绍 21 1、实现步骤: 22 2、工作窃取算法 22 3、分而治之 23 4、Fork/Join使用的标准范式 24 5、Fork/Join框架的异常处理 26 6、Fork/Join框架的实现原理 26 二、...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【线程】ThreadLocal的作用 90 【Spring】什么是IOC和DI?DI是如何实现的 91 【Spring】spring中的IOC(控制反转)的原理 92 【Spring】什么是AOP 92 【Spring】Spring事务机制 93 声明式事物 93 编程式事务 94 ...

    疯狂JAVA讲义

    1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6 1.3.2 Java程序的运行机制和JVM 6 1.4 开发...

    java单例模式看这一篇就够了

    深入分析java单例模式什么是单例模式单例模式的常见写法一、饿汉式单例优点缺点示例二、懒汉式单例示例1(普通写法)示例2(synchronized写法)示例3(DCL写法)示例4(内部类写法)三、注册式单例示例1(容器式)示例2(枚举式...

    史上最全java面试,103项重点知识,带目录

    30. 哪些集合类是线程安全的? 12 31. 迭代器 Iterator 是什么? 12 32. Iterator 怎么使用?有什么特点? 12 33. Iterator 和 ListIterator 有什么区别? 13 三、多线程 13 35. 并行和并发有什么区别? 13 36. 线程...

Global site tag (gtag.js) - Google Analytics