Java多线程原理及ThreadLocal多线程实例详解
一、线程和进程的概念
现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
进程是程序的一个动态执行过程,是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在 Windows系统中,一个运行的exe就是一个进程。线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。
二、Java中线程的实现
在Java中想实现多线程有两种手段,一种是集成Thread类,另一种就是实现Runnable接口。下面看继承自Thread类线程的创建原理。
首先定义一个线程类,该类必须继承自Thread类,同时必须明确的覆写run()方法,如:
class MyThread extends Thread{
public void run(){ //覆写Thread类中的run方法此方法是线程中
线程主体;
}
}
然后定义一个主类,实例化线程类的对象,发动启动线程的命令,如:
public class ThreadText{
public stataic void main(String args[]){
MyThread m1=new MyThread();//实例化对象
m1.start();//启动多线程
}
}
实现Runnable接口,首先定义一个线程类继承自Runnable接口,如:
class MyThread implements Runnable{
public void run(){ //覆写Runnable接口中的run方法
线程主体;
}
}
然后定义一个主类,实例化线程类的对象,发动启动线程的命令,如:
public class ThreadText{
public stataic void main(String args[]){
MyThread m1=new MyThread();//实例化Runnable子类对象
Thread t1=new Thread(m1);//实例化Thread类对象
t1.start();//启动多线程
}
}
三、线程的几种状态
在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
java多线程的应用 ThreadLocal多线程实例详解
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程” 。其实,ThreadLocal并不是一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable更容易让人理解一些。当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
JDK 5 以后提供了泛型支持,ThreadLocal 被定义为支持泛型:
public class ThreadLocal<T> extends Object
T 为线程局部变量的类型。该类定义了 4 个方法:
1) protected T initialValue():返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。 该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。
2)public T get():返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。
3)public void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠 initialValue() 方法来设置线程局部变量的值。
4)public void remove():移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程读取,且这期间当前线程没有设置其值,则将调用其 initialValue() 方法重新初始化其值。这将导致在当前线程多次调用 initialValue 方法。
下面是一个使用 ThreadLocal 的例子,每个线程产生自己独立的序列号。就是使用ThreadLocal存储每个线程独立的序列号复本,线程之间互不干扰。
package sync;
public class SequenceNumber {
// 定义匿名子类创建ThreadLocal的变量
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
// 覆盖初始化方法
public Integer initialValue() {
return 0;
}
};
// 下一个序列号
public int getNextNum() {
seqNum.set(seqNum.get() 1);
return seqNum.get();
}
private static class TestClient extends Thread {
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
// 线程产生序列号
public void run() {
for (int i = 0; i < 3; i ) {
System.out.println("thread[" Thread.currentThread().getName() "] sn[" sn.getNextNum() "]");
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
SequenceNumber sn = new SequenceNumber();
// 三个线程产生各自的序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}
程序的运行结果如下:
thread[Thread-1] sn[1]
thread[Thread-1] sn[2]
thread[Thread-1] sn[3]
thread[Thread-2] sn[1]
thread[Thread-2] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[1] thread[Thread-0] sn[2]
thread[Thread-0] sn[3]
从运行结果可以看出,使用了 ThreadLocal 后,每个线程产生了独立的序列号,没有相互干扰。通常我们通过匿名内部类的方式定义 ThreadLocal的子类,提供初始的变量值。
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。 而 ThreadLocal 则从另一个角度来解决多线程的并发访问。ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
需要注意的是 ThreadLocal 对象是一个本质上存在风险的工具,应该在完全理解将要使用的线程模型之后,再去使用 ThreadLocal 对象。这就引出了线程池(thread pooling)的问题,线程池是一种线程重用技术,有了线程池就不必为每个任务创建新的线程,一个线程可能会多次使用,用于这种环境的任何 ThreadLocal 对象包含的都是最后使用该线程的代码所设置的状态,而不是在开始执行新线程时所具有的未被初始化的状态。 那么 ThreadLocal 是如何实现为每个线程保存独立的变量的副本的呢?通过查看它的源代码,我们会发现,是通过把当前“线程对象”当作键,变量作为值存储在一个 Map 中。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。
ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
上面的这些资料,一部分是自己写的,一部分是参考别人的,然后自己改的.大家在学习的过程中如果有什么疑问也可以在这里提出来.
您可能感兴趣的文章:
Java多线程原理及ThreadLocal多线程实例详解
SimpleDateFormat线程不安全性的三种方法解决
经典Java线程面试题70道
java 线程thread基础知识点总结
Java并发控制机制学习笔记
Python中的ThreadLocal变量如何理解
java并发编程实践笔记
怎么理解python多线程
修改golang源代码实现无竞争版ThreadLocal
Python多线程中的共享变量的简单处理