java多线程

多线程作用

提高程序效率,cpu在多个线程间切换

并发与并行

并发:同一时刻,多个指令在单个cpu交替执行

并行:同一时刻,多个指令在多个cpu同时执行

实现方式

方式一:继承Thread类方式

  • 自定义类声明为Thread子类

  • 重写run方法

  • 书写线程执行的代码

  • 测试类中创建子类对象t1,并且启动线程

t1.start();

//setName方法可以设置线程名字
t1.setName("xc");

//在线程内部可以通过getName()方法获取

方式二:实现Runable类,重写run方法

  • 自定义类,实现Runable接口

  • 重写run方法

  • 测试类中创建自定义类对象rn

  • 测试类中新建一个Thread对象t1,构造函数传入自定义对象rn

  • 测试类中启动t1

t1.start();

问题:Runable接口中的run方法,无法调用Thread类的方法,getName之类的方法无法调用?

//获取到当前线程的对象
Thread t = Thread.currentThread();

方式三:利用Callable接口和Future接口方式实现

目的:方式一二无返回值,无法获取多线程执行结果

特点:可以获取多线程执行结果
(记忆法:自己run,没人管,自己call,有回应)

  • 自定义类实现Callable接口,泛型即为返回结果的类型

  • 重写call方法

  • 测试类中创建Callable对象mc(表示多线程要执行的任务)

  • 测试类中创建(Future接口实现类)FutureTask的对象ft,构造函数传入mc (管理多线程运行结果)

  • 测试类中创建Thread类对象t1,构造函数传入ft,启动线程

t1.start(); 
  • 获取方法执行结果
ft.get();

三种方式对比

一二种方式无法获得返回值,第三种可以获取返回值。
第一种最简单,但是可扩展性最差:无法继承其他对象。
二三中扩展性强,不能直接调用Thread类中的方法。

Thread常见成员方法

String getName()

  • 返回此线程名称
  • 线程默认名字为 Thread-x(序号从0开始)

void setName(String name)

  • 设置当前线程名字
  • 也可以通过Thread的构造方法,传入名字。注意第一种实现直接继承Thread,构造方法不能继承,需要手动实现构造方法,调用父类构造。

static Thread currentThread()

  • 返回当前线程对象
  • main方法中直接调用currentThread,获得的线程getName结果为main

static void sleep

  • 让线程休眠指定时间,单位为毫秒。休眠结束后会醒来,继续执行。

setPriority(int newPriority)

  • 设置线程优先级(只能影响概率)
  • java里线程优先级最小1,最大10,默认5
  • java采取抢占式调度(随机性)

final int getPriority()

  • 获取线程优先级

final void setDaemon(bookean on)

  • 设置为守护线程,守护线程生命周期依赖非守护进程
  • 当设置为true后,非守护线程结束了,守护线程会陆续结束(不是立刻)
  • 如:性能监控工具的指标采集进程,随应用的停止而终止

public static void yield()

  • 出让线程
  • 出让当前cpu执行权,令代码执行相对均匀

public static void join()

  • 插入线程
  • 使用对象调用
//作用是把t线程插入到当前线程之前,只有t线程执行完毕,当前线程才继续
//当前线程指的是运行t.join()这段代码的线程
t.join()

生命周期

img

注意:进程执行sleep() 结束休眠后返回就绪状态,重新抢夺执行权,不会马上执行代码。睡眠也相当于出让执行权。

安全问题及解决

问题及原因

想让多个线程访问同一个变量,可以在类上定义静态属性,但是仍然会有同步问题

原因:进程执行过程中随时可能被抢夺执行权。

解决方法

同步代码块

//锁对象是任意的,随便创建一个对象即可,但是要保证对每个执行该代码线程是【唯一的】 。最常见的实现是当前类的字节码对象,Xxx.class。一个可行的方法是:定义一个static类属性。
//锁并不保证cup执行权不被抢夺,而是保证就算被抢夺,其他线程也只能等待,因为未解锁。

synchronized(锁对象){
    //被锁住的代码
}

同步方法

将synchronized关键字加在方法上面。

修饰符 synchronized 返回值类型 方法名(方法参数){
    ...
}

同步方法的锁对象是java规定的:

  • 当前方法为非静态:this //即当前方法的调用者
  • 当前方法为静态:当前类的字节码文件对象

若使用方法二runable实现多线程,因为runable实现类对象是作为Thread的构造参数传入,即使创建多个线程也只需要一个对象,所以多个线程访问同一个变量可以不加static,因为是同一个对象。

抽取策略:将同步代码块中所有代码抽取为一个方法。

lock锁

特点:支持手动上锁,释放锁

在类上定义一个Lock类型的成员属性
Lock是接口,不可以直接实例化,使用实现类ReentrantLock来实例化

lock.lock();

//代码逻辑

lock.unlock();

注意:若采用方式一,Lock类型成员属性需要定义为static!!!
注意:涉及跳转逻辑的,需要保证lock.unlock();可以成功执行。推荐的方法是使用try catch finally,将unlock逻辑放在finally中,保证一定会执行。

实际应用

Stringbuilder 是线程不安全的

Stringbuffer 是线程安全的,每个方法都是同步方法

多线程程序编写步骤

  • 循环
  • 同步代码块
  • 判断共享数据是否到达末尾(到了末尾)
  • 判断共享数据是否到达末尾(没到末尾)

生产者消费者(等待唤醒机制)

多线程执行具有随机性,等待唤醒机制使线程执行有序。

成员

生产者:负责生产数据

消费者:负责消费数据

第三者:控制执行顺序

  • 需要有一个状态量,判断当前允许 生产者/消费者 活动
  • 需要有一个锁变量,线程的sychronized,wait,notify均需要借助它调用
  • 可能需要一个共享数据,用于控制生产者与消费者交替的轮数

常见方法

//当前进程等待,直到被其他线程唤醒,会释放当前持有的所有锁
void wait();

//随机唤醒单个线程
void notify();

//唤醒所有线程
void notifyAll();

阻塞队列

相当于成员中第三者的角色

put take方法,底层实现了同步机制。

实现类

ArrayBlockingQueue
底层是数组,有界,需要指定队列长度

LinkedBlockingQueue
底层是链表,无界

阻塞队列实现生产者消费者

生产者消费者必须共用阻塞队列
测试类中实现阻塞队列,构造传给生产者与消费者

生产者调用 queue.put("xxx");

消费者调用 queue.take();

线程六大状态

img

img

没有运行状态?
JVM层次结构位于操作系统与应用层之间,线程运行起来后交由操作系统管理,这样有助于解耦

posted @ 2025-07-16 11:28  violet0evergarden  阅读(10)  评论(0)    收藏  举报