Ezio's Blog
Posts Categories Tags Music Mood About
Ezio's Blog· Light
☰ Menu
Posts Categories Tags Music Mood About
Expand all Back to top Go to bottom

Java线程梳理

Author: Ezio Date: February 1, 2021  14:16:54 Category: Java

线程

实现方式

  1. 通过继承Thread类实现一个线程

  2. 通过实现Runnable接口实现一个线程

  3. 使用Callable和Future

线程生命周期:

创建线程——>就绪——>运行——>阻塞——>死亡

涉及方法:

new,start,run,wait,notify,sleep,interrupt

sleep() 和 wait() 的区别

  • sleep 来自 Thread类,wait 来自 Object 类
  • sleep 没有释放锁,wait 释放了锁
  • 使用范围不一样,wait、notify/notifyAll 必须在同步块或者同步方法中使用,sleep任意地方都可以使用
  • sleep必须捕获异常,wait不需

进程:

资源分配的基本单位,运行调度的基本单位,系统中并发执行的单位

线程:

进程中执行运算的最小单位,亦是执行处理机调度的基本单位

synchronized与Lock区别

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步

线程间的协作:

**同步:**synchronized与Lock实现,上锁保证同步;

**通信:**线程间的唤醒;wait()、notify()、notifyAll()

ps:只有在同步控制方法或同步控制块里才能调用 wait() 、notify() 和 notifyAll()。

Synchronized 原理

在编译的字节码中加入了两条指令来进行代码的同步。

monitorenter :

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

实现线程安全的方法

  • 锁

    • synchronized 关键字
    • Lock
    • 乐观锁:设置时间戳或者版本号之类的(JPA中的乐观锁的实现)
  • 线程本地存储

    • 成员变量 int num = 10;
    • 多个线程都要操作这个变量,但是需求是每个线程中的num值初始都为10,互不影响
    • ThreadLocal

锁的类型:

可重入锁、自旋锁、公平锁、读写锁、独占锁、乐观锁

synchronized的优化(JDK1.6后加入):

  • 偏向锁(无锁):

    大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后(线程的id会记录在对象的Mark Word中),消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。

  • 轻量级锁(CAS):

    轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;轻量级锁的意图是在没有多线程竞争的情况下,通过CAS操作尝试将MarkWord更新为指向LockRecord的指针,减少了使用重量级锁的系统互斥量产生的性能消耗。

  • 重量级锁:

    虚拟机使用CAS操作尝试将MarkWord更新为指向LockRecord的指针,如果更新成功表示线程就拥有该对象的锁;如果失败,会检查MarkWord是否指向当前线程的栈帧,如果是,表示当前线程已经拥有这个锁;如果不是,说明这个锁被其他线程抢占,此时膨胀为重量级锁。

ThreadLocal基础知识

很多地方叫做线程本地变量,也有些地方叫做线程本地存储。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

线程池

\1. 线程复用:实现线程复用的原理应该就是要保持线程处于存活状态(就绪,运行或阻塞)

\2. 控制并发数量:(核心线程和最大线程数控制)

\3. 管理线程(设置线程的状态)

Author: Ezio

Permalink: https://ezioy.cn/2021/02/01/Java%E7%BA%BF%E7%A8%8B%E6%A2%B3%E7%90%86/

License: Copyright (c) 2019 CC-BY-NC-4.0 LICENSE

Slogan: Nothing is true,Everything is permitted

Tag(s): # Java
back · home
JVM运行时数据区 Java集合
Ezio © 2019 - 2026 | Powered by Hexo & Chic | 访客数量:   浏览次数: | 渝公网安备50011302222043 | 渝ICP备2023013933号-1