Java线程知识点总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程知识点总结相关的知识,希望对你有一定的参考价值。
文章目录
- Java 线程基础
- 线程简介
- 什么是进程
- 什么是线程
- 进程和线程的区别
- 创建线程
- Thread
- Runnable
- Callable、Future、FutureTask
- Callable
- Future
- FutureTask
- Callable + Future + FutureTask 示例
- 线程基本用法
- 线程休眠
- 线程礼让
- 终止线程
- 守护线程
- 线程通信
- wait/notify/notifyAll
- join
- 管道
- 线程生命周期
- 线程常见问题
- sleep、yield、join 方法有什么区别
- 为什么 sleep 和 yield 方法是静态的
- Java 线程是否按照线程优先级严格执行
- 一个线程两次调用 start()方法会怎样
- `start` 和 `run` 方法有什么区别
- 可以直接调用 `Thread` 类的 `run` 方法么
- 参考资料
Java 线程基础
关键词:
Thread
、Runnable
、Callable
、Future
、wait
、notify
、notifyAll
、join
、sleep
、yeild
、线程状态
、线程通信
线程简介
什么是进程
简言之,进程可视为一个正在运行的程序。它是系统运行程序的基本单位,因此进程是动态的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。进程是操作系统进行资源分配的基本单位。
什么是线程
线程是操作系统进行调度的基本单位。线程也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
进程和线程的区别
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 线程比进程划分更细,所以执行开销更小,并发性更高。
- 进程是一个实体,拥有独立的资源;而同一个进程中的多个线程共享进程的资源。
创建线程
创建线程有三种方式:
- 继承
Thread
类 - 实现
Runnable
接口 - 实现
Callable
接口
Thread
通过继承 Thread
类创建线程的步骤:
- 定义
Thread
类的子类,并覆写该类的 run
方法。run
方法的方法体就代表了线程要完成的任务,因此把 run
方法称为执行体。 - 创建
Thread
子类的实例,即创建了线程对象。 - 调用线程对象的
start
方法来启动该线程。
public class ThreadDemo
public static void main(String[] args)
// 实例化对象
MyThread tA = new MyThread("Thread 线程-A");
MyThread tB = new MyThread("Thread 线程-B");
// 调用线程主体
tA.start();
tB.start();
static class MyThread extends Thread
private int ticket = 5;
MyThread(String name)
super(name);
@Override
public void run()
while (ticket > 0)
System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");
ticket--;
Runnable
实现 Runnable
接口优于继承 Thread
类,因为:
- Java 不支持多重继承,所有的类都只允许继承一个父类,但可以实现多个接口。如果继承了
Thread
类就无法继承其它类,这不利于扩展。 - 类可能只要求可执行就行,继承整个
Thread
类开销过大。
通过实现 Runnable
接口创建线程的步骤:
- 定义
Runnable
接口的实现类,并覆写该接口的 run
方法。该 run
方法的方法体同样是该线程的线程执行体。 - 创建
Runnable
实现类的实例,并以此实例作为 Thread
的 target 来创建 Thread
对象,该 Thread
对象才是真正的线程对象。 - 调用线程对象的
start
方法来启动该线程。
public class RunnableDemo
public static void main(String[] args)
// 实例化对象
Thread tA = new Thread(new MyThread(), "Runnable 线程-A");
Thread tB = new Thread(new MyThread(), "Runnable 线程-B");
// 调用线程主体
tA.start();
tB.start();
static class MyThread implements Runnable
private int ticket = 5;
@Override
public void run()
while (ticket > 0)
System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");
ticket--;
Callable、Future、FutureTask
继承 Thread 类和实现 Runnable 接口这两种创建线程的方式都没有返回值。所以,线程执行完后,无法得到执行结果。但如果期望得到执行结果该怎么做?
为了解决这个问题,Java 1.5 后,提供了 Callable
接口和 Future
接口,通过它们,可以在线程执行结束后,返回执行结果。
Callable
Callable 接口只声明了一个方法,这个方法叫做 call():
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;
那么怎么使用 Callable 呢?一般情况下是配合 ExecutorService 来使用的,在 ExecutorService 接口中声明了若干个 submit 方法的重载版本:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
第一个 submit 方法里面的参数类型就是 Callable。
Future
Future 就是对于具体的 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。
public interface Future<V>
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
FutureTask
FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口和 Future 接口。
所以,FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。
public class FutureTask<V> implements RunnableFuture<V>
// ...
public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable, V result)
public interface RunnableFuture<V> extends Runnable, Future<V>
void run();
事实上,FutureTask 是 Future 接口的一个唯一实现类。
Callable + Future + FutureTask 示例
通过实现 Callable
接口创建线程的步骤:
- 创建
Callable
接口的实现类,并实现 call
方法。该 call
方法将作为线程执行体,并且有返回值。 - 创建
Callable
实现类的实例,使用 FutureTask
类来包装 Callable
对象,该 FutureTask
对象封装了该 Callable
对象的 call
方法的返回值。 - 使用
FutureTask
对象作为 Thread
对象的 target 创建并启动新线程。 - 调用
FutureTask
对象的 get
方法来获得线程执行结束后的返回值。
public class CallableDemo
public static void main(String[] args)
Callable<Long> callable = new MyThread();
FutureTask<Long> future = new FutureTask<>(callable);
new Thread(future, "Callable 线程").start();
try
System.out.println("任务耗时:" + (future.get() / 1000000) + "毫秒");
catch (InterruptedException | ExecutionException e)
e.printStackTrace();
static class MyThread implements Callable<Long>
private int ticket = 10000;
@Override
public Long call()
long begin = System.nanoTime();
while (ticket > 0)
System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");
ticket--;
long end = System.nanoTime();
return (end - begin);
线程基本用法
线程(Thread
)基本方法清单:
方法 | 描述 |
| 线程的执行实体。 |
| 线程的启动方法。 |
| 返回对当前正在执行的线程对象的引用。 |
| 设置线程名称。 |
| 获取线程名称。 |
| 设置线程优先级。Java 中的线程优先级的范围是 [1,10],一般来说,高优先级的线程在运行时会具有优先权。可以通过 |
| 获取线程优先级。 |
| 设置线程为守护线程。 |
| 判断线程是否为守护线程。 |
| 判断线程是否启动。 |
| 中断另一个线程的运行状态。 |
| 测试当前线程是否已被中断。通过此方法可以清除线程的中断状态。换句话说,如果要连续调用此方法两次,则第二次调用将返回 false(除非当前线程在第一次调用清除其中断状态之后且在第二次调用检查其状态之前再次中断)。 |
| 可以使一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。 |
| 静态方法。将当前正在执行的线程休眠。 |
| 静态方法。将当前正在执行的线程暂停,让其他线程执行。 |
线程休眠
使用 Thread.sleep
方法可以使得当前正在执行的线程进入休眠状态。
使用 Thread.sleep
需要向其传入一个整数值,这个值表示线程将要休眠的毫秒数。
Thread.sleep
方法可能会抛出 InterruptedException
,因为异常不能跨线程传播回 main
中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
public class ThreadSleepDemo
public static void main(String[] args)
new Thread(new MyThread("线程A", 500)).start();
new Thread(new MyThread("线程B", 1000)).start();
new Thread(new MyThread("线程C", 1500)).start();
static class MyThread implements Runnable
/** 线程名称 */
private String name;
/** 休眠时间 */
private int time;
private MyThread(String name, int time)
this.name = name;
this.time = time;
@Override
public void run()
try
// 休眠指定的时间
Thread.sleep(this.time);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(this.name + "休眠" + this.time + "毫秒。");
线程礼让
Thread.yield
方法的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行 。
该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
public class ThreadYieldDemo
public static void main(String[] args)
MyThread t = new MyThread();
new Thread(t, "线程A").start();
new Thread(t, "线程B").start();
static class MyThread implements Runnable
@Override
public void run()
for (int i = 0; i < 5; i++)
try
Thread.sleep(1000);
catch (Exception e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "运行,i = " + i);
if (i == 2)
System.out.print("线程礼让:");
Thread.yield();
终止线程
Thread
中的 stop
方法有缺陷,已废弃。使用
Thread.stop
停止线程会导致它解锁所有已锁定的监视器(由于未经检查的 ThreadDeath
异常会在堆栈中传播,这是自然的结果)。 如果先前由这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。stop() 方法会真的杀死线程,不给线程喘息的机会,如果线程持有 ReentrantLock 锁,被 stop() 的线程并不会自动调用 ReentrantLock 的 unlock() 去释放锁,那其他线程就再也没机会获得 ReentrantLock 锁,这实在是太危险了。所以该方法就不建议使用了,类似的方法还有 suspend() 和 resume() 方法,这两个方法同样也都不建议使用了,所以这里也就不多介绍了。
Thread.stop
的许多用法应由仅修改某些变量以指示目标线程应停止运行的代码代替。 目标线程应定期检查此变量,如果该变量指示要停止运行,则应按有序方式从其运行方法返回。如果目标线程等待很长时间(例如,在条件变量上),则应使用中断方法来中断等待。
当一个线程运行时,另一个线程可以直接通过 interrupt
方法中断其运行状态。
public class ThreadInterruptDemo
public static void main(String[] args)
MyThread mt = new MyThread(); // 实例化Runnable子类对象
Thread t = new Thread(mt, "线程"); // 实例化Thread对象
t.start(); // 启动线程
try
Thread.sleep(2000); // 线程休眠2秒
catch (InterruptedException e)
System.out.println("3、main线程休眠被终止");
t.interrupt(); // 中断线程执行
static class MyThread implements Runnable
@Override
public void run()
System.out.println("1、进入run()方法");
try
Thread.sleep(10000); // 线程休眠10秒
System.out.println("2、已经完成了休眠");
catch (InterruptedException e)
System.out.println("3、MyThread线程休眠被终止");
return; // 返回调用处
System.out.println("4、run()方法正常结束");
如果一个线程的 run
方法执行一个无限循环,并且没有执行 sleep
等会抛出 InterruptedException
的操作,那么调用线程的 interrupt
方法就无法使线程提前结束。
但是调用 interrupt
方法会设置线程的中断标记,此时调用 interrupted
方法会返回 true
。因此可以在循环体中使用 interrupted
方法来判断线程是否处于中断状态,从而提前结束线程。
安全地终止线程有两种方法:
- 定义
volatile
标志位,在 run
方法中使用标志位控制线程终止 - 使用
interrupt
方法和 Thread.interrupted
方法配合使用来控制线程终止
【示例】使用 volatile
标志位控制线程终止
public class ThreadStopDemo2
public static void main(String[] args) throws Exception
MyTask task = new MyTask();
Thread thread = new Thread(task, "MyTask");
thread.start();
TimeUnit.MILLISECONDS.sleep(50);
task.cancel();
private static class MyTask implements Runnable
private volatile boolean flag = true;
private volatile long count = 0L;
@Override
public void run()
System.out.println(Thread.currentThread().getName() + " 线程启动");
while (flag)
System.out.println(count++);
System.out.println(Thread.currentThread().getName() + " 线程终止");
/**
* 通过 volatile 标志位来控制线程终止
*/
public void cancel()
flag = false;
【示例】使用 interrupt
方法和 Thread.interrupted
方法配合使用来控制线程终止
public class ThreadStopDemo3
public static void main(String[] args) throws Exception
MyTask task = new MyTask();
Thread thread = new Thread(task, "MyTask");
thread.start();
TimeUnit.MILLISECONDS.sleep(50);
thread.interrupt();
private static class MyTask implements Runnable
private volatile long count = 0L;
@Override
public void run()
System.out.println(Thread.currentThread().getName() + " 线程启动");
// 通过 Thread.interrupted 和 interrupt 配合来控制线程终止
while (!Thread.interrupted())
System.out.println(count++);
System.out.println(Thread.currentThread().getName() + " 线程终止");
守护线程
什么是守护线程?
- 守护线程(Daemon Thread)是在后台执行并且不会阻止 JVM 终止的线程。当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
- 与守护线程(Daemon Thread)相反的,叫用户线程(User Thread),也就是非守护线程。
为什么需要守护线程?
- 守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。典型的应用就是垃圾回收器。
如何使用守护线程?
- 可以使用
isDaemon
方法判断线程是否为守护线程。 - 可以使用
setDaemon
方法设置线程为守护线程。
- 正在运行的用户线程无法设置为守护线程,所以
setDaemon
必须在 thread.start
方法之前设置,否则会抛出 llegalThreadStateException
异常; - 一个守护线程创建的子线程依然是守护线程。
- 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。
public class ThreadDaemonDemo
public static void main(String[] args)
Thread t = new Thread(new MyThread(), "线程");
t.setDaemon(true); // 此线程在后台运行
System.out.println("线程 t 是否是守护进程:" + t.isDaemon());
t.start(); // 启动线程
static class MyThread implements Runnable
@Override
public void run()
while (true)
System.out.println(Thread.currentThread().getName() + "在运行。");
参考阅读:Java 中守护线程的总结
线程通信
当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
wait/notify/notifyAll
-
wait
- wait
会自动释放当前线程占有的对象锁,并请求操作系统挂起当前线程,让线程从 Running
状态转入 Waiting
状态,等待 notify
/ notifyAll
来唤醒。如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify
或者 notifyAll
来唤醒挂起的线程,造成死锁。 -
notify
- 唤醒一个正在 Waiting
状态的线程,并让它拿到对象锁,具体唤醒哪一个线程由 JVM 控制 。 -
notifyAll
- 唤醒所有正在 Waiting
状态的线程,接下来它们需要竞争对象锁。
注意:
-
wait
、notify
、notifyAll
都是 Object
类中的方法,而非 Thread
。-
wait
、notify
、notifyAll
只能用在 synchronized
方法或者 synchronized
代码块中使用,否则会在运行时抛出 IllegalMonitorStateException
。为什么
wait
、notify
、notifyAll
不定义在 Thread
中?为什么 wait
、notify
、notifyAll
要配合 synchronized
使用?首先,需要了解几个基本知识点:
- 每一个 Java 对象都有一个与之对应的 监视器(monitor)
- 每一个监视器里面都有一个 对象锁 、一个 等待队列、一个 同步队列
了解了以上概念,我们回过头来理解前面两个问题。
为什么这几个方法不定义在
Thread
中?由于每个对象都拥有对象锁,让当前线程等待某个对象锁,自然应该基于这个对象(
Object
)来操作,而非使用当前线程(Thread
)来操作。因为当前线程可能会等待多个线程的锁,如果基于线程(Thread
)来操作,就非常复杂了。为什么
wait
、notify
、notifyAll
要配合 synchronized
使用?如果调用某个对象的
wait
方法,当前线程必须拥有这个对象的对象锁,因此调用 wait
方法必须在 synchronized
方法和 synchronized
代码块中。
生产者、消费者模式是 wait
、notify
、notifyAll
的一个经典使用案例:
public class ThreadWaitNotifyDemo02
private static final int QUEUE_SIZE = 10;
private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);
public static void main(String[] args)
new Producer("生产者A").start();
new Producer("生产者B").start();
new Consumer("消费者A").start();
new Consumer("消费者B").start();
static class Consumer extends Thread
Consumer(String name)
super(name);
@Override
public void run()
while (true)
synchronized (queue)
while (queue.size() == 0)
try
System.out.println("队列空,等待数据");
queue.wait();
catch (InterruptedException e)
e.printStackTrace();
queue.notifyAll();
queue.poll(); // 每次移走队首元素
queue.notifyAll();
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + " 从队列取走一个元素,队列当前有:" + queue.size() + "个元素");
static class Producer extends Thread
Producer(String name)
super(name);
@Override
public void run()
while (true)
synchronized (queue)
while (queue.size() == QUEUE_SIZE)
try
System.out.println("队列满,等待有空余空间");
queue.wait();
catch (InterruptedException e)
e.printStackTrace();
queue.notifyAll();
queue.offer(1); // 每次插入一个元素
queue.notifyAll();
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + " 向队列取中插入一个元素,队列当前有:" + queue.size() + "个元素");
join
在线程操作中,可以使用 join
方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
public class ThreadJoinDemo
public static void main(String[] args)
MyThread mt = new MyThread(); // 实例化Runnable子类对象
Thread t = new Thread(mt, "mythread"); // 实例化Thread对象
t.start(); // 启动线程
for (int i = 0; i < 50; i++)
if (i > 10)
try
t.join(); // 线程强制运行
catch (InterruptedException e)
e.printStackTrace();
System.out.println("Main 线程运行 --> " + i);
static class MyThread implements Runnable
@Override
public void run()
for (int i = 0; i < 50; i++)
System.out.println(Thread.currentThread().getName() + " 运行,i = " + i); // 取得当前线程的名字
管道
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下 4 种具体实现:PipedOutputStream
、PipedInputStream
、PipedReader
和 PipedWriter
,前两种面向字节,而后两种面向字符。
public class Piped
public static void main(String[] args) throws Exception
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
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;
Print(PipedReader in)
this.in = in;
public void run()
int receive = 0;
try
while ((receive = in.read()) != -1)
System.out.print((char) receive);
catch (IOException e)
e.printStackTrace();
线程生命周期
java.lang.Thread.State
中定义了 6 种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。
以下是各状态的说明,以及状态间的联系:
- 新建(New) - 尚未调用
start
方法的线程处于此状态。此状态意味着:创建的线程尚未启动。 - 就绪(Runnable) - 已经调用了
start
方法的线程处于此状态。此状态意味着:线程已经在 JVM 中运行。但是在操作系统层面,它可能处于运行状态,也可能等待资源调度(例如处理器资源),资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层操作系统的资源调度。 - 阻塞(Blocked) - 此状态意味着:线程处于被阻塞状态。表示线程在等待
synchronized
的隐式锁(Monitor lock)。synchronized
修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,即处于阻塞状态。当占用 synchronized
隐式锁的线程释放锁,并且等待的线Java总结篇系列:Java多线程
多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的。
一.线程的生命周期及五种基本状态
关于Java中线程的生命周期,首先看一下下面这张较为经典的图:
上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二. Java多线程的创建及启动
Java中线程的创建常见有如三种基本形式
1.继承Thread类,重写该类的run()方法。
1 class MyThread extends Thread { 2 3 private int i = 0; 4 5 @Override 6 public void run() { 7 for (i = 0; i < 100; i++) { 8 System.out.println(Thread.currentThread().getName() + " " + i); 9 } 10 } 11 }
1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 for (int i = 0; i < 100; i++) { 5 System.out.println(Thread.currentThread().getName() + " " + i); 6 if (i == 30) { 7 Thread myThread1 = new MyThread(); // 创建一个新的线程 myThread1 此线程进入新建状态 8 Thread myThread2 = new MyThread(); // 创建一个新的线程 myThread2 此线程进入新建状态 9 myThread1.start(); // 调用start()方法使得线程进入就绪状态 10 myThread2.start(); // 调用start()方法使得线程进入就绪状态 11 } 12 } 13 } 14 }
如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。
2.实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
1 class MyRunnable implements Runnable { 2 private int i = 0; 3 4 @Override 5 public void run() { 6 for (i = 0; i < 100; i++) { 7 System.out.println(Thread.currentThread().getName() + " " + i); 8 } 9 } 10 }
1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 for (int i = 0; i < 100; i++) { 5 System.out.println(Thread.currentThread().getName() + " " + i); 6 if (i == 30) { 7 Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象 8 Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程 9 Thread thread2 = new Thread(myRunnable); 10 thread1.start(); // 调用start()方法使得线程进入就绪状态 11 thread2.start(); 12 } 13 } 14 } 15 }
相信以上两种创建新线程的方式大家都很熟悉了,那么Thread和Runnable之间到底是什么关系呢?我们首先来看一下下面这个例子。
1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 for (int i = 0; i < 100; i++) { 5 System.out.println(Thread.currentThread().getName() + " " + i); 6 if (i == 30) { 7 Runnable myRunnable = new MyRunnable(); 8 Thread thread = new MyThread(myRunnable); 9 thread.start(); 10 } 11 } 12 } 13 } 14 15 class MyRunnable implements Runnable { 16 private int i = 0; 17 18 @Override 19 public void run() { 20 System.out.println("in MyRunnable run"); 21 for (i = 0; i < 100; i++) { 22 System.out.println(Thread.currentThread().getName() + " " + i); 23 } 24 } 25 } 26 27 class MyThread extends Thread { 28 29 private int i = 0; 30 31 public MyThread(Runnable runnable){ 32 super(runnable); 33 } 34 35 @Override 36 public void run() { 37 System.out.println("in MyThread run"); 38 for (i = 0; i < 100; i++) { 39 System.out.println(Thread.currentThread().getName() + " " + i); 40 } 41 } 42 }
同样的,与实现Runnable接口创建线程方式相似,不同的地方在于
1 Thread thread = new MyThread(myRunnable);
那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。
1 public interface Runnable { 2 3 public abstract void run(); 4 5 }
我们看一下Thread类中对Runnable接口中run()方法的实现:
@Override public void run() { if (target != null) { target.run(); } }
也就是说,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述给到的列子中,由于多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。
3.使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
看着好像有点复杂,直接来看一个例子就清晰了。
1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 5 Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象 6 FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象 7 8 for (int i = 0; i < 100; i++) { 9 System.out.println(Thread.currentThread().getName() + " " + i); 10 if (i == 30) { 11 Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程 12 thread.start(); //线程进入到就绪状态 13 } 14 } 15 16 System.out.println("主线程for循环执行完毕.."); 17 18 try { 19 int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果 20 System.out.println("sum = " + sum); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } catch (ExecutionException e) { 24 e.printStackTrace(); 25 } 26 27 } 28 } 29 30 31 class MyCallable implements Callable<Integer> { 32 private int i = 0; 33 34 // 与run()方法不同的是,call()方法具有返回值 35 @Override 36 public Integer call() { 37 int sum = 0; 38 for (; i < 100; i++) { 39 System.out.println(Thread.currentThread().getName() + " " + i); 40 sum += i; 41 } 42 return sum; 43 } 44 45 }
首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:
1 public class FutureTask<V> implements RunnableFuture<V> { 2 3 //.... 4 5 }
1 public interface RunnableFuture<V> extends Runnable, Future<V> { 2 3 void run(); 4 5 }
于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。
执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?
原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。
上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。
三. Java多线程的就绪、运行和死亡状态
就绪状态转换为运行状态:当此线程得到处理器资源;
运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。
由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:
1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 5 MyRunnable myRunnable = new MyRunnable(); 6 Thread thread = new Thread(myRunnable); 7 8 for (int i = 0; i < 100; i++) { 9 System.out.println(Thread.currentThread().getName() + " " + i); 10 if (i == 30) { 11 thread.start(); 12 } 13 if(i == 40){ 14 myRunnable.stopThread(); 15 } 16 } 17 } 18 } 19 20 class MyRunnable implements Runnable { 21 22 private boolean stop; 23 24 @Override 25 public void run() { 26 for (int i = 0; i < 100 && !stop; i++) { 27 System.out.println(Thread.currentThread().getName() + " " + i); 28 } 29 } 30 31 public void stopThread() { 32 this.stop = true; 33 } 34 35 }
---------------------------------------------------------------------------------
笔者水平有限,若有错漏,欢迎指正,如果转载以及CV操作,请务必注明出处,谢谢!以上是关于Java线程知识点总结的主要内容,如果未能解决你的问题,请参考以下文章