CP-02-线程类自己

创建线程的三种方法

  • 继承Thread类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ThreadDemo extends Thread {

// 1. 新建一个类继承 Thread 类,并重写 Thread 类的 run() 方法。
@Override
public void run() {
System.out.println("Hello Thread");
}

public static void main(String[] args) {

// 2. 创建 Thread 子类的实例。
ThreadDemo threadDemo = new ThreadDemo();
// 3. 调用该子类实例的 start() 方法启动该线程。
threadDemo.start();
}
}
  • 实现Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RunnableDemo implements Runnable {

// 1. 创建一个类实现 Runnable 接口,并重写该接口的 run() 方法。
@Override
public void run() {
System.out.println("Hello Runnable");
}


public static void main(String[] args) {

// 2. 创建该实现类的实例。
RunnableDemo runnableDemo = new RunnableDemo();

// 3. 将该实例传入 Thread(Runnable r) 构造方法中创建 Thread 实例。
Thread thread = new Thread(runnableDemo);

// 4. 调用该 Thread 线程对象的 start() 方法。
thread.start();

}
}
  • 配合FutureTask和Callable实现

该方式可以获取线程执行的结果

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
public class CallableDemo implements Callable<String> {

// 1. 创建一个类实现 Callable 接口,并重写 call() 方法。
@Override
public String call() throws Exception {
System.out.println("CallableDemo is Running");
return "Hello Callable";
}

public static void main(String[] args) {

// 2. 创建该 Callable 接口实现类的实例。
CallableDemo callableDemo = new CallableDemo();

// 3. 将 Callable 的实现类实例传入 FutureTask(Callable<V> callable) 构造方法中创建 FutureTask 实例。
FutureTask<String> futureTask = new FutureTask<>(callableDemo);

// 4. 将 FutureTask 实例传入 Thread(Runnable r) 构造方法中创建 Thread 实例。
Thread thread = new Thread(futureTask);

// 5. 调用该 Thread 线程对象的 start() 方法。
thread.start();

// 6. 调用 FutureTask 实例对象的 get() 方法获取返回值。
// 该方法阻塞直到子线程结束返回结果
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}

线程的三种状态

  • 就绪状态:调用线程的start()方法后,处于就绪状态,随时等待cpu调度执行
  • 运行状态:cpu调度执行run方法
  • 阻塞状态:执行的cpu时间片到了,线程被cpu调度暂时挂起,调度其他线程执行

线程优先级

1
2
3
4
5
6
7
8
9
//设置优先级
public final void setPriority(int newPriority)
//获取优先级
public final int getPriority()

//Thread类的三个静态变量
MAX_PRIORITY:优先级为 10
NORM_PRIORITY:优先级为 5
MIN_PRIORITY:优先级为 1

线程优先级为1-10范围内

后台线程/守护线程

1
2
3
4
//将线程设置为后台线程,参数true
public final void setDaemon(boolean on)
//返回此线程是否为后台线程
public final boolean isDaemon()

后台线程的特点就是当前台线程全部结束后,后台线程就会随之结束。

它的唯一用途就是为其他线程提供服务,如:计时器线程,定时的向其他线程发送信号或清空过时的高速缓存项线程。当程序只剩下守护线程时,JVM就退出了,因为只剩它守护线程了,没有服务对象了,也就没有再继续运行的必要了。

守护线程不应该去访问固定资源,如:文件,数据库,因为它不定在什么时候被中断,而导致资源未关闭。

native方法

native关键字说明它修饰的是一个原生态方法,它的实现不是在当前文件,而是采用其他语言编写的(如C, C++等)。

Java语言本身不能对操作系统底层方法进行访问和调用,而C,C++等语言可以,因为操作系统是采用这些语言实现的。但Java可以通过JNI接口调用其他语言来实现对底层进行访问。

Thread.currentThread()

  • 返回当前执行线程的Thread实例对象

Thread.State getState()

  • 得到实例线程的当前状态,Thread.State枚举值参考JDK API

Thread.sleep()

1
2
3
//Java原生方法,实现采用其他语言实现
public static native void sleep(long millis)
public static void sleep(long millis, int nanos)

注意为静态方法,让当前线程从运行状态到阻塞状态,参数为阻塞的时间,millis为毫秒,nanos为纳秒

yield()

1
2
//Java原生方法,实现采用其他语言实现
public static native void yield();

理论上,yield意味着放手,放弃,投降。

一个调用yield()方法的线程告诉JVM它乐意让其他同优先级线程或比它优先级高的线程占用自己的位置。即让当前线程从运行状态到就绪状态,而不是阻塞状态,稍后可能会继续被调度执行。

这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,不能保证使得当前正在运行的线程迅速转换到可运行的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class YieldDemo extends Thread {

@Override
public void run() {

for (int i = 0; i < 50; i++) {
System.out.println(getName() + " " + i);

//i=20时,当前线程主动让出cpu执行权,
if (i == 20) {
Thread.yield();
}
}
}

public static void main(String[] args) {

YieldDemo yieldDemo1 = new YieldDemo();
YieldDemo yieldDemo2 = new YieldDemo();

yieldDemo1.start();
yieldDemo2.start();
}
}

join()

1
2
3
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

这个方法要有两个线程,也就是一个线程t1的线程体中有另一个线程t2调用join()方法,即t1的run方法中有t2.join()调用,此时t1会被阻塞,直到t2线程执行完,或join(millis)时间到,才会继续执行t1的run方法。

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
public class JoinDemo extends Thread {

//子线程任务
@Override
public void run() {
for(int i = 0; i < 50; i++) {

System.out.println(getName() + " " + i);
}
}

public static void main(String[] args) throws Exception {

JoinDemo joinDemo = new JoinDemo();

for(int i = 0; i < 50; i++) {

//主线程运行到i=20时被阻塞,开始执行50次子线程,然后继续执行主线程
if(i == 20) {
joinDemo.start();
joinDemo.join();

}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}

sleep()和yield()区别

作用处 sleep() yield()
给其他线程执行机会 会给其他线程执行机会,不会理会其他线程的优先级 只会给优先级相同,或者优先级更高的线程执行机会
影响当前线程的状态 从阻塞到就绪状态 直接进入就绪状态
异常 需要抛出 InterruptedException 不需要抛出任何异常

Tread Dump(线程转储)

  • 线程转储是一个JVM中活动线程的列表
  • 它对分析系统的性能瓶颈和死锁非常有用

分析工具

  • 有很多方法可以获取线程转储——使用Profiler,Kill -3命令,jstack工具等等
  • jstack工具,因为它容易使用并且是JDK自带的
  • 由于它是一个基于终端的工具,所以我们可以编写一些脚本去定时的产生线程转储以待分析