目录
线程
程序:一段静态的代码
进程:正在执行的程序,是操作系统资源分配的最小单位
线程:进程可进一步细分为线程,是进程内部最小的执行单元,是操作系统进行任务调度的最小单元,属于进程
现在Java线程的本质就是操作系统的线程
线程与进程的关系
-
一个进程可以包含多个线程,线程隶属于进程
-
一个进程中至少包含一个线程(主线程),在主线程中可以创建其他线程
-
一个进程内的所有线程共享该进程的内存资源
总结:线程是进程可以划分的最小执行单位,线程和进程最大的区别是进程基本上都是独立的,而各线程不一定,因为同一进程的线程之间可能互相影响。线程执行开销小,但不利于资源的管理和保护,进程则相反。
程序计数器、虚拟机栈和本地方法栈为什么是私有的?
-
程序计数器主要是为了保证切换线程后能恢复到正确的位置
-
虚拟机栈和本地方法栈是为了保证局部变量不被其他线程访问到
并发与并行的区别
-
并发:两个及两个以上的作业在同一时间段进行,也就是依次进行
-
并行:两个及两个以上的作业在同一时刻进行
同步和异步的区别
-
同步:发出一个调用后,在没有得到结果前,该调用就不可以返回,一直等
-
异步:调用在发出后,不用等待返回结果,该调用可以直接返回
线程的创建
继承Thread类
类就不能继承其他类
public class MyThread extends Thread{
@Override
public void run() {// 线程执行代码 执行完毕 线程死亡
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread:"+i);
}
}
}
public class Test {
/*
main启动java主线程
*/
public static void main(String[] args) {
//创建线程
MyThread myThread = new MyThread();
myThread.start();//启动线程
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
实现Runnable
类还可以继承其他类
public class ThreadeDemo implements Runnable{
/*
currentThread() 获得当前正在执行的线程对象
getName()获取线程名称
*/
@Override
public void run() {
/*for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}*/
System.out.println(Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
//创建线程执行任务
ThreadeDemo threadeDemo = new ThreadeDemo();
//Thread t = new Thread(threadeDemo);//这是创建线程
Thread t = new Thread(threadeDemo,"自定义线程");//这是创建线程,并为线程命名
//t.setName("");// 为线程命名的方法
t.start();//启动线程,在操作系统中注册,加入到就绪队列,并不是立即执行
t.setPriority(10);//设置优先级
Thread.currentThread().setPriority(1);
System.out.println(t.getPriority());//获得优先级
System.out.println(Thread.currentThread().getPriority());
System.out.println("main");
}
}
实现Callable接口
与使用Runnable相比,Callable功能更强大一些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回结果
public class SumThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int num = 0;
for (int i = 0; i < 100; i++) {
num+=i;
}
return num;
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SumThread sumThread = new SumThread();
// 接收任务
FutureTask<Integer> futureTask = new FutureTask(sumThread);
// 创建线程
Thread t = new Thread(futureTask);
t.start();
Integer sum = futureTask.get();// 获得call方法返回值
System.out.println(sum);
}
}
线程间的同步方式
-
互斥锁:采用互斥对象机制,只有拥有互斥对象的线程才可以访问共同资源,例如synchronized和Lock
-
读写锁:允许多个线程同时读取共享资源,但是只有一个线程可以对资源进行写操作
-
信号量:允许同一时刻多个线程访问同一资源,但是要控制访问的最大线程数
-
屏障:屏障是一种同步原语,用于等待多个线程到达某个点再一起继续执行。
-
事件:Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
PCB是什么?
PCB(Process Control Block) 即进程控制块,是操作系统中用来管理和跟踪进程的数据结构,每个进程都对应着一个独立的 PCB。可以将 PCB 视为进程的大脑。
PCB主要包含以下内容:
-
进程的描述信息:包括进程的名称、标识符等
-
进程的调度信息:包括阻塞原因、进程状态、优先级等
-
进程对资源的需求情况:包括CPU时间、内存空间等
-
进程打开的文件信息:包括文件类型、打开模式等
线程的优先级
实际上,计算机上只有一个CPU,各个线程轮流获取到CPU的执行权,才能使用任务
优先级较高的线程有更多的机会获取到CPU,反之亦然
优先级用整数表示,取值范围在1~10,一般情况下线程默认优先级为5,但是可以通过setPriority和getPriority方法来设置或返回优先级;
调度策略:
-
时间片
-
抢占式:高优先级的线程抢占CPU
Java的调度方法:
同优先级线程组成先进先出的队列,使用时间片的策略
高优先级,使用抢占式策略
线程状态
新建:当一个线程被声明并创建时,那么该线程就是新建状态
就绪:新建的线程被start()后,将进入等待线程的队列等待CPU的时间片
运行:当就绪的线程获取到CPU资源时,便进入到了运行状态
阻塞:在某种情况下,被认为挂起或者执行输入输出操作时,让CPU临时终止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或者线程提前被强制终止或者出现了异常
线程的分类
java中的线程分为两类:用户线程和守护线程
通俗来讲,任何一个守护线程都是非守护线程的保姆,只要当前JVM中还存在任何一个非守护线程没有结束,那么守护线程就会一直工作,当最后一个非守护线程结束时,守护线程也会随之结束
ThreadDemo1 t1 = new ThreadDemo1();
t1.setDaemon(true);//设置线程为守护线程,必须在启动前设置,其他的用户线程结束,守护线程自动结束
t1.start();
注意:设置守护线程一定要在启动线程之前,否则会抛出一个
IllegalThreadStateException异常
什么是线程的上下文切换?
也就是线程的切换,当线程进行切换时,要保存当前线程的上下文,等待下次线程获取到CPU执行的时候恢复现场,并加载下一个占用CPU的线程的上下文
以下几种情况,线程会从占用的CPU中退出:
-
主动让出CPU,例如调用sleep()、wait()等方法
-
时间片用完,因为操作系统要防止一个线程占用CPU资源太长时间导致其他线程死亡
-
被用了阻塞类型的操作,例如IO,线程被阻塞
什么是线程的死锁?
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,由于线程无限期的被阻塞,因此程序不能终止,被称为线程死锁
线程A持有资源2,线程B持有资源1,它们同时都想申请对方的资源,所以这两个线程会因为互相等待进入到死锁状态
public class DemoThread extends Thread {
static Object obja = new Object();
static Object objb = new Object();
boolean flag = true;
public DemoThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (obja) {
System.out.println("if obja");
synchronized (objb) {
System.out.println("if objb");
}
}
} else {
synchronized (objb) {
System.out.println("else objb");
synchronized (obja) {
System.out.println("else obja");
}
}
}
}
}
/*
该测试实现的可能结果:
1. if obja else objb 这是因为当我们第一线程开始执行后,在其执行完System.out.println("if obja");我们的第二个线程可能这个时候拿到了objb锁,也开始执行,这个时候不管是第一个线程还是第二个线程,都在等待对方释放自己需要的同步资源,这就形成了线程死锁
2. else objb if obja 和上面的原因同理 也是形成了死锁
3.if obja if objb else objb else obja 这种情况是最可观的情况,当第一个线程执行完,第二个线程再获得锁,再去执行
*/
public class Test {
public static void main(String[] args) {
DemoThread d1 = new DemoThread(true);
d1.start();
DemoThread d2 = new DemoThread(false);
d2.start();
}
}
产生死锁的四个必要条件
-
互斥条件:该资源任意时刻只能有一个线程占用
-
请求与保持条件:一个线程因请求资源而被阻塞,对已获得的资源保持不放
-
不剥夺条件:线程已获得的资源不能被其他线程强制剥夺,必须自己使用完毕才可以释放
-
循环等待条件:若干线程形成一种循环等待的关系
如何预防和避免死锁?
破坏掉四个必要条件:
-
破坏申请与保持条件:一次性申请全部资源
-
破坏不剥夺条件:占用部分资源的线程申请其他资源的时候,如果申请不到,主动释放自己的资源
-
破坏循环等待条件:按某一顺序申请资源,释放资源则反着释放
线程中一些常用的方法
run()
start():启动线程
构造方法
获取当前线程
获取名字
sleep():让线程休眠指定时间
join():等待该线程结束(把其他线程加入到此线程,让其他线程阻塞)
yield():线程让步
wait():当前线程进入阻塞状态,并释放同步监视器
notify():唤醒被wait的一个线程,如果多个线程被wait,就唤醒优先级最高的那个
notifyAll():唤醒所有被wait的线程
sleep()方法和wait()方法对比
共同点:两者都可以暂停线程
区别:
-
sleep()
方法没有释放锁,而wait()
方法释放了锁 -
wait()
方法通常用于线程的交互/通信,sleep()
方法被用于暂停 -
wait()
后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()
或者notifyAll()
方法。sleep()方法执行完后,线程会自动苏醒,或者也可以使用wait(long timeout)
超时后线程会自动苏醒。 -
sleep()
是Thread
类的静态本地方法,wait()
则是Object
类的本地方法。
为什么wait()方法不定义在Thread中?
wait()
是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object
)而非当前的线程(Thread
)
为什么 sleep() 方法定义在 Thread 中?
因为sleep()是让当前线程暂停,不涉及到对象类,也用不到对象锁
可以直接调用Thread的run方法吗?
调用 start()
方法方可启动线程并使线程进入就绪状态,直接执行 run()
方法的话不会以多线程的方式执行。会把run()方法认为是main线程下的普通方法
线程通信
线程通信指的是多个线程通过相互牵制,相互调度,即线程间的相互作用
经典案例
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待
/*
柜台 放商品 共享数据
*/
public class Counter {
int num = 0;
/*
生产者生产商品
synchronized修饰的是非静态方法 锁是this 就是counter对象,只有一个
*/
public synchronized void add(){
if (num==0){
num++;
System.out.println("生产者生产商品");
this.notify();//唤醒消费者
}else {
try {
this.wait();// 有商品,生产者等待 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
消费者取走商品
*/
public synchronized void sub(){
if (num==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
num--;
System.out.println("消费者拿走了商品");
this.notify();// 唤醒生产者
}
}
}
/*
生产者线程
*/
public class ProductorThread extends Thread {
Counter c;
public ProductorThread(Counter c) {
this.c = c;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
c.add();
}
}
}
/*
消费者线程
*/
public class CustomerThread extends Thread {
Counter c;
public CustomerThread(Counter c) {
this.c = c;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
c.sub();
}
}
}
public class Test {
public static void main(String[] args) {
Counter counter = new Counter();// 柜台 只有一个,是共享数据
ProductorThread p = new ProductorThread(counter);
CustomerThread c = new CustomerThread(counter);
p.start();
c.start();
}
}
执行结果: