目录
进程和线程
程序
进程
线程
进程和线程的区别
Java中描述线程这个对象的类——java.lang.Thread类
1. 创建线程的方法
a. 继承Thread类,覆写run方法
b. 实现Runnable接口,覆写run方法
方式a 和方式b 的不同写法
c. 实现Callable接口
多线程和顺序执行的执行速度差异
2. Thread 类及常见方法
2.1 Thread类常见方法
2.2 Thread 的核心属性
2.3 线程属性示例
2.4 中断一个线程
中断线程有两种方式
2.5 等待一个线程——join()
2.6 获取当前线程对象的引用——currentThread()
2.7 休眠当前线程——sleep()
3. 线程的状态
3.1 观察线程的所有状态
3.2 线程状态和状态转移的意义
观察 1:关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
观察 2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
观察 3:yield()方法——从运行状态转为就绪状态
程序:一系列有组织的文件,封装操作系统的各种API,实现不同的效果。
进程:程序在系统中的一次执行过程。
进程是现代操作系统中资源分配(CPU,内存等关键系统资源)的最小单位。不同进程之间是相互独立的。
IP地址:唯一在网络上标识一台主机的位置,就相当于我们收发快递的地址。
127.0.0.1——>本机的IP
MySQL服务端启动后,进程端口号就是3306
Http:80端口号
Https:443端口号可以通过IP地址找到主机,那么如何定位主机的某个进程?——端口号
端口号在操作系统上能唯一的定位一个进程,进程的端口号一般不变。
进程的PCB(操作系统描述本次进程的信息结构)PID,每次启动进程都会改变。
线程:就是进程中的子任务。同一个进程的所有线程共享该进程的资源,线程是操作系统任务(系统调度)执行的基本单位。
买CPU都会告诉你几核心几线程
例如:4核8线程
指的是当前电脑的CPU有4个核心(4个独立的芯片),每个核心上能并行处理2个线程
同时能并行执行8个线程。打开浏览器——>打开的是一个进程
线程就是进程中的一个独立任务,在浏览器上可以同时下载视频、同时听音乐,下载任务是一个线程,听音乐就是另一个线程。
1. 进程是os资源分配的基本单位,线程是os系统调度的基本单位。
2. 创建和销毁进程的开销要远比创建和销毁线程大得多(创建和销毁一个进程的时间要比创建和销毁一个线程大的多),线程更加轻量化。
例如:启动浏览器的速度(进程)慢于启动浏览器的标签(线程)的速度。
3. 调度一个线程也远比调度一个进程快的多。4. 进程包含线程,每个进程至少包含一个线程(主线程)。
main方法就是一个主线程,java命令 + 主类名称 就是启动Java进程
5. 进程之间彼此相对独立,不同的进程不会共享内存空间;同一个进程的线程共享内存空间。
JDK提供的线程库实际上就是利用操作系统提供的线程库进行二次封装
线程的核心类,都是通过Thread类来启动一个新的线程。
import java.util.Random; /** * 第一个多线程示例代码 */ public class FirstThreadDemo { private static class MyThread extends Thread { // run方法就是线程的核心工作方法,线程要干的所有事情都在run方法中进行定义 @Override public void run() { Random random = new Random(); while (true) { // 打印当前线程名称 System.out.println(Thread.currentThread().getName()); // 当前线程随机暂停0-9秒 try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public static void main(String[] args) { // 创建三个线程对象 MyThread m1 = new MyThread(); MyThread m2 = new MyThread(); MyThread m3 = new MyThread(); //启动三个子线程 m1.start(); m2.start(); m3.start(); Random random = new Random(); while (true) { // 打印当前线程名称 System.out.println(Thread.currentThread().getName()); // 当前线程随机暂停0-9秒 try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
1. run方法是线程类的工作任务,由JVM来执行。
run方法决定了线程启动之后的核心执行流程方法,每个线程都是从run方法开始执行代码,当run方法执行完毕,线程就进入销毁状态
2. start方法是Thread类中启动线程的方法
只有当线程对象调用start方法之后才会被系统调度,进入运行状态。线程启动之后会由JVM自动执行每个线程的run方法。
问题:若这四个线程是顺序执行的,主方法会不会打印输出?其他线程的打印呢?
答:①顺序执行时,3个子线程run方法执行之后才执行主线程。
②只有m1子线程在打印
当线程已经启动之后,再次调用该线程的start方法就会抛出此异常,说明该线程已经启动过了。
JDK和JRE相比最大的区别,提供了很多开发程序会用到的辅助工具
javac ——> javac.exe
java ——> java.exe
jconsole命令查看当前运行的JVM内部的线程情况——>jconsole.exe
创建线程:Java中创建一个线程一共有四种方式
a. 继承Thread类,覆写run方法(线程的核心工作任务方法)
b. 实现Runnable接口,覆写run方法
c. 覆写Callable接口,覆写call方法
d. 使用线程池创建线程
a、b最终启动线程都是通过Thread类的start方法启动线程
Ⅰ. 一个子类继承Thread类
Ⅱ. 覆写run方法
Ⅲ. 产生当前这个子类对象,而后调用start方法启动线程public class Main { public static void main(String[] args) { //创建线程类对象 ThreadMethod mt = new ThreadMethod(); mt.start(); System.out.println("主线程的输出语句"); } }
public class ThreadMethod extends Thread{ @Override public void run() { System.out.println("子线程的输出结果"); } }
以上产生两种不同输出结果
调用start方法启动线程,是由JVM产生操作系统的线程并启动,到底什么时候真正启动,对于我们来说不可见的,也没法控制。
Ⅰ. 一个子类继承Runnable类
Ⅱ. 覆写run方法
Ⅲ. 产生当前Thread子类对象,而后调用start方法启动线程/** * 这个实现了Runnable接口的子类,并不是直接的线程对象,只是一个线程的核心工作任务。 * 线程的任务和线程实体的关系 */ public class RunnableMethod implements Runnable { @Override public void run() { System.out.println("Runnable方式实现的子线程任务"); } }
public class Main { public static void main(String[] args) throws InterruptedException { // 创建线程的任务对象 RunnableMethod runnableMethod = new RunnableMethod(); // 创建线程对象,将任务对象传入线程对象 Thread thread = new ThreadMethod(runnableMethod); // 启动线程 thread.start(); System.out.println("主线程的输出语句"); } }
这个实现了Runnable接口的子类,并不是直接的线程对象,只是一个线程的核心工作任务。
推荐使用方式b,实现Runnable接口更加灵活,子类还能实现别的接口,继承别的类。
方式a只能继承Thread,单继承局限。
匿名内部类继承了Thread类,然后实现run方法
public class OtherMethod { public static void main(String[] args) { // 匿名内部类继承Thread类 Thread t1 = new Thread() { @Override public void run() { System.out.println("匿名内部类继承Thread类"); System.out.println(Thread.currentThread().getName()); } }; t1.start(); System.out.println("这是主线程" + Thread.currentThread().getName()); } }
匿名内部类实现Runnable接口
public class OtherMethod { public static void main(String[] args) { // Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("匿名内部类继承Runnable接口"); System.out.println(Thread.currentThread().getName()); } }); thread.start(); System.out.println("这是主线程" + Thread.currentThread().getName()); } }
Lambda表达式实现Runnable接口
public static void main(String[] args) { // Lambda表达式实现Runnable接口 Thread t1 = new Thread(()-> System.out.println("使用Lambda表达式实现Runnable接口")); t1.start(); System.out.println("这是主线程" + Thread.currentThread().getName()); }
带返回值的接口,覆写call方法(线程的核心工作任务方法,有返回值)
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * Callable接口的使用 */ public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable
callable = new Callable () { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 1000; i++) { sum += i; } return sum; } }; // 接收call方法的返回值使用FutureTask对象 FutureTask futureTask = new FutureTask<>(callable); // Thread类接收Callable接口必须通过FutureTask类 Thread t = new Thread(futureTask); t.start(); // get方法会阻塞当前线程,直到call方法执行完毕,才恢复执行 int result = futureTask.get(); System.out.println("子线程执行结束,result = " + result); } }
import java.util.concurrent.*; public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable
callable = new Callable () { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 1000; i++) { sum += i; } return sum; } }; ExecutorService pool = Executors.newFixedThreadPool(3); // FutureTask就是Future接口的子类,Future接口就是搭配Callable接口使用的,来接收Callable的返回值 Future callResult = pool.submit(callable); int result = callResult.get(); System.out.println("子线程执行结束,result = " + result); } }
执行10个亿量级数字的连续累加
顺序执行,先+10亿,再+10亿,最终都是在主线程中+20亿。
并发执行,子线程+10亿,主线程+10亿,主线程和了线程并发的在执行+10亿理论来讲,并发执行的速度是顺序执行的一倍,耗时是一半。
/** * 串行和并发的时间对比 */ public class ThreadNB { private static final long COUNT = 10_0000_0000; public static void main(String[] args) throws InterruptedException { serial(); concurrent(); } // 串行进行20亿的累加 private static void serial() { long start = System.nanoTime(); long a = 0; for (long i = 0; i < COUNT; i++) { a++; } // 主线程中也执行 +10亿操作 // b的执行需要等待a走完才能进行 long b = 0; for (long i = 0; i < COUNT; i++) { b++; } long end = System.nanoTime(); double allTime = (end - start) * 1.0 / 1000 / 1000; System.out.println("顺序执行共耗时" + allTime + "ms"); } // 并发执行 +10亿操作 private static void concurrent() throws InterruptedException { long start = System.nanoTime(); // 子线程进行 + 10亿操作 Thread thread = new Thread(() -> { long a = 0; for (long i = 0; i < COUNT; i++) { a++; } }); thread.start(); // 主线程中也执行 +10亿操作 long b = 0; for (long i = 0; i < COUNT; i++) { b++; } // 等待子线程执行结束,主线程和子线程的加法操作都完成 // 等待子线程thread执行结束才能执行下面代码 thread.join(); long end = System.nanoTime(); double allTime = (end - start) * 1.0 / 1000 / 1000; System.out.println("并发执行共耗时" + allTime + "ms"); } }
多线程最大的应用场景就是把一个大任务拆分为多个子任务(交给子线程),多个子线程并发执行,提高系统的处理效率。
例如:12306系统是多线程程序
我们每个人其实都是一个线程,我们多个人可以同时登录系统买票,付款操作是一个非常耗时的操作。
若不是多线程,每个人买票都得排队,从第一个登录系统的人开始买票,依次进行,非常慢,这个程序就没法用。
无论是继承Thread类还是实现Runnable接口,最终启动线程调用的都是Thread类的start方法。Thread类就是JVM描述管理线程的类,每个线程都对应唯一的一个Thread对象1. 构造方法
public static void main(String[] args) { // 一般搭配子类使用,需要有一个继承了Thread类的子类 Thread t1 = new Thread(); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("传入Runnable对象"); } }); Thread t3 = new Thread("3号线程"); Thread t4 = new Thread(new Runnable() { @Override public void run() { System.out.println("666"); } },"4号线程"); }
1. ID 是线程的唯一标识,不同线程不会重复。每个线程都有一个独立的id。
2. 名称是各种调试工具用到的。
3. 状态表示线程当前所处的一个情况。
4. 优先级高的线程,理论上来说更容易被调度到。优先级越高的线程越有可能被CPU优先执行,Java程序只是建议优先级高的线程优先执行,到底执行不执行,OS说了算。
5. 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
6. 是否存活,即简单的理解为 run 方法是否运行结束了。7. 线程的中断问题,下面我们进一步说明
/** * 线程常用属性 */ public class ThreadAttr { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程存活"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + "线程即将消逝"); } }, "测试线程"); // -----------------------------------------------------------------以下为主线程 System.out.println(Thread.currentThread().getName() + ":ID" + thread.getId()); System.out.println("状态:" + Thread.currentThread().getState()); System.out.println("优先级:" + Thread.currentThread().getPriority()); System.out.println("是否为后台线程 ?" + Thread.currentThread().isDaemon()); System.out.println("主线程是否存活 ?" + Thread.currentThread().isAlive()); System.out.println("子线程是否存活 ?" + thread.isAlive()); thread.start(); } }
中断线程,线程间通信的一种方式
中断一个正在执行的线程(run方法还没有执行结束);普通线程会在run方法执行结束之后自动停止。
a. 通过共享变量进行中断
设置转账场景
/** * 通过共享变量中断线程 */ public class ThreadInterruptByVar { private static class MyThread implements Runnable { // 多个线程都会用到的变量加上volatile关键字 // 表示当前线程是否需要停止 volatile boolean isQuit = false; @Override public void run() { while (!isQuit) { System.out.println(Thread.currentThread().getName() + "正在转账..."); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + "转账中断"); } } public static void main(String[] args) throws InterruptedException { // 创建线程的任务对象 MyThread mt = new MyThread(); // 创建线程对象,将任务对象传入线程对象 Thread thread = new Thread(mt, "转账线程"); System.out.println("可以开始转账了"); thread.start(); // 主线程暂停3s,暂停thread Thread.sleep(3000); System.out.println("转账完毕,可以停止"); mt.isQuit = true; } }
注意
Thread类的所有静态方法,在哪个线程中调用的,就生效在哪个线程。
b. 使用Thread.interrupted()静态方法 或 Thread对象的成员方法isInterrupted()
Thread类的内部包含了一个属性,当前线程是否被中断的属性。Thread.interrupted()静态方法
/** * 通过内置的属性中断线程 */ public class ThreadInterruptedByMethod { private static class MyRunnable implements Runnable { @Override public void run() { // 静态方法,判断当前线程是否被中断 while (!Thread.interrupted()) { System.out.println(Thread.currentThread().getName() + "正在转账"); try { Thread.sleep(1000); } catch (InterruptedException e) { // 当线程被中断时,会抛出中断异常 // 抛出中断异常之后,中断状态就会还原! System.err.println("转账失败"); break; } } System.out.println(Thread.currentThread().getName() + "系统故障"); } } public static void main(String[] args) throws InterruptedException { MyRunnable mt = new MyRunnable(); Thread thread = new Thread(mt, "转账线程"); System.out.println("准备转账"); thread.start(); Thread.sleep(5 * 1000); // 中断子线程 // 调用此方法就会将子线程的状态置为中断状态 thread.interrupt(); } }
Thread对象的成员方法isInterrupted()
package thread; /** * 通过内置的属性中断线程 */ public class ThreadInterruptedByMethod { private static class MyRunnable implements Runnable { @Override public void run() { // 成员方法判断当前线程是否被中断 while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "正在转账"); try { Thread.sleep(1000); } catch (InterruptedException e) { // 当线程被中断时,会抛出中断异常 // 抛出中断异常之后,中断状态就会还原! System.err.println("转账失败"); break; //此处捕获异常后,会跳出循环 } } System.out.println(Thread.currentThread().getName() + "系统故障"); } } public static void main(String[] args) throws InterruptedException { MyRunnable mt = new MyRunnable(); Thread thread = new Thread(mt, "转账线程"); System.out.println("准备转账"); thread.start(); Thread.sleep(5 * 1000); // 中断子线程 // 调用此方法就会将子线程的状态置为中断状态 thread.interrupt(); } }
线程收到内置的中断通知有两种方式:
a. 当线程调用sleep / wait / join等方法处在阻塞状态时,收到中断通知thread.interrupt()就会抛出一个中断异常InterruptedException,当抛出异常后,当前线程的中断状态会被清除。
类似一个按下去会弹起的开关,按下则关,弹起则开。
/** * 通过内置的属性中断线程 */ public class ThreadInterruptedByMethod { private static class MyRunnable implements Runnable { @Override public void run() { // 静态方法,判断当前线程是否被中断 // while (!Thread.interrupted()) { // 成员方法,判断当前线程对象是否被中断了 while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "正在转账"); try { Thread.sleep(1000); } catch (InterruptedException e) { // 当线程被中断时,会抛出中断异常 // 抛出中断异常之后,中断状态就会还原! System.err.println("转账失败"); // break; } } System.out.println(Thread.currentThread().getName() + "系统故障"); } } public static void main(String[] args) throws InterruptedException { MyRunnable mt = new MyRunnable(); Thread thread = new Thread(mt, "转账线程"); System.out.println("准备转账"); thread.start(); Thread.sleep(5 * 1000); // 中断子线程 // 调用此方法就会将子线程的状态置为中断状态 thread.interrupt(); } }
b. 线程没有调用以上三种方法时,处在正常运行状态,收到中断通知thread.interrupt()
Thread.interrupted():判断当前线程是否被中断,若中断状态为true,清除中断标志。
/** * 两种中断方法的区别 */ public class ThreadDoubleMethod { private static class MyRun implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.interrupted()); } } } public static void main(String[] args) { MyRun myRun = new MyRun(); Thread thread = new Thread(myRun); thread.start(); thread.interrupt(); } }
Thread.currentThread().isInterrupted():判断指定线程对象是否状态为中断状态,若状态为ture,不会清除中断标志。
类似按下去不会弹起的开关。
/** * 两种中断方法的区别 */ public class ThreadDoubleMethod { private static class MyRun implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().isInterrupted()); } } } public static void main(String[] args) { MyRun myRun = new MyRun(); Thread thread = new Thread(myRun); thread.start(); thread.interrupt(); } }
中断则状态一直为true
eg:主线程中调用thread1.join(),主线程就会进入阻塞状态,直到thread1执行结束,主线程才会继续向后执行
/** * 线程等待 join方法-成员方法 */ public class ThreadJoin { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "还在学习JavaSE部分"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "JavaSE线程"); Thread t2 = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "进入数据结构的学习部分"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "数据结构线程"); System.out.println("先学习JavaSE"); t1.start(); // 主线程死等t1,直到t1执行结束主线程再恢复执行 t1.join(); // 此时走到这里,t1线程已经执行结束,再启动t2线程 t2.start(); // 在哪调用别的线程的join方法,阻塞的是调用join的线程 // main -> 调用t2.join() 阻塞主线程,直到t2完全执行结束再恢复主线程的执行 // 主线程只等t2 2000ms - 2s,若t2在2s之内还没结束,主线程就会恢复执行 t2.join(2000); // t2线程也执行结束了,继续执行主线程 System.out.println("开始学习JavaEE部分"); System.out.println(Thread.currentThread().getName()); } }
/** * 获取当前线程对象 */ public class ThreadCurrent { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()); } }
Thread.sleep(long millis):在哪调用,就休眠哪个线程
/** * 线程状态 */ public class ThreadState { public static void main(String[] args) { for (Thread.State state: Thread.State.values()) { System.out.println(state); } } }
NEW
新建线程对象就是new状态
/** * 线程的新建,运行和终止状态 */ public class NewAndRunnableState { public static void main(String[] args) { // 产生一个线程对象,该对象默认的状态就是新建状态 - NEW Thread t = new Thread(() -> { for (int i = 0; i < 1000; i++) { } }, "子线程"); System.out.println(t.getName() + " : " + t.getState()); t.start(); while (t.isAlive()) { System.out.println(t.getName() + " : " + t.getState()); } System.out.println(t.getName() + " : " + t.getState()); } }
使用 isAlive 方法判定线程的存活状态。
RUNNABLE
就绪和运行都是runnable状态
TERMINATED
线程的run方法执行完毕,或者抛出异常、不正常执行完毕都会进入终止状态
三种都属于线程的阻塞状态(该线程需要暂缓执行,这三个造成的暂缓执行的原因不同)
线程间等待与唤醒机制。wait和notify是Object类的方法,用于线程的等待与唤醒,必须搭配synchronized锁来使用。
WAITING
等待被另一个线程唤醒(notify方法)
/** * 三种阻塞状态 */ public class BlockedState { public static void main(String[] args) { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { while (true) { try { // WAITING 线程等待 lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { // BLOCKED 等待获取锁对象 System.out.println("waiting......"); } }, "t2"); t2.start(); } }
notify方法,唤醒线程
/** * 三种阻塞状态 */ public class BlockedState { public static void main(String[] args) { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { // WAITING 线程等待 lock.wait(); System.out.println("被唤醒"); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { // BLOCKED 等待获取锁对象 System.out.println("waiting......"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } lock.notify(); } }, "t2"); t2.start(); } }
TIMED_WAITING
超时等待,需要等待一段时间后自动唤醒
BLOCKED
锁等待,需要等待其他线程释放锁对象
/** * 三种阻塞状态 */ public class BlockedState { public static void main(String[] args) { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { while (true) { try { // TIMED_WAITING 等待时间到了自动唤醒 Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { // BLOCKED 等待获取锁对象 System.out.println("waiting......"); } }, "t2"); t2.start(); } }
超时等待,该线程需要等待一段时间之后再恢复执行
该线程在等待别的线程释放资源
/** * yield方法 */ public class YieldTest { public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { System.out.println(Thread.currentThread().getName()); // 1号线程就会让出CPU,进入就绪态,等待被CPU继续调度 Thread.yield(); } }, "1号线程"); t1.start(); Thread t2 = new Thread(() -> { while (true) { System.out.println(Thread.currentThread().getName()); } }, "2号线程"); t2.start(); } }
调用yield方法的线程会主动让出CPU资源,从运行态转为就绪态,等待被CPU继续调度。
什么时候让出CPU,又是什么时候被CPU再次调度,都是os调度的,我们无权选择:
a. 让出之后立马又被调度了yield和sleep都会导致线程让出CPU,当线程再次调度回CPU时,有可能会重新读主存。
JVM规范明确表示,yield和sleep方法不一定会强制刷新工作内存,读取主存。