线程与进程
进程是以独立于其他进程的方式运行的,进程间是互相隔离的。一个进程无法直接访问另一个进程的数据。进程的资源诸如内存和CPU时间片都是由操作系统来分配。
线程又被称为轻量级进程。每个线程有它独自的调用栈, 但是在同一进程下的线程又能互相访问它们间的共享数据。每个线程都有它独自的缓存。如果一个线程读取了某些共享数据,那么它将这些数据存放在自己的缓存中以供将来再次读取;
单线程与多线程
单线程
单线程就是进程中只有一个线程。单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行;
public class SingleThread {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
System.out.print(i + " ");
}
}
}
多线程
由一个以上线程组成的程序称为多线程程序。常见的多线程程序如:GUI应用程序、I/O操作、网络容器等;
Java中,一定是从主线程开始执行(main方法),然后在主线程的某个位置启动新的线程。
线程的基本操作
创建线程
1)继承java.lang.Thread
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.print(i + " ");
}
}
}
public class MultiThread {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); //启动子线程
//主线程继续同时向下执行
for (int i = 0; i < 10000; i++) {
System.out.print(i + " ");
}
}
}
上述代码中,MyThread类继承了类java.lang.Thread,并覆写了run方法。主线程从main方法开始执行,当主线程执行至t.start()时,启动新线程(注意此处是调用start方法,不是run方法),新线程会并发执行自身的run方法;
2)实现java.lang.Runnable接口
public class MyThread implements Runnable {
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.print(i + " ");
}
}
}
public class MultiThread {
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start(); //启动子线程
//主线程继续同时向下执行
for (int i = 0; i < 10000; i++) {
System.out.print(i + " ");
}
}
}
暂停
Java中线程的暂停是调用java.lang.Thread类的sleep方法(注意是类方法)。
该方法会使当前正在执行的线程暂停指定的时间,如果线程持有锁,sleep方法结束前并不会释放该锁。
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
try {
Thread.sleep(1000); //当前main线程暂停1000ms
} catch (InterruptedException e) {
}
}
}
}
上述代码中,当main线程调用Thread.sleep(1000)后,线程会被暂停,如果被interrupt,则会抛出InterruptedException异常。
互斥
Java中线程的共享互斥操作,会使用synchronized关键字。线程共享互斥的架构称为监视(monitor),而获取锁有时也称为“持有(own)监视”。
每个锁在同一时刻,只能由一个线程持有。
注意:synchronized方法或声明执行期间,如程序遇到任何异常或return,线程都会释放锁。
中断
java.lang.Thread类有一个interrupt
方法,该方法直接对线程调用。当被interrupt的线程正在sleep或wait时,会抛出InterruptedException
异常。
事实上,interrupt
方法只是改变目标线程的中断状态(interrupt status),而那些会抛出InterruptedException
异常的方法,如wait、sleep、join等,都是在方法内部不断地检查中断状态的值。
-
interrupt方法
Thread实例方法:必须由其它线程获取被调用线程的实例后,进行调用。实际上,只是改变了被调用线程的内部中断状态; -
Thread.interrupted方法
Thread类方法:必须在当前执行线程内调用,该方法返回当前线程的内部中断状态,然后清除中断状态(置为false) ; -
isInterrupted方法
Thread实例方法:用来检查指定线程的中断状态。当线程为中断状态时,会返回true;否则返回false。
协调
1、wait set / wait方法
wait set是一个虚拟的概念,每个Java类的实例都有一个wait set,当对象执行wait方法时,当前线程就会暂停,并进入该对象的wait set。
当发生以下事件时,线程才会退出wait set:
①有其它线程以notify方法唤醒该线程
②有其它线程以notifyAll方法唤醒该线程
③有其它线程以interrupt方法唤醒该线程
④wait方法已到期
注:当前线程若要执行obj.wait()
,则必须先获取该对象锁。当线程进入wait set后,就已经释放了该对象锁。
下图中线程A先获得对象锁,然后调用wait()方法(此时线程B无法获取锁,只能等待)。当线程A调用完wait()方法进入wait set后会自动释放锁,线程B获得锁。
[图片上传失败...(image-e2668b-1618225930761)]
2、notify方法
notify方法相当于从wait set中从挑出一个线程并唤醒。
下图中线程A在当前实例对象的wait set中等待,此时线程B必须拿到同一实例的对象锁,才能调用notify方法唤醒wait set中的任意一个线程。
注:线程B调用notify方法后,并不会立即释放锁,会有一段时间差。
3、notifyAll方法
notifyAll方法相当于将wait set中的所有线程都唤醒
wait、notify、notifyAll这三个方法都是java.lang.Object类的方法(注意,不是Thread类的方法)。
若线程没有拿到当前对象锁就直接调用对象的这些方法,都会抛出java.lang.IllegalMonitorStateException异常。
- obj.wait()是把当前线程放到obj的wait set;
- obj.notify()是从obj的wait set里唤醒1个线程;
- obj.notifyAll()是唤醒所有在obj的wait set里的线程;
进程状态
image.png创建状态(new):进程正在被创建,仅仅在堆上分配内存,尚未进入就绪状态;
就绪状态(Runnable):进程已处于准备运行的状态,即进程已获得除了CPU之外的所需资源,一旦分配到CPU时间片即可进入运行状态。
运行状态(Running):进程正在运行,占用CPU资源,执行代码。任意时刻点,处于运行状态的进程(线程)的总数,不会超过是CPU的总核数;
阻塞状态(Blocked): 进程处于等待某一事件而放弃CPU,暂停运行。阻塞状态分3类:
- 阻塞在对象等待池:当进程在运行时执行Object.wait()方法,虚拟机会把线程放入等待池;
- 阻塞在对象锁池 :当进程在运行时企图获取已经被其他进程占用的同步锁时,虚拟机会把线程放入锁池;
- 其他阻塞状态 :当进程在运行时执行Sleep()方法,或调用其他进程的join()方法,或者发出I/O请求时,进入该阻塞状态。
死亡状态(dead):进程正在被结束,这可能是进程正常结束或其他原因中断退出运行。
进程结束运行前,系统必须置进程为dead态,再处理资源释放和回收等工作。
线程状态转移
线程状态转移.png0:新建态到就绪态
- 新建态:一个线程被创建出来时候所处的状态 ;
- 就绪态:线程调用start()方法后,便处于可以被操作系统调度的状态,即就绪态
该状态可以由三处转化而来,新建态执行了start、线程阻塞结束、锁池等待队列中的线程获得了锁。
1:就绪态到运行态
运行态:表示当前线程被操作系统调度,分配了时间片,执行线程中的run方法时的状态。
运行态只可以由就绪态的线程转化而来,如果多个线程都处在就绪态,就等待操作系统分配。
2:运行态到就绪态
时间片用完
操作系统为了公平,不可能从就绪态里面选择一个,一直执行完,而是随机切换到另外的线程去执行,每个线程分配的执行时间结束,操作系统去调用别的线程,当前刚执行结束的线程便由运行态重新回到就绪态,等待操作系统的再次分配;
yield()
让该线程重新回到就绪队列,但是yield()让当前线程回到就绪队列后,并不能保证操作系统再次调用不会选择该线程,所以yield()方法不能用来控制线程的执行顺序;
3:运行态到阻塞态
阻塞态表示当前线程被由于某种原因,被挂起,也就是被阻塞,正在运行的线程被阻塞后,即使结束阻塞状态也回不去运行态,只能回到就绪态,等待os分配cpu资源去调度;
造成阻塞的方法有:sleep()、join()等
4:阻塞态到就绪态
结束阻塞态,都是回到就绪态,无法再立即回到运行态。
5:运行态到等待队列
等待队列,就是当前线程占有锁之后,主动把锁让出,使自身进入等待队列,wait加notify可以保证线程执行的先后顺序。
notify()是通知一个等待队列的线程回到锁池队列。
notifyAll()是通知所有处在等待队列的线程,都回到锁池队列。
6:运行态到锁池队列
原因是锁被占用,拿不到需要等待锁释放,就进入锁池队列。
7:等待队列到锁池队列
调用notify()让等待队列中的线程进入锁池队列。
8:锁池队列到就绪态
处在锁池队列中的线程可以拿到锁,进入就绪态,等待操作系统的调度,从而进入运行态。
9:运行态到死亡态
死亡态只能由运行态进入,运行态中的线程。
网友评论