进程和线程
并发和并行
- 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,单核CPU执行多任务的过程叫做并发
- 并行:同一时刻,多个任务同时执行,多核CPU同时执行,实现并行
CPU时间片轮转
CPU把时间分成片,我们叫做时间片,一个片为20毫秒,也就是说一个程序的运算值给20毫秒的时间,时间一到,则马上运算另一个程序的数据。这样做虽然没有使得CPU性能得到充分发挥,但是做到了用户友好性。
进程
- 进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。
- 程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列
- 进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
线程
- 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。
- 一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。
**线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。**同样多线程也可以实现并发操作,每个请求分配一个线程来处理。 - 线程的五个状态:新建、就绪、运行、阻塞、死亡,其中阻塞是因为线程需要调用的资源被其他线程占用,需要等待资源释放才能进入到就绪态,等待CPU执行线程进入运行态
创建线程的两种方法
使用线程:
- 定义线程
- 创建线程对象
- 启动线程
- 终止线程:线程进入死亡状态,所有资源被释放
1、继承Thread类,重写run()方法:
/**
* @Author: 李文波
* @Date: 2023/1/6 9:36
* Description: 使用继承Thread并重写run()方法的办法来使用线程
* 需要线程去完成的功能代码写在run方法中
* 当run方法中的代码执行完,线程就死亡,资源会被释放
* @return: void
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程--" + i);
}
}
}
2、实现你Runnable接口,重写run()方法:
注意:
//使用实现接口的方式来创建线程
//实现接口的方式是没有start方法启动的
//用Thread变成宿主,把这个线程对象寄生到Thread类中
Mythread_ofImplementInterface mtii = new Mythread_ofImplementInterface();
Thread thread1 = new Thread(mtii);
thread1.setName("子线程二 ");
thread1.start();
线程阻塞原因
1、线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠n毫秒,然后恢复运行。
2、线程要执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才能恢复运行。
3、线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒。
4、线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如,当线程执行System.in.read()方法时,如果用户没有向控制台输入数据,则该线程会一直等读到了用户的输入数据才从read()方法返回。进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态。
5、请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回。
6、线程从Socket的输入流读取数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现了异常,才从输入流的read()方法返回或异常中断。输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型。
为何启动进程的时候用 start调用 而不是直接调用run()方法:
因为start()方法会调用start0()方法,该方法调用之后欧,线程并不会马上执行,而是会变成可运行状态,具体什么时候执行,还需要看CPU调度
线程的常用调度方法:
方法名称 | 参数列表 | 返回值 | 方法效果 |
---|---|---|---|
setPriority | int | void | 更改线程的优先级,优先级是1-10,1最低,10最高 |
sleep | long millis | void | 设置线程休眠millis毫秒 |
join | void | 等待该线程终止,比如t1.join();那么t1就抢占CPU资源,其他线程要等t1死亡之后才能运行 | |
join | long millis | void | 等待该线程终止,不过最多只能等待millis毫秒 |
static yield | void | 暂停执行当前线程,等待其他线程优先级相同或者更高的线程,当前线程进入就绪状态,不转为阻塞状态,也就是说,可能暂停之后马上又被CPU执行了 | |
interrupt | void | 中断线程,由运行态转换为死亡态 | |
wait | void | 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。而当前线程排队,等候其他线程调用notify() 或 notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行 | |
notify | void | 一旦执行此方法,那么会唤醒被wait的线程,如果有多个线程被wait,那么唤醒优先级最高的那个 | |
notifyAll | void | 一旦执行此方法,那么会唤醒所有被wait的线程 | |
sleep() 与 wait() 的异同
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
(1) 两个方法声明的位置不同:Thread类中声明sleep() ,
Object类中声明wait()
(2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
(3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
(4)当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
synchronized关键字
意为同步,当一个方法或者代码块被synchronized修饰时,意为着这个方法或者这一段代码块是线程安全的
也就是说,如果当前有一个线程访问到了加锁内容,那么其他线程需要等该线程结束才能对加锁内容进行操作,保证在任意时刻只能有一个线程对加锁内容进行操作。
但是我们一般不推荐锁方法,因为方法并不是所有代码都需要上锁,如果只锁需要上锁的代码块可以提高CPU利用率
- 如果锁的是方法,那么只需要在权限修饰符后添加synchronized关键字即可
- 如果锁的是代码块,那么需要使用对象锁
synchronized (obj){
//代码块内容
}