1.什么是进程?什么是线程
进程相对于exe程序来说他是一个动态的概念,可以说是运行中的程序。进程是资源(CPU、内存等)分配的基本单位
线程可以理解为进程中独立运行的子任务。是程序执行时的最小单位
例如:qq.exe程序运行时很多的子任务也同时在运行,比如添加好友,视频,下载文件,听音乐
进程福州想操作系统申请资源。在一个进程中,多个线程可以共享进程中相同的内存或者文件资源
我们可以从jvm的角度来剖析:
可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
2.多线程的优势
使用多线程就是在使用异步 可以在同一时间执行多个任务
3.什么情况下使用多线程
1).阻塞。一旦系统中出现了阻塞现象,则可以根据实际情况来使用多线程技术提高运行效率
2).依赖。业务分为2个执行过程,分别为A和B。当A业务发生阻塞情况时,B业务的执行不依赖A业务的执行结果
4.如何创建线程
在学习如何创建线程前,先看一下thread类的声明结构:
public class Thread implements Runable
由于java关于继承的特点----不支持多继承,所以为了更好的扩展类我们一般使用实现runnable接口.即一边实现一边继承。但2种方式创建线程的功能是一样的没有本质的区别
创建线程的方法:
1.继承Thread方法
new Thread(()->{},"").start();
2.实现Runnable接口
public class MyThread2 implements Runnable {//实现Runnable接口
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
//创建并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者 new Thread(new MyThread2()).start();
}
}
扩展:strat()方法执行了如下步骤:
1.通过jvm告诉操作系统创建Thread
2.操作系统开辟内存并使用Windows SDK中的createThread()函数创建Thread线程对象。
3.操作系统对Thread对象进行调度,以确定执行时机
4.Thread在操作系统中被成功执行
3.使用线程池
4.实现Callable接口,重写==call() ==方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕
public class Main {
public static void main(String[] args) throws Exception {
// 将Callable包装成FutureTask,FutureTask也是一种Runnable
MyCallable callable = new MyCallable();
//包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口
FutureTask<Integer> futureTask = new FutureTask<>(callable);
//传入线程执行目标,实例化线程对象
new Thread(futureTask).start();
// get方法会阻塞调用的线程
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
//具体重写call方法
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
return sum;
}
}
Callable 也是一种函数式接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
// 构造函数
public FutureTask(Callable<V> callable);
// 取消线程
public boolean cancel(boolean mayInterruptIfRunning);
// 判断线程
public boolean isDone();
// 获取线程执行结果
public V get() throws InterruptedException, ExecutionException;
}
RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
以上类之间的关系:
三种方式比较:
- Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
- Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
- Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
- 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
- Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现
5.线程的调度是随机的
6.线程的生命周期
/**
* 线程生命周期中的的六种状态
* NEW:还没有调用start()的线程实例所处的状态
* RUNNABLE:正在虚拟机中执行的线程所处的状态
* BLOCKED:等待在监视器锁上的线程所处的状态
* WAITING:等待其它线程执行特定操作的线程所处的状态
* TIMED_WAITING:等待其它线程执行超时操作的线程所处的状态
* TERMINATED:退出的线程所处的状态
* 给定时间点,一个线程只会处于以下状态中的一个,这些状态仅仅是虚拟机层面的线程状态,并不能反映任何操作系统中线程的状态
*/
public enum State {
//还没有调用start()开启的线程实例所处的状态
NEW,
//正在虚拟机中执行或者等待被执行的线程所处的状态,但是这种状态也包含线程正在等待处理器资源这种情况
RUNNABLE,
// 等待在监视器锁上的线程所处的状态,比如进入synchronized同步代码块或同步方法失败
BLOCKED,
// 等待其它线程执行特定操作的线程所处的状态;比如线程执行了以下方法: Object.wait with no timeout、Thread.join with no timeout、 LockSupport.park
WAITING,
// 等待其它线程执行超时操作的线程所处的状态;比如线程执行了以下方法: Thread.sleep、Object.wait with timeout
//Thread.join with timeout、LockSupport.parkNanos、LockSupport.parkUntil
TIMED_WAITING,
//退出的线程所处的状态
TERMINATED;
}
状态详细说明:
1.初始状态(NEW)
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
2.Runnable 线程调用了start()方法后进入该状态,该状态包含了三种情况:
- 就绪状态 :等待cpu分配时间片【1.就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。2.调用线程的start()方法,此线程进入就绪状态。】
- 运行状态:进入Runnable方法执行任务
- 阻塞状态:BIO 执行阻塞式io流时的状态
3.Blocked
没获取到锁时的阻塞状态(阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态)
4.等待(WAITING)
调用wait()、join()或者LockSupport.park以及Thread.sleep()等方法后的状态。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
5.超时等待(TIMED_WAITING)
调用 sleep(time)、wait(time)、join(time)等方法后的状态。处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
6. 终止状态(TERMINATED)
线程执行完成或抛出异常后的状态。
比如:1.当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。2.在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
7.线程优先级
Thread 类中,使用如下属性来代表优先级。
private int priority;
我们可以通过 ==setPriority(int newPriority)==来设置新的优先级,通过 getPriority() 来获取线程的优先级。
Thread初始化priority的源码
Thread parent = currentThread();
this.priority = parent.getPriority();
原来,线程默认的优先级是继承父线程的优先级。
严谨一点说,子线程默认优先级和父线程一样,Java 主线程默认的优先级是 5。
Java 中定义了 3 种优先级,分别是最低优先级(1)、正常优先级(5)、最高优先级(10),代码如下所示。Java 优先级范围是 [1, 10],设置其他数字的优先级都会抛出 IllegalArgumentException异常。
注意:代码执行顺序跟线程的优先级无关。高优先级的线程大概率比低优先的线程优先获得 CPU 资源。优先级高的运行的快
8.线程的基本方法
1.currentThread()方法:返回代码正在被哪个线程调用(获取当前线程)
public class CurrentThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
class MyThread extends Thread{
public MyThread(){
System.out.println("构造方法的打印:"+Thread.currentThread().getName());
}
public void run(){
System.out.println("run方法的打印:"+Thread.currentThread().getName());
}
}
输出结果:
通过执行结果可以看出:MyThread.java类的构造函数是被main线程调用的,而run方法是被Thread-0的线程调用1的
2.isAlive()方法:判断当前线程是否存活
public class IsAliveTest {
@SneakyThrows
public static void main(String[] args) {
MythreadIsAlive myThread = new MythreadIsAlive();
System.out.println("begin =="+myThread.isAlive());
myThread.start();
System.out.println("end =="+myThread.isAlive());
//主线程睡眠1s 目的:让子线程结果
Thread.sleep(1000);
System.out.println("睡玩之后end =="+myThread.isAlive());
}
}
class MythreadIsAlive extends Thread{
@Override
public void run() {
System.out.println("run="+this.isAlive());
}
}
输出结果:
那么什么是活动状态呢?线程已经启动且尚未终止的状态即活动状态
3.sleep(Long millis)方法:在指定的时间(毫秒)内让当前“正在执行的线程"休眠,这个正在执行的线程是指this.currentThread()返回的线程
4.stackTraceElement[] getStackTrace()方法:返回一个表示该线程堆栈跟踪元素组。如果该线程尚未启动或者已经终止,则该方法返回一个零长度数组。如果返回的数组不是零长度,则其第一个元素代表堆栈顶,他是该数组中最新的方法调用。最后一个元素代表堆栈底,是该数组中最旧的方法调用
5.static void dumpStack()方法:作用是将当前线程的堆栈跟踪信息输出值标准错误流。
6.static Map<Thread,StackTraceElement[]> getAllStackTraces()方法:
作用是返回所有活动线程的堆栈跟踪的一个映射
7.getId()方法:获取线程的唯一标识
8.yield():放弃当前的cpu资源,将它让给其他的任务去占用CPU执行时间,但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片
9.interrupt():在当前线程中打了一个停止的标记,并不是真正的停止线程
10.interrupted():测试当前线程是否已经中断,执行后具有将状态标志清除为false的功能 //public static boolean interrupted()
11.isInterrupted():测试线程是否已经中断,但不清除状态标志 //public boolean isInterrupted()
12.suspend():暂停线程
13.resume():恢复线程