疯狂的小鸡

多线程-java.lang.Thread

字数统计: 1.9k阅读时长: 7 min
2018/10/11 Share

线程基本使用

一种是创建Thread子类的一个实例并重写run方法,第二种是创建类的时候实现Runnable接口。

当创建一个线程的时候,可以给线程起一个名字。它有助于我们区分不同的线程。

运行调用start()方法。start()方法将线程提供给JVM调度。run方法只是一个普通方法。

线程常用方法

  • thread.join()
    在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。

  • thread.interupt()
    线程的thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

  • Thread.isInterrupted()
    检测当前的中断标记。

  • Thread.interrupted()
    检测当前的中断标记,然后重置中断标记为false。

  • thread.sleep(Long time)
    线程休眠指定时间。

  • thread.yield();
    结束当前处理器,等待JVM再次调度。

  • Thread.currentThread()
    静态方法。返回当前正在执行的进程。

Java 同步块

Java 同步块(synchronized block)用来标记方法或者代码块是同步的。Java同步块用来避免竞争。

有四种不同的同步块:

  • 实例方法
    Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。

  • 静态方法
    静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
    对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。

  • 实例方法中的同步块
    Java同步块构造器用括号将对象括起来。 使用“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。

  • 静态方法中的同步块
    方法同步在该方法所属的类对象上。synchronized(MyClass.class)

线程通信

1)通过共享对象通信
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。

1
2
3
4
5
6
7
8
9
public class MySignal{
protected boolean hasDataToProcess = false;
public synchronized boolean hasDataToProcess(){
return this.hasDataToProcess;
}
public synchronized void setHasDataToProcess(boolean hasData){
this.hasDataToProcess = hasData;
}
}

2)准备处理数据的线程B正在等待数据变为可用。换句话说,它在等待线程A的一个信号,这个信号使hasDataToProcess()返回true。线程B运行在一个循环里,以等待这个信号:

1
2
3
4
5
protected MySignal sharedSignal = ...
...
while(!sharedSignal.hasDataToProcess()){
//do nothing... busy waiting
}

3)wait(),notify()和notifyAll()
忙等待没有对运行等待线程的CPU进行有效的利用,除非平均等待时间非常短。否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号。

Java有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态。java.lang.Object 类定义了三个方法,wait()、notify()和notifyAll()来实现这个等待机制。

一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法。为了调用wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()。这是强制性的!一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。以下是MySingal的修改版本——使用了wait()和notify()的MyWaitNotify:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MonitorObject{
}
public class MyWaitNotify{
MonitorObject myMonitorObject = new MonitorObject();
public void doWait(){
synchronized(myMonitorObject){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
}

public void doNotify(){
synchronized(myMonitorObject){
myMonitorObject.notify();
}
}
}

一旦一个线程被唤醒,不能立刻就退出wait()的方法调用,直到调用notify()的线程退出了它自己的同步块。换句话说:被唤醒的线程必须重新获得监视器对象的锁,才可以退出wait()的方法调用,因为wait方法调用运行在同步块里面。如果多个线程被notifyAll()唤醒,那么在同一时刻将只有一个线程可以退出wait()方法,因为每个线程在退出wait()前必须获得监视器对象的锁。

ThreadLocal

Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。

1)创建一个ThreadLocal变量:

1
private ThreadLocal myThreadLocal = new ThreadLocal();

你实例化了一个ThreadLocal对象。每个线程仅需要实例化一次即可。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。

2)访问ThreadLocal对象
一旦创建了一个ThreadLocal对象,你就可以通过以下方式来存储此对象的值:

1
myThreadLocal.set("A thread local value");

也可以直接读取一个ThreadLocal对象的值:

1
String threadLocalValue = (String) myThreadLocal.get();

get()方法会返回一个Object对象,而set()方法则依赖一个Object对象参数。

为了使get()方法返回值不用做强制类型转换,通常可以创建一个泛型化的ThreadLocal对象。

3)初始化ThreadLocal
由于ThreadLocal对象的set()方法设置的值只对当前线程可见,那有什么方法可以为ThreadLocal对象设置的值对所有线程都可见。

为此,我们可以通过ThreadLocal子类的实现,并覆写initialValue()方法,就可以为ThreadLocal对象指定一个初始化值。

1
2
3
4
5
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override protected String initialValue() {
return "This is the initial value";
}
};

此时,在set()方法调用前,当调用get()方法的时候,所有线程都可以看到同一个初始化值。

6)InheritableThreadLocal
InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。

参考文档:
Jenkov.com/java-concurrency

更多Java基础系列文章,参见Java基础大纲

CATALOG
  1. 1. 线程基本使用
  2. 2. 线程常用方法
  3. 3. Java 同步块
  4. 4. 线程通信
  5. 5. ThreadLocal