Wilder's Blog.

并发编程总结三

字数统计: 1.5k阅读时长: 6 min
2018/02/20 Share

线程简介

线程的理解

现代操作系统调度的最小单元是线程,也加轻量级进程,在一个进程里可以创建多个线程,这些线程拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。

使用多线程的原因

  • 更多的处理器核心
  • 更快的响应时间
  • 更好的变成模型

    线程优先级

    现代操作系统基本采用时分的形式调度运行的线程。在java线程中,通过一个整型成员变量priority来控制优先级,对应的方法为 setPriority() ,默认优先级是5,优先级高的线程分配时间片的数量要多余优先级低的线程。

    线程的状态

    | 状态名称 | 说明 |
    | ———— | :————————————–: |
    | NEW | 初始状态,线程被构建,但还没有调用start()方法 |
    | RUNNABLE | 运行状态,java线程将操作系统中的就绪和运行两个状态统称为“运行中” |
    | BLOCKED | 阻塞状态,表示线程阻塞于锁 |
    | WAITING | 等待状态,只有当其它线程进行通知或者中断的时候,等待状态的线程才会从wait()中返回 |
    | TIME_WAITING | 超时等待状态,在指定时间内可以自行返回 |
    | TERMINATED | 终止状态,表示当前线程已经执行完毕 |

Daemon线程

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。当一个java虚拟机中不存在非Daemon线程的时候,java虚拟机将会退出。一个线程如果要设置为Daemon线程,必须要在这个线程start()之前进行设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Daemon {
public static void mian(String[] args){
Thread thread = new Thread(new DaemonRunner(),"DaemonRunner");
thread.setDaemon(true); //设置为Daemon线程
thread.start();
}

static class DaemonRunner implements Runnable{
public void run(){
try{
Thread.sleeep(4000);
}finally{
System.out.println("Hello world");
}
}
}
}

运行程序之后,发现并没有输出。这是因为main线程和Daemon线程都在运行,main线程结束之后,Daemon就算没有运行结束也会退出。

启动和终止线程

构造线程

线程对象在构造时需要提供线程需要的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。

启动线程

线程对象在初始化完成之后,调用start()方法可以启用这个线程。

线程中断

其它线程通过调用该线程的interrupt()方法对该线程进行中断操作,线程还可以通过isInterrupted()方法来判断该线程是否被中断。
注意:在抛出InterruptedException的方法(比如Thread.sleep())之前,java虚拟机会先将该线程的中断标识清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Interrupted{
static class SleepRunner implements Runnable{
public void run(){
while(true){
Thread.sleep(1000);
}
}
}
static class BusyRunner implements Runnable{
public void run(){
while(true){}
}
}

public static void main(String args[]){
Thread sleepThread = new Thread(new SleepRunner(),"sleepThread");
sleepThread.setDaemon(true);
Thread busyThread = new Thread(new BusyRunner(),"busyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
Thread.sleep(5000);
sleepThread.interrupt();
busyThread.interrupt();
sleepThread.isInterrupted(); //结果为false
busyThread。isInterrupted(); //结果为true
}
}

线程间的通信

线程等待/通知机制

等待/通知机制是任何java对象都具备的,因为这些方法被定义在Object类上
| 方法名称 | 描述 |
| ————— | :————————————–: |
| notify() | 通知一个在对象上等待的线程,使其从wait()方法中返回,而返回的前提是该线程获取到了对象的锁 |
| notifyAll() | 通知所有等待在该对象的线程 |
| wait() | 调用该方法会使线程进入WAITING状态,只有等其它线程唤醒或者该线程被中断的时候才会从wait()方法返回,需要注意的是调用wait()方法之后将会释放锁 |
| wait(long time) | 等待一定的时间自动返回 |
等待/通知机制是指当一个线程调用了对象O的wait()方法之后,该线程进入等待状态,只有当其它线程调用notifyAll()或者notify()方法之后,该线程才会从wait()方法中返回,两个线程就通过对象O来进行交互。
调用wait()、notify()可以注意的细节:

  • 使用wait()、notify()等方法之前要先对调用对象加锁
  • 调用wait()方法之后,线程状态将从RUNNING变为WAITING,并将当前线程放入对象的等待队列中
  • notify()和notifyAll()方法调用以后,等待线程依旧不会从wait()方法中返回,需要等notify()方法的线程释放锁之后线程才有机会从wait()放回

管道输入/输出流

和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Piped{
public static void main(String[] args){
PipeWriter out = new PipedWriter();
PipedReader in = new PipedReader();
//将输入流和输出流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in),"Print");
printThread.start();
int receive = 0;
try{
while(receive = System.in.read()) != -1){
out.write(receive);
}
}finally{
out.close();
}
}
static class Print implements Runnable{
private PipedReader in;
public Print(PipedReader in){
this.in = in;
}
public void run(){
int receive = 0;
while(receive = in.read())!= -1){
System.out.print((char)receive);
}catch(IOException ex){
}
}
}
}

可以看到对于Piped类型的流,必须要先进行绑定,调用connect()方法将输入和输出流绑定起来。

ThreadLoacal的使用

ThreadLocal即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值
比如说线程A设置一个线程ID为A,set(A),则通过get()方法返回的就是A;线程B设置线程Id为B,则get()方法得到的就是B

有一些关于线程池和线程应用的具体代码在github上,欢迎大家指点迷津
https://github.com/WilderGao/ThreadStudy

CATALOG
  1. 1. 线程简介
    1. 1.1. 线程的理解
    2. 1.2. 使用多线程的原因
    3. 1.3. 线程优先级
    4. 1.4. 线程的状态
    5. 1.5. Daemon线程
  2. 2. 启动和终止线程
    1. 2.1. 构造线程
    2. 2.2. 启动线程
    3. 2.3. 线程中断
  3. 3. 线程间的通信
    1. 3.1. 线程等待/通知机制
    2. 3.2. 管道输入/输出流
    3. 3.3. ThreadLoacal的使用