引自:https://www.runoob.com/java/java-multithreading.html
引自:https://blog.csdn.net/boker_han/article/details/82918881#Runnable_39
线程:指的是进程中一个个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程是jvm调度的最小单元,也叫做轻量级进程,进程是由线程组成,线程拥有私有的程序计数器以及栈,并且能够访问堆中的共享资源。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
为什么需要多线程:
1)随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,而同一个时刻一个线程只能运行在一个cpu上,那么计算机的资源被浪费了,所以需要使用多线程。
2)也是为了提高系统的响应速度,如果系统只有一个线程可以执行,那么当不同用户有不同的请求时,由于上一个请求没处理完,那么其他的用户必定需要在一个队列中等待,大大降低响应速度,所以需要多线程。
一个线程的生命周期
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
-
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
-
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
-
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态
线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
线程的三种创建方式
1.继承Thread类
1)创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
2)继承类必须重写 run() 方法,该方法是新线程的入口点。
3)也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
例1:
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); }
public void start () {
System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo( "Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo( "Thread-2"); T2.start(); } }
例2:
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 } 12 13 14 public class ThreadTest { 15 16 public static void main(String[] args) { 17 for (int i = 0; i < 100; i++) { 18 System.out.println(Thread.currentThread().getName() + " " + i); 19 if (i == 30) { 20 Thread myThread1 = new MyThread(); // 创建一个新的线程 myThread1 此线程进入新建状态 21 Thread myThread2 = new MyThread(); // 创建一个新的线程 myThread2 此线程进入新建状态 22 myThread1.start(); // 调用start()方法使得线程进入就绪状态 23 myThread2.start(); // 调用start()方法使得线程进入就绪状态 24 } 25 } 26 } 27 }
2.实现Runnable接口
优点:
- 扩展性好,可以在此基础上继承其他类,实现其他必需的功能
- 对于多线程共享资源的场景,具有天然的支持,适用于多线程处理一份资源的场景
缺点:构造线程实例的过程相对繁琐一点
例1:
1 class RunnableDemo implements Runnable { 2 private Thread t; 3 private String threadName; 4 5 RunnableDemo( String name) { 6 threadName = name; 7 System.out.println("Creating " + threadName ); 8 } 9 10 public void run() { 11 System.out.println("Running " + threadName ); 12 try { 13 for(int i = 4; i > 0; i--) { 14 System.out.println("Thread: " + threadName + ", " + i); 15 // 让线程睡眠一会 16 Thread.sleep(50); 17 } 18 }catch (InterruptedException e) { 19 System.out.println("Thread " + threadName + " interrupted."); 20 } 21 System.out.println("Thread " + threadName + " exiting."); 22 } 23 24 public void start () { 25 System.out.println("Starting " + threadName ); 26 if (t == null) { 27 t = new Thread (this, threadName); 28 t.start (); 29 } 30 } 31 } 32 33 public class TestThread { 34 35 public static void main(String args[]) { 36 RunnableDemo R1 = new RunnableDemo( "Thread-1"); 37 R1.start(); 38 39 RunnableDemo R2 = new RunnableDemo( "Thread-2"); 40 R2.start(); 41 } 42 }
例2:
1 package com.thread; 2 public class MyRunnable implements Runnable { 3 //线程体 4 @Override 5 public void run() { 6 System.out.println("Hello, I am the defined thread created by implements Runnable"); 7 } 8 public static void main(String[] args){ 9 //线程的执行目标对象 10 MyRunnable myRunnable = new MyRunnable(); 11 //实际的线程对象 12 Thread thread = new Thread(myRunnable); 13 //启动线程 14 thread.start(); 15 } 16 }
3.实现Callable接口
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
优点:
- 扩展性好
- 支持多线程处理同一份资源
- 具备返回值以及可以抛出受检查异常
缺点:
- 相较于实现Runnable接口的方式,较为繁琐
例1:
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); if(i==20) { new Thread(ft,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return i; } }
例2:
package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "Hello, I am the defined thread created by implements Callable"; } public static void main(String[] args){ //线程执行目标 MyCallable myCallable = new MyCallable(); //包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口 FutureTask<String> futureTask = new FutureTask<>(myCallable); //传入线程执行目标,实例化线程对象 Thread thread = new Thread(futureTask); //启动线程 thread.start(); String result = null; try { //获取线程执行结果 result = futureTask.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(result); } }
创建线程的三种方式的对比
- 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
在多线程编程时,需要了解以下几个概念:
- 线程同步
- 线程间通信
- 线程死锁
- 线程控制:挂起、停止和恢复