Java多线程

目录

创建线程的三种方式

通过继承Thread类

通过实现Runnable接口

通过实现Callable接口

Runnable接口和Callable接口的区别

解决线程安全问题

synchronized同步机制

Lock锁

线程死锁

volatile关键字的作用

保证可见性

禁止指令重排

线程通信

Thread 类中的 yield 方法有什么作用

Java 中用到的线程调度算法是什么

ThreadLocal

四种引用类型


创建线程的三种方式

分别是继承Thread类、实现Runnable接口、实现Callable接口。

1、通过继承Thread类

  • 定义Thread类的子类,并重写该类的run()方法,该run()方法将作为线程执行体。
  • 创建Thread子类的实例,即创建了线程对象。
  • 调用线程对象的start()方法来启动该线程。

2、通过实现Runnable接口

  • 定义Runnable接口的实现类,并实现该接口的run()方法,该run()方法将作为线程执行体。
  • 创建Runnable实现类的实例,并将其作为Thread的target来创建Thread对象,Thread对象为线程对象。
  • 调用线程对象的start()方法来启动该线程。
  • 建议实现Runnable接口的方式
    • 避免了类的单继承的局限性
    • 更适合处理有共享数据的问题
    • 实现了代码和数据的分离

3、通过实现Callable接口

  • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。然后再创建Callable实现类的实例。
  • 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

Runnable接口和Callable接口的区别

  • call方法可以有返回值,Callable使用泛型参数,可以指定call方法返回参数类型
  • call方法可以用throw的方式处理异常

解决线程安全问题

多个线程间有共享的资源,所以会产生线程安全问题

synchronized同步机制

  • 同步代码块
    • synchronized(同步监视器){需要被同步的代码}
  • 同步方法
    • 用synchronized关键字修饰的方法

Lock锁

  • ReentrantLock
    • Java 5新增了一个java.util.concurrent包来支持同步,其中ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
    • 1、创建Lock的实例对象,确保多个线程共用一个Lock实例对象,所以需要考虑用static final
    • 2、执行lock方法,锁定对共享资源的调用
    • 3、执行unlock方法,释放对共享资源的锁定

synchronized不管是同步代码块还是同步方法,都需要在结束一对{ }后,释放对同步监视器的调用

Lock是通过两个方法控制需要被同步的代码,更灵活

Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高

线程死锁

  • 死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。​
  • 造成死锁的原因
    • 循坏等待
    • 不可抢夺
    • 占用且等待
    • 互斥

volatile关键字的作用

保证可见性

  • 当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。
    当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制它被值刷新到主内存中。当一个线程用到被volatile关键字修饰的值的时候,虚拟机会强制要求它从主内存中读取。

禁止指令重排

  • 指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果是正确的,但是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。非常经典的例子是在单例方法中同时对字段加入voliate,就是为了防止指令重排序。

线程通信

wait()、notify()、notifyAll()

  • 这三个方法是Object类中声明的方法,必须在同步代码块或同步方法中使用(只有采用synchronized实现线程同步时才能使用这三个方法), 且这三个方法的调用者必须是同步监视器。
  • wait():让当前线程进入等待(阻塞)状态,并释放锁(释放对同步监视器的调用)
  • notify():唤醒一个正在等待的线程
  • notifyAll():唤醒所有正在等待的线程

wait()和sleep()的区别

  • sleep()是Thread类中的静态方法,而wait()是Object类中的成员方法;
  • sleep()可以在任何地方使用,而wait()只能在同步方法或同步代码块中使用;
  • sleep()不会释放锁,而wait()会释放锁(同步监视器),需要通过notify()/notifyAll()唤醒;

Thread 类中的 yield 方法有什么作用

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。

Java 中用到的线程调度算法是什么

线程调度是指按照特定机制为多个线程分配 CPU 的使用权。(Java是由JVM中的线程计数器来实现线程调度)

有两种调度模型:分时调度模型和抢占式调度模型。 ​

  • 分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。 ​
  • Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

ThreadLocal

每个线程都有一个线程隔离区

ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量,只能由当前 Thread 使用,其它 Thread 不可访问,那就不存在多线程间共享的问题。

逻辑关系 Thread->ThreadLocal-> ThreadLocalMap

每个线程都有一个ThreadLocal,ThreadLocal中又有ThreadLocalMap,其中key是ThreadLocal实例,value是我们set进去的值。

四种引用类型

绝大部分对象都是强引用

  • 强引用 不会被GC
  • 软引用 内存不足时被GC,软引用可用来实现内存敏感的高速缓存。
  • 弱引用 JVM进行垃圾回收时就会被GC
  • 虚引用 任何时候都可能被GC
  • ThreadLocalMap中key是弱引用,方法执行完后threadLocal实例会被直接GC(不需要了)
  • ThreadLocalMap中value是强引用,方法执行完后,避免数据丢失。

使用完ThreadLocal之后手动remove进行回收,否则可能会导致数据错乱或内存泄漏(该回收的数据没有被回收)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ta是个码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值