快速了解多线程的一些常见知识点

共 5162字,需浏览 11分钟

 ·

2021-10-18 10:05

在Java编程中,多线程是非常重要知识点. 关于Java线程有些不可不知的知识点需要牢记,下面就介绍了这些知识点.


1. 优先级

每个Java线程都有对应的优先级,高优先级的线程比低优先级的线有更多的执行机会。新创建的Java线程默认的优先级是5, 最低是1,最高是10;

1
2
3
4
5
6
7
8
9
10
11
12
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

Note:如果在一个线程中创建一个新的线程,新线程的默认优先级和所在线程的优先级保持一致。

2. daemon 或 非daemon

一个Java线程可以是daemon的也可以是非daemon的,JVM在启动的时候启动了main线程,该线程是非daemon;JVM在下面2情况下会退回:

  1. 调用Runtime.exit()方法

  2. 所有非daemon的线程都结束了。

3. 创建线程的方式

有2种创建线程的方式,一种是继承Thread类;一种是直接new一个Thread对象,并在构造函数中传入一个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
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
}
}
PrimeThread p = new PrimeThread(143);
p.start();

//new
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
 . . .
}
}
PrimeRun p = new PrimeRun(143);
new Thread(p).start();

4. 线程的状态

JDK中关于线程状态的定义:

  • new : 一个新创建的,没有调用start方法的线程处于该状态;

  • RUNNABLE :可执行的,在JVM中是处于执行中,但是在等待其它的操作系统资源,如cpu资源;

  • BLOCKED :阻塞的,一个线程在进入(或再次进入)一个同步方法或同步块时等待监视器锁时处于该状态。

  • WAITING :等待,一个线程在调用了如下的方法时会处于等待状态:
    1.Object.wait
    2.Thread.join //正在主线程中调用一个一个线程的join方法,在主线程处于等待状态
    3.LockSupport#park()

  • TIMED_WAITING 等待的,不过该等待状态是有时间限制的。一个线程调用下面的方法会进入该状态:
    1.Thread.sleep
    2.Object.wait
    3.Thread.join
    4.LockSupport.parkNanos
    5.LockSupport.parkUntil

  • TERMINATED 终止,当一个线程执行完成后处于该状态

5. 线程的调度

理想的情况下,每个程序的线程都拥有一个专属于自己的处理器;在计算机还不能拥有几千,甚至几百万CPU处理器的情况下,多个线下需要共享仅有的cpu资源,如果分配线程执行所需的cpu资源就需要线程调度器的调度了。在操作系统层面有自己的线程调度器,在JVM中也存在Java线程调度器。

在Java线程调度中有2点比较重要:

  1. Java规范并没有强制要求每个JVM按照特定的调度规则调用线程,或者必须包含一个线程调度器。线程调度的实现完全是依赖平台的。

  2. 在编写Java多线程代码时,我们唯一需要考虑的是不要让一个线程大量的占用cpu时间(eg.死循环)。

大多数平台上JVM的线程调度是依赖操作系统本身的线程调度器的,每个线下有不同的优先级,在基于时间片的规则下,高优先级的线程拥有更多的CPU执行机会;相同优先级的线程可以按照FIFO调度。

6. Runnable、Callable、Future 和 FutureTasek的区别

Java中存在Runnable、Callable、Future、FutureTask这几个与线程相关的类或者接口,在Java中也是比较重要的几个概念,我们通过下面的简单示例来了解一下它们的作用与区别。

6.1 Runnable

Runnable 应该是这几个类我们使用的最多的一个。JDK的文档说明:如果一个类的实例想要通过一个线程来执行,则该类应该实现Runnable接口。Runnable被设计用来对那些处于active状态时会执行代码的对象提供一个统一的协议。例如Thread类就实现了Runnable接口。

1
2
3
public interface Runnable {
public abstract void run();
}

6.2 Callable

Callable 表示一个可以携带返回结果的任务。该接口的实现类需要一个没有参数的call方法。Callable 和 Runnable类似,都是被设计用来被另外一个线程执行的任务。但是Runnable不能返回一个结果,且不能抛出一个checked异常。

1
2
3
4
5
6
7
8
9
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}

6.3 Future

一个Futrue表示一个异步计算的结果。它提供了一系列方法,用来检测计算是否完成,获取计算结果,等待计算结果等。只能通过get方法获取计算结果;如果计算没有完成,在必要条件下,则get方法一直等待,直到任务完成。cancel方法用来取消任务。其它的方法都是用来测试计算是否完成,或是否取消。一旦计算完成后,就不可以被取消。如果你想使用Future,但并不需要返回一个结果,则可以使用Future并返回null。

一个简单的例子(JDK原文):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target) throws InterruptedException {
Future future = executor.submit(new Callable() {
public String call() {
return searcher.search(target);
}});
displayOtherThings();
try{
displayText(future.get()); // use future
}catch(ExecutionException ex){
cleanup(); return;
}
}
}

6.4 FutureTask

FutureTask 表示一个可以取消的异步计算任务。它实现了Runnable接口和Future接口。FutureTask 可以用来包装Runnable和Callable对象。应该FutureTask实现了Runnable接口。同时可以通过被提交到Executor去执行。

1
2
3
4
5
6
7
8
9
10
11
public FutureTask(Callable callable) {  
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}

可以看到,Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。该适配函数的实现如下 :

1
2
3
4
5
public static  Callable callable(Runnable task, T result) {  
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}

RunnableAdapter适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}

7. 总结

由于FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行。
并且还可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。因此FutureTask既是Future、
Runnable,又是包装了Callable(如果是Runnable最终也会被转换为Callable ), 它是这两者的合体。

source: //leokongwq.github.io/2016/10/16/java-runnable-callable-future.html

喜欢,在看

浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报