什么是线程?
从OS角度看,线程是调度的基本单位
从应用开发者的角度看,线程是一个分担任务的角色
为什么要引入线程?
因为进程的频繁创建,销毁,本身就比较消耗资源。因此引入线程,创建线程,并没有申请资源,销毁线程,也没有去申请资源。线程是在进程内部创建的,共用之前的资源。
从操作系统的角度看线程和进程
在操作系统内核的角度,不分“线程还是进程”,只认PCB。
当创建一个进程出来,其实就是创建了一个PCB出来,同时这个PCB也可以视为当前进程中包含了一个线程了(一个进程中至少得有一个线程)。
进程和线程之间的区别和联系【经典面试题】
-
进程是包含线程的,一个进程可以有一个线程,也可以有多个线程。
-
每个进程都有独立的内存空间(虚拟地址空间),同一个进程的多个线程之间,共用这个虚拟地址空间。
-
进程是操作系统分配资源的基本单位,线程是操作系统调度执行的基本单位(操作系统调度的最小任务单位是线程)。
线程和代码的关系
一个线程就是代码的一个执行流(按照一定的顺序,来执行一组指令)
使用Java来操作线程,在Java中,使用Thread这个类的对象来表示一个操作系统中的线程。
PCB是在操作系统内核中描述线程
Thread来是在Java代码中描述线程
Thread类中start和run的区别
start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行
在直接调用run方法的时候,程序并不会创建新线程,run方法内部的代码将在当前线程上运行。
从调用次数来看:start()不能被重复调用,run()可以
start()中的run代码可以不执行完就继续执行下面的代码,即进行了线程切换。直接调用run方法必须等待其代码全部执行完才能继续执行下面的代码。
线程创建的五种方式
- 通过继承Thread类,重写run方法
//Thread是Java标准库中描述的一个关于线程的类
//常用的方式就是自己定义一个类继承Thread
//重写Thread中的run方法,run方法就表示线程要执行的具体任务(代码)
class MyThread extends Thread{
@Override
public void run() {
//super.run();
System.out.println("创建线程,开始执行");
}
}
public class Thread_TestDemo1 {
public static void main(String[] args) {
Thread t = new MyThread();
//start方法,就会在操作系统中真的创建一个线程出来(内核中搞个PCB,加入到双向链表中)
//这个新的线程,就会执行run中所描述的代码
t.start();
}
}
- 创建Thread实例,传入一个Runnable实例。实现Runnable接口,重写run方法。
class MyRunable implements Runnable{
@Override
public void run() {
while (true){
try {
System.out.println("hello thread");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunable());
t.start();
}
}
- 继承Thread,重写run,使用匿名内部类的方式
public class ThreadDemo5 {
public static void main(String[] args) {
//匿名内部类
//相当于是创建了一个匿名的类,这个类继承了Thread
//此处咱们new的实例,其实是new了这个新的子类的实例
Thread t = new Thread(){
@Override
public void run() {
while(true){
try {
System.out.println("hello thread");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
}
- 实现Runnable,重写run,使用匿名内部类
package java100_0926;
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
System.out.println("hello thread");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
}
}
- 使用lambda表达式来表示要执行的任务
package java100_0926;
public class ThreadDemo7 {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (true){
try {
System.out.println("hello thread");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
lambda是直接执行任务,不需要重写run方法。
以上这些创建线程的方式,本质都相同。都是借助Thread类,在内核中创建新的PCB,加入到内核的双向链表中。
区别是,指定线程要执行的任务的方式不一样,此处的区别,其实都只是单纯的java语法层面的区别。
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("1");
}
});
t.start();
System.out.println("2");
}
本质上main线程和 t 子线程是同时并发并行的执行,打印的顺序是随机的。
但子线程需要申请系统运行及时间片轮转调度,而main线程一直处于运行态,从概率上看main先执行的几率非常大。所以是2,1
可视化查看新创建的线程的方法
使用在JDK中内置的jconsole工具
Thread类的具体用法(类中的属性和方法)
通过构造方法给线程命名
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
System.out.println("MyThread");
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"myThread");
t.start();
属性 | 获取方法 | 备注 |
---|---|---|
ID | getId() | |
名称 | getName() | 给线程命名,方便调试 |
状态 | getState() | 存在的意义是辅助进行线程调度 |
优先级 | getPriority() | 可以对线程设定优先级,设定优先级的方法是:Thread.setPriority(int n) // 1~10, 默认值5。优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。 |
是否后台线程 | isDaemon() | 创建的一个线程,默认不是后台线程,是否是后台线程,影响了JVM进行是否能够退出 |
是否存活 | isAlive() | |
是否被中断 | isInterrupted() | 判断内核中的PCB是否被销毁 |
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"myThread");
t.start();
System.out.println(t.getId());
System.out.println(t.getName());
System.out.println(t.getPriority());
System.out.println(t.getState());
System.out.println(t.isAlive());
System.out.println(t.isDaemon());
System.out.println(t.isInterrupted());
}
线程中断-interurpt()
为了实现能够控制线程,按照需要随时结束,实际开发中,有两种办法
- 简单粗暴的办法,使用一个boolean变量来作为循环结束标记
- 使用Thread.interrupted()或者Thread.currentThread().isInterrupted()代替自定义标志位。
获取线程内置的标记位:线程的isInterrupted()判定当前线程是不是应该要结束循环
修改线程内置的标记位:Thread.interrupt()来修改这个标记位
public static void main(String[] args){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println("Thread run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
// 在这里加上break保证循环能够结束
break;
}
}
}
},"MyThread");
t.start();
// 留出三秒时间给 t 线程执行 方便观察效果
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
这里的interrupt方法可能有两种行为:
- 如果当前线程正在运行,此时就会修改Thread.currentThread().isInterurpted()标记位true
- 如果当前线程正在sleep/wait/等待锁 此时会触发InterruptedException
isInterrupted() 这个是Thread的实例
interrupted()这个是Thread的类方法
使用这两者的区别(后续标记位的不同)
- 当调用静态的interrupted()来判定标记位的时候,就会返回true,同时就会把标记位再改回false。下次再调用interrupted()就会返回false
- 如果是调用非静态的isInterrupted()来判断标记位,也会返回ture。同时不会对标记位就行修改,后面在调用isInterrupted()的时候就仍然返回true
线程等待-join()
通过对另一个线程对象调用join()方法可以等待其执行结束;
当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。但是,有的时候我们希望线程执行的顺序是可控的。线程等待就是一种办法。
死等线程 t 运行结束再执行主线程
public static void main(String[] args){
Thread t = new Thread(new Runnable() {
int count = 0;
@Override
public void run() {
while (count<10000000){
count++;
System.out.println("thread run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"MyThread");
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束");
}
获取当前线程引用
Thread t = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getId());
System.out.println(this.getId());
}
};
t.start();
this和Thread.currentThread没有区别的前提是使用继承Thread,重写run的方式创建线程。
如果当前是通过Runnable或者lambda的方式,this就不可以。
Java线程对象Thread类的状态包括
- New:新创建的线程,尚未执行;
- Runnable:运行中的线程,正在执行run()方法的代码;
- Blocked:运行中的线程,因为某些操作被阻塞而挂起
- Waiting:运行的线程,因为某些操作在等待中;
- Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
- Terminated:线程已终止,因为run()方法执行完毕;