概念
线程的相关概念
- 程序:Program,是一个CPU运行指令的集合
- 进程:Process,(正在执行中的程序)是一个静态的概念
- 进程是程序的一次静态态执行过程,占用特定的地址空间
- 每个进程都是独立的,由3部分组成cpu,data,code
- 缺点:内存的浪费,Cpu的负担
- 线程:是进程中一个"单一的连续控制流程(a singles Threadequentialflow of control)/执行路径
- 线程又被称为轻量级进程
- 一个进程可拥有多个并行的进程
- 一个进程中的线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象。而且他们从同一堆中分配对象->通信、数据操作、同步操作
- 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
- 一个进程中至少有一个线程(主线程)
java中的进程和线程
- Java虚拟机启动的时候会有一个进程Java.exe,该进程中至少有一个线程,在负责java程序的执行。而且这个线程运行的代码存在于main方法中,该线程称之为主线程
- 一个进程中的线程共享代码和数据空间
- 线程结束,进程未毕结束,但进程结束,线程一定结束。
- 进程中包含线程,线程是进程的一部分
进程与线程的区别
区别 | 进程 | 线程 |
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。 | 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程则执行过程不是一条线的,而是多条线(线程) 共同完成的 | 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。 |
Java实现多进程
- 在Java中负责线程的这个功能的是Java.lang.Thread 这个类
- 可以通过创建Thread的实例来创建新的线程
- 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体
- 通过调用Thead类的start0方法来启动一个线程
线程的创建方式一(集成Thread类)
- 集成Thread类
- 重写run方法
- 创建对象、调用start方法,启动线程
public class ThreadDemo extends Thread{
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--------------"+i);
}
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
for(int i =0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
线程的创建方式二(实现Runnable接口)
- 实现Runnable接口
- 重写run方法
- 创建Thread对象(构造函数创建)
- 调用start方法、启动线程
public class RunnableDemo implements Runnable {
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--------------"+i);
}
}
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
for(int i =0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
案例实现(卖票)
继承Thread实现
package com.wanglin.study.ticket;
/**
*
* @author wanglin
* @date 2023/09/08 14:53
**/
public class TicketThread extends Thread{
//5张票
private static Integer ticket = 5;
@Override
public void run() {
for (int i = 10;i<100;i++){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在出售第---------------" + (ticket--) + "------张票");
}
}
}
public static void main(String[] args) {
TicketThread thread = new TicketThread();
TicketThread thread1 = new TicketThread();
TicketThread thread2 = new TicketThread();
TicketThread thread3 = new TicketThread();
TicketThread thread4 = new TicketThread();
thread.start();
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
Runnable接口实现:
package com.wanglin.study.ticket;
/**
*
* @author wanglin
* @date 2023/09/08 15:03
**/
public class TicketRunnable implements Runnable{
private Integer ticket = 5;
@Override
public void run() {
for (int i = 1;i<100;i++){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "正在出售第---------------" + (ticket--) + "------张票");
}
}
}
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
Thread t1 = new Thread(ticketRunnable);
Thread t2 = new Thread(ticketRunnable);
Thread t3 = new Thread(ticketRunnable);
Thread t4 = new Thread(ticketRunnable);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
对比
- 继承Thread类方式的缺点:那就是如果我们的类已经从一个类继(如小程序必须继承自 Applet 类),则无法继承 Thread(Java是单继承)
- 通过Runnable接口实现多线程
- 优点:可以同时实现继承别的类。实现Runnable接口方式要通用一些
- 避免单继承
- 方便共享资源同一份资源多个代理访问
线程的状态
- 新生状态
- 用new关键词新建一个线程后、该线程对象就除于新生状态
- 处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态
- 就绪状态
- 处于就绪状态的线程具备了运行的条件、但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU
- 当系统选定一个等待执行的线程(就绪状态)后,它就会从就绪状态进入执行状态,该动作称为“CPU调度”
- 运行状态
- 在运行状态的线程执行自己的run方法中的代码,直到等待某资源而阻塞或完成任务而死亡。
- 如果线程在给定的时间片内没有执行结束,就会被系统换下来回到等待执行状态
- 阻塞状态
- 处于运行状态的线程在某些情况下,比如执行了sleep方法,或等待I/O设备资源,将让出CPU并暂时停止自己运行,进入阻塞状态
- 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程变转入就绪状态,重新导就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行
- Join方法(代码执行逻辑)
- 死亡状态
- 死亡状态是线程生命周期的最后一个阶段。线程死亡的原因有三个
- 正常运行的线程完成了他的全部工作
- 线程被强制性终止、比如通过调用stop()方法终止线程(不推荐)
- 线程抛出未捕获异常
- 死亡状态是线程生命周期的最后一个阶段。线程死亡的原因有三个
线程相关的操作方法
序号 | 方法名称 | 描述 |
1 | public static Thread currentThread() | 返回目前正在执行的线程 |
2 | public final String getName() | 返回线程的名称 |
3 | public final int getPriority() | 返回线程的优先级 |
4 | public final void setPriority(int newPriority) | 设置线程的优先级 |
5 | public final boolean isAlive() | 判断线程是否在活动,如果是,返回true,否则返回false |
6 | public final void join() | 调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行 |
7 | public static void sleep(long millis) | 使用当前正在执行的线程休眠millis秒,线程处于阻塞状态 |
8 | public static void yield() | 当前正在执行的线程暂停一次,允许其他线程执行,不阻塞,线程进入就绪状态,如果没有其他等待执行的线程,这个时候当前线程就会马上恢复执行。 |
9 | public final void stop() | 强迫线程停止执行。已过时。不推荐使用 |
线程同步与死锁
多线程的运行出现了安全性问题
线程同步(加锁)
- 使用同步代码块解决线程安全问题
- 使用同步方法解决线程安全问题
同步前提:
- 必须有两个或两个以上的线程
- 必须是多线程使用同一资源
- 必须保证同步中只有一个线程在执行
package com.wanglin.study.ticket;
/**
*
* @author wanglin
* @date 2023/09/08 15:03
**/
public class TicketRunnable implements Runnable{
private Integer ticket = 5;
@Override
public void run() {
for (int i = 1;i<100;i++){
synchronized (this){
if (ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第---------------" + (ticket--) + "------张票");
}
}
}
}
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
Thread t1 = new Thread(ticketRunnable);
Thread t2 = new Thread(ticketRunnable);
Thread t3 = new Thread(ticketRunnable);
Thread t4 = new Thread(ticketRunnable);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
使用关键词synchronized实现代码同步(加锁),通常将当前对象作为同步对象。其理论是在需要操作资源的代码片段上加锁,实现同一时刻只有一个线程能够操作资源。
缺点:效率低,在高并发中使用CAS机制
- 同步监视器
- synchronized(obj)0中的obj称为同步监视器
- 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监
视器 - 同步方法中无需指定同步监视器,因为同步方法的监视器是this,也就是该对象
本身
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
死锁
- 同步可以解决线程安全问题、但是过多使用同步会产生死锁问题
- 死锁一般情况表示线程互相等待,是程序运行出现的问题
线程的生产者和消费者
- 生产者不断生产,消费者不断取走生产者生产的产品
- 生产者生产产品放到一个区域,之后消费者从此区域取出产品