Java 多线程

介绍Java中多线程。

进程状态

Java中没有进程状态这个概念,JVM是建立在单进程多线程的模型设计。

因此,所谓的进程状态指的是操作系统的进程状态。

Linux进程状态

线程状态

Java线程状态

Java虚拟机的六种状态

  • New:新建状态,线程未开始执行,未执行start命令;
  • Ruanble:运行状态,由于线程切换会频繁在Ready(就绪)Running两个状态之间切换;
  • Blocked:阻塞状态,sychronized会让线程进入该状态;
  • Waiting:等待状态,waitjoin会让线程进入该状态,需要notify等命令来唤醒;
  • TimedWaiting:同上,带有超时时间;
  • Terminated:结束状态,线程执行完毕或出现异常。

Runnable & Thread

线程的实现包括两种方式:实现Runnable接口继承Thread类

run与start的不同

Thread.run方法是用于执行线程的具体操作。

thread.start方式是用于启动当前线程,线程启动后会自动调用Thread.run方法。

java不支持多继承,对于已有父类的继承类来说,需要实现Runnable接口来实现线程类。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class ThreadTest {

public static class Hello implements Runnable {
@Override
public void run() {
// do something
System.out.println("Hello Start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello End");
}
}

static class HelloThread extends Thread {
@Override
public void run() {
// do something
System.out.println("HelloThread Start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HelloThread End");
}
}

public static void main(String[] args) {
Hello hello = new Hello();
Thread t1 = new Thread(hello);
t1.setDaemon(true);
t1.start();

Thread t2 = new HelloThread();
t2.setDaemon(true);
t2.start();

try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

synchronized

Blocked(阻塞)状态是通过synchronized触发的。

synchronized关键字能够保证在同一时刻最多只有一个线程执行该段代码。

synchronized可用于标记对象代码块函数等。

1
2
3
synchronized(someObj) { // dosomething }
sychronized(this) { // dosomething}
synchronized public void method() { // dosomething }

synchronized底层使用使用操作系统的Mutex Lock实现的,代码块的同步使用monitorentermonitorexit指令实现。

interrupt

interrupt:用于停止当前线程,不会立刻停止,而是先给当前线程打一个中断标记。

interrupted:测试当前线程是否已经中断状态,执行后清除状态标识为false

isInterrupted:测试当前线程是否已经中断状态,不清除状态标识。

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
// 观察打印结果
public static class ExtendsThread1 extends Thread {

@Override
public void run() {
try {
for (int i = 0; i < 5000; i++) {
Thread.sleep(1000);
if (this.isInterrupted()) {
System.out.println("interrupted");
return;
}
System.out.println(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

public static void test2() {
ExtendsThread1 thread1 = new ExtendsThread1();
thread1.start();
Thread.sleep(2000);
thread1.interrupt();
}

suspend

suspend用于暂停线程,但已经废弃了。

废弃原因:使用suspend和resume方法容易因为线程暂定导致数据不同步的情况,会造成死锁。

yield

yeild用于放弃当前占用的CPU资源,但放弃的时间不确定,下一次获得CPU的占用权的时间同样不能确定。

yeild特性

  • 静态的原生(native)方法;
  • 状态切换的非实时性
  • 当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程;
  • 它仅能使一个线程从Running状态转到Ready状态,而不是等待或阻塞状态。

实际上,yield仅能让Java线程在Runable状态内部转换。

priority

setPriority方法可以用来设置线程的优先级,优先级高的线程更容易获得CPU的执行权

java中线程优先级分为1~10,默认定义:

1
2
3
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

线程的优先级是可继承的

daemon

java中可以通过setDaemon方法设置守护线程

守护线程是一种特殊的线程,只有当JVM停止运行时,该线程才会被结束。例如,垃圾回收器就是一个用于回收内存对象的守护线程