本篇博文主要包含:
- 进程、线程及多线程的基本概念
- 线程创建的五种方式
- 线程常用api
- 守护线程与非守护线程(用户线程)
- 线程的五种状态
- join()、yield()和priority()方法
一. 线程基本概念
-
进程:每个正在系统上运行的程序都是一个进程,如QQ等。每个进程包含一到多个线程。
-
线程:线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。即正在独立运行的一条执行路径。
-
多线程:能够在同一时间执行多于一个线程,每个线程都能独立运行,互不影响,进而提高了程序的效率,而不提高带宽速度。在多线程中如果其中一个线程抛异常执行中断,而其他线程却不会受其影响。
一个操作系统中可以有多个进程,每个进程中一定会有一个决定代码执行顺序的主线程,如main函数。 -
线程创建有哪些方式:
(1)继承Thread类
(2)实现Runnable接口
(3)使用匿名内部类
(4)Callable类
(5)使用线程池创建线程(企业中常用)
使用实现Runnable接口比继承Thread类好,因为可以实现多个接口和继承一个类,另外还符合面向接口开发。 -
单线程与多线程的区别:单线程中代码从上往下执行,相当于同步,如果其中某个方法中的代码挂了,其下面的代码就不能继续执行了;多线程就相当于异步,同时可以执行多个方法,其中一个方法中的代码挂了,不会影响其他方法的执行。
二. 线程创建方式代码实现
- 继承Thread类
public class ThreadDemo{
public static void main(String[] args) {
//调用线程
Thread01 thread01 = new Thread01();
//启动线程,不是调用run方法,而是调用start方法。
//启动线程后,代码不会从上往下执行。
thread01.start();
for(int i=0; i<5; i++) {
System.out.println("main:"+i);
}
}
}
class Thread01 extends Thread{
//run方法中编写多线程需要执行的代码
@Override
public void run() {
for(int i=0; i<5; i++) {
System.out.println("childern:"+i);
}
}
}
运行结果:
- 实现Runnable接口
public class RunnableDemo {
public static void main(String[] args) {
//调用线程
Thread02 t = new Thread02();
Thread thread = new Thread(t);
//启动线程
thread.start();
for(int i=0; i<5; i++) {
System.out.println("main:"+i);
}
}
}
class Thread02 implements Runnable{
//run方法中编写多线程需要执行的代码
@Override
public void run() {
for(int i=0; i<5; i++) {
System.out.println("childern:"+i);
}
}
}
运行结果:
- 使用匿名内部类
public class AnonymousDemo {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
//run方法中编写多线程需要执行的代码
@Override
public void run() {
for(int i=0; i<5; i++) {
System.out.println("childern:"+i);
}
}
});
//启动线程
thread.start();
for(int i=0; i<5; i++) {
System.out.println("main:"+i);
}
}
}
运行结果:
- Callable类,相较于实现 Runnable 接口的方式,区别在于Callable有返回值并且可以抛出异常。
public class CallableDemo {
//相较于实现 Runnable 接口的方式,区别在于Callable有返回值并且可以抛出异常。
static class SumTask implements Callable<Long> {
@Override
public Long call() throws Exception {
long sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
System.out.println("childern: "+i);
}
System.out.println("sum: "+ sum);
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Long> futureTask = new FutureTask<Long>(new SumTask());
//执行线程方式一:
/*Executor executor=Executors.newSingleThreadExecutor();
executor.execute(futureTask);*/
//执行线程执行线程方式二:
new Thread(futureTask).start();
for (int i = 0; i < 5; i++) {
System.out.println("main: "+i);
}
System.out.println("返回值: "+futureTask.get());
}
}
运行结果:
Callable常用api
boolean cancel(boolean mayInterruptIfRunning) | 如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。参数:mayInterruptIfRunning - 如果应该中断执行此任务的线程,则为 true;否则允许正在运行的任务运行完成。返回值:如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true |
---|---|
boolean isCancelled() | 返回值:如果在任务正常完成前将其取消,则返回 true。 |
boolean isDone() | 返回值:如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。 |
V get() | 返回值:如有必要,等待计算完成,然后获取其结果。 |
V get(long timeout,TimeUnit unit) | 返回值:如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。 |
代码演示:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class CallableDemo {
//Callable和Runnbale一样代表着任务,区别在于Callable有返回值并且可以抛出异常。
static class SumTask implements Callable<Long> {
@Override
public Long call() throws Exception {
long sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
System.out.println("childern: "+i);
}
System.out.println("sum: "+ sum);
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("Start:" + System.nanoTime());
//执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Long> futureTask = new FutureTask<Long>(new SumTask());
//执行线程方式一:
Executor executor=Executors.newSingleThreadExecutor();
executor.execute(futureTask);
//执行线程执行线程方式二:
//new Thread(futureTask).start();
//Thread.sleep(10);
//取消线程,如果无法取消任务,则返回 false,这通常是由于它已经正常完成;否则返回 true
//futureTask.cancel(true);
try {
//取得返回值
System.out.println("返回值: "+futureTask.get());
// 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
System.out.println("返回值: "+futureTask.get(1, TimeUnit.SECONDS));
} catch (Exception e) {
e.printStackTrace();
}
//如果在任务正常完成前将其取消,则返回 true。
System.out.println("是否被取消: "+futureTask.isCancelled());
//如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
System.out.println("任务是否完成: "+futureTask.isDone());
System.out.println("End:" + System.nanoTime());
}
}
运行结果:
- 使用线程池创建线程(企业中常用)(见后续)
- 常用线程api
start() | start() 启动线程 |
---|---|
currentThread() | 获取当前线程对象 |
getId() | 获取当前线程ID Thread-编号 该编号从0开始 |
getName() | 获取当前线程名称 |
sleep(long mill) | 休眠线程 |
Stop() | 停止线程,不安全,不推荐使用 |
start() | start() 启动线程 |
---|---|
Thread() | 分配一个新的 Thread 对象 |
Thread(String name) | 分配一个新的 Thread对象,具有指定的 name正如其名。 |
Thread(Runable r) | 分配一个新的 Thread 对象 |
Thread(Runable r, String name) | 分配一个新的 Thread对象,具有指定的 name正如其名。 |
代码演示:
public class RunnableDemo {
public static void main(String[] args) {
//调用线程
Thread02 t = new Thread02();
Thread thread = new Thread(t);
//启动线程
thread.start();
for(int i=0; i<5; i++) {
System.out.println("main:"+i+"; 线程id: "+Thread.currentThread().getId()+" ;线程name: "+Thread.currentThread().getName());
}
}
}
class Thread02 implements Runnable{
@Override
public void run() {
for(int i=0; i<5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("childern:"+i+"; 线程id: "+Thread.currentThread().getId()+" ;线程name: "+Thread.currentThread().getName());
}
}
}
运行结果:
三. 守护线程与非守护线程
Java分为两种线程:非守护线程和守护线程
(1)非守护线程(又称用户线程):用户自己创建的线程,如果主线程停止掉,不会影响到到用户线程,和主线程互不影响。
(2)守护线程:指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程。守护线程会和主线程一起销毁。
代码演示:
public class ThreadDemo{
public static void main(String[] args) {
//调用线程
Thread01 thread01 = new Thread01();
//设置该线程为守护线程,与主线程一起销毁
thread01.setDaemon(true);
//启动线程
thread01.start();
for(int i=0; i<5; i++) {
System.out.println("main:"+i);
}
System.out.println("主线程执行完毕~~~~~");
}
}
class Thread01 extends Thread{
@Override
public void run() {
for(int i=0; i<5; i++) {
System.out.println("childern:"+i);
}
}
}
thread01为用户线程时,运行结果:
thread01为守护线程时,运行结果:
四. 线程的五种状态
线程的五种状态:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
(1)新建状态(New):
当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码。
(2)就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
(3)运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
(4)阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
…
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
(5)死亡状态(Dead)
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
五. join、yield和priority方法
- join()的作用:是让其他线程变为等待,直到当前线程执行完毕,才释放。
- yield()的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
- priority():来控制优先级,范围为1-10,其中10最高,默认值为5。
代码演示:
public class ThreadJoinDemo {
//thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
//比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0; i<50; i++) {
System.out.println("t1: "+ i);
}
}
});
// 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配
t1.setPriority(10);
t1.start();
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();// 让其他线程(t2)变为等待,直到当前t1线程执行完毕,才释放。
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<50; i++) {
System.out.println("t2: "+ i);
}
}
});
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<50; i++) {
System.out.println("t3: "+ i);
}
}
});
t3.start();
}
}