守护线程,线程组,线程池,ThreadLocal

来源:互联网 发布:c语言培训学校 编辑:程序博客网 时间:2024/05/16 01:36

守护线程  

守护线程是一类特殊的线程,它和普通线程的区别在于它并不是应用程序的核心部分 ,当一个应用程序的所有非守护线程终止运行时,即使仍然有守护线程在运行,应用程序 也将终止,反之,只要有一个非守护线程在运行,应用程序就不会终止。守护线程一般被 用于在后台为其它线程提供服务。  

可以通过调用方法 isDaemon() 来判断一个线程是否是守护线程,也可以调用方法 setDa emon() 来将一个线程设为守护线程。

线程组

线程组是一个 Java 特有的概念,在 Java 中,线程组是类ThreadGroup 的对象,每 个线程都隶属于唯一一个线程组,这个线程组在线程创建时指定并在线程的整个生命期内 都不能更改。你可以通过调用包含 ThreadGroup 类型参数的 Thread 类构造函数来指定线程属的线程组,若没有指定,则线程缺省地隶属于名为 system 的系统线程组。在 Java 中,除了预建的系统线程组外,所有线程组都必须显式创建。在 Java 中, 除系统线程组外的每个线程组又隶属于另一个线程组,你可以在创建线程组时指定其所隶 属的线程组,若没有指定,则缺省地隶属于系统线程组。这样,所有线程组组成了一棵以 系统线程组为根的树。

Java 允许我们对一个线程组中的所有线程同时进行操作,比如我们可以通过调用线程 组的相应方法来设置其中所有线程的优先级,也可以启动或阻塞其中的所有线程。 Java 的线程组机制的另一个重要作用是线程安全。线程组机制允许我们通过分组来区分有 不同安全特性的线程,对不同组的线程进行不同的处理,还可以通过线程组的分层结构来 支持不对等安全措施的采用。Java 的 ThreadGroup 类提供了大量的方法来方便我们对线 程组树中的每一个线程组以及线程组中的每一个线程进行操作。

线程池 

诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。 服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。

构建服务器应用程序的一个过于简单的模型应该是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。

那么这种方法的严重不足就很明显。每个请求对应一个线程(thread-per-request)方法的不足之一是:为每个请求创建一个新线程的开销很大 ;在一个 JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。 

我们可以实现一个线程池类,其中客户机类等待一个可用线程、将任务传递给该线程以便执行、然后在任务完成时将线程归还给池, 

一般一个简单线程池至少包含下列组成部分: 

  • 线程池管理器(ThreadPoolManager):用于创建并管理线程池 
  • 工作线程(WorkThread): 线程池中线程 
  • 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
  • 任务队列:用于存放没有处理的任务。提供一种缓冲机制。

ThreadLocal

概述

其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

API说明

ThreadLocal():创建一个线程本地变量。

T get():返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。

protected T initialValue():返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue的典型实现将调用一个适当的构造方法,并返回新构造的对象。

void remove():移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。

void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于initialValue() 方法来设置线程局部变量的值。

在程序中一般都重写initialValue方法,以给定一个特定的初始值。

典型实例

创建一个Bean,通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。

public class Student {    private int age = 0;   //年龄     public int getAge() {        return this.age;    }     public void setAge(int age) {        this.age = age;    }} /** * 多线程下测试程序 */public class ThreadLocalDemo implements Runnable {    //创建线程局部变量studentLocal,在后面你会发现用来保存Student对象    private final static ThreadLocal studentLocal = new ThreadLocal();     public static void main(String[] agrs) {        ThreadLocalDemo td = new ThreadLocalDemo();        Thread t1 = new Thread(td, "a");        Thread t2 = new Thread(td, "b");        t1.start();        t2.start();    }     public void run() {        accessStudent();    }     /**     * 示例业务方法,用来测试     */    public void accessStudent() {        //获取当前线程的名字        String currentThreadName = Thread.currentThread().getName();        System.out.println(currentThreadName + " is running!");        //产生一个随机数并打印        Random random = new Random();        int age = random.nextInt(100);        System.out.println("thread " + currentThreadName + " set age to:" + age);        //获取一个Student对象,并将随机数年龄插入到对象属性中        Student student = getStudent();        student.setAge(age);        System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());        try {            Thread.sleep(500);        }        catch (InterruptedException ex) {            ex.printStackTrace();        }        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());    }     protected Student getStudent() {        //获取本地线程变量并强制转换为Student类型        Student student = (Student) studentLocal.get();        //线程首次执行此方法的时候,studentLocal.get()肯定为null        if (student == null) {            //创建一个Student对象,并保存到本地线程变量studentLocal中            student = new Student();            studentLocal.set(student);        }        return student;    }}

运行结果:

a is running! 

thread a set age to:76 

b is running! 

thread b set age to:27 

thread a first read age is:76 

thread b first read age is:27 

thread a second read age is:76 

thread b second read age is:27 

可以看到a、b两个线程age在不同时刻打印的值是完全相同的。这个程序通过妙用ThreadLocal,既实现多线程并发,又兼顾数据的安全性。

总结

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。

ThreadLocal使用的一般步骤

1.在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。

2.在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。

3.在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。


以上内容整理自互联网


0 0