Java线程与锁知识点总结

版权声明:本文为博主原创文章,转载请附上博文链接! https://blog.csdn.net/qq_24095055/article/details/88552539

Java线程与锁知识点总结

进程与线程

进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。一个进程一般包括多个线程,这些线程共享本进程的内存和资源。

线程的状态

在这里插入图片描述

  • 新建(New):创建后尚未启动的线程的状态
  • 运行(Runnable):包含Running和Ready
  • 无期限等待(Waiting):不会被分配CPU执行时间,需要显式被唤醒
  • 没有设置Timeout参数的Object.wait()方法
  • 没有设置Timeout参数的Thread.join()方法
  • LockSupport.part()方法
  • 限期等待(Timed Waiting):在一定时间后会由系统自动唤醒
  • Thread.sleep()方法
  • 设置了Timeout参数的Object.wait()方法
  • 设置了Timeout参数的Thread.join()方法
  • LockSupport.parkNanos()方法
  • LockSupport.parkUntil()方法
  • 阻塞(Blocked):等待获取排它锁
  • 结束(Terminated):已终止线程的状态,线程已经结束执行

实现线程的几种方式

  • 继承Thread类创建线程
  • 实现Runnable接口创建线程
  • 实现Callable接口创建新线程(可用Future返回结果)

进程和线程的区别

  • 线程不能看做独立应用,而进程可看做独立应用。
  • 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径。
  • 线程没有独立的地址空间,多进程的程序比多线程程序健壮
  • 进程的切换比线程的切换开销大

Java进程和线程的关系

  • Java对操作系统提供的功能进行封装,包括进程和线程
  • 运行一个程序会产生一个进程,进程包含至少一个线程
  • 每个进程对应一个JVM实例,多个线程共享JVM里的堆
  • Java采用单线程编程模型,程序会自动创建主线程
  • 主线程可以创建子线程,原则上要后于子线程完成执行

Thread中的start和run方法的区别

  • 调用start()方法会创建一个新的子线程并启动
  • run()方法只是Thread的一个普通方法的调用,还是在主线程中执行。
    执行效果如图:
    在这里插入图片描述
    在这里插入图片描述

Thread和Runnable的关系

  • Thread是实现了Runnable接口的类,使得run支持多线程
  • 因类的单一继承原则,推荐多使用Runnable接口

如何实现处理线程的返回值

如下场景:
在这里插入图片描述
因为sleep方法没有执行完,可是主线程又没有等子线程执行完,导致输出的value为null。

解决的方法有三种:

  • 主线程等待法
  • 使用Thread类的join()阻塞当前线程以等待子线程处理完毕
  • 通过Callable接口实现:通过FutureTask Or 线程池获取
    在这里插入图片描述

volatile和synchronized的区别

volatile 是在告诉JVM当前变量在cpu缓存中的值是不确定的,需要从主存中读取(禁止指令的重排序); synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。volatile与synchronized的区别如下:

比较点 volatile synchronized
阻塞 不会发生线程阻塞 会发生线程阻塞
修饰 变量 方法、代码块、类
原子性 不能保证 可以保证
安全性 非线程安全 线程安全
解决问题 变量在多线程之间的可见性 多线程之间访问资源的同步性

sychronized和Lock

比较点 sychronized Lock
解释 Java关键字 Java接口
显隐 隐式锁 需显示指定起始位置和终止位置
释放锁 获取锁的线程会在执行完同步代码后自动释放锁(或者JVM会在线程执行发生异常时释放锁) 在finally中必须释放锁,不然容易造成线程死锁
等待 一个线程获得锁后阻塞,其他线程会一直等待 线程不会一直等待,超时会释放
锁类型 可重入但不可中断、非公平 可重入、可中断、可公平也可不公平

synchronized和ReentrantLock的区别

  • ReentrantLock(再入锁)

    位于java.util.concurrent.locks包
    和CountDownLatch、FutureTask、Semaphore一样基于AQS实现
    能够实现比synchronized更细粒度的控制,如控制fairness
    调用lock()之后,必须调用unlock()释放锁

  • ReentrantLock公平性的设置

    ReentrantLock fairLock = new ReentrantLock(true);
    参数为true时,倾向于将锁赋予等待时间最久的线程
    公平锁:获取锁的顺序按先后顺序调用lock方法的顺序(慎用)
    非公平锁:抢占的顺序不一定,看运气
    synchronized是非公平锁

  • ReentrantLock将锁对象化

    判断是否有线程,或者某个特写线程,在派对等待获取锁
    带超时的获取锁的尝试
    感知有没有成功获取锁

  • 总结

    • synchronized是关键字,ReentrantLock是类
    • ReentrantLock可以对获取锁的等待时间进行设置,避免死锁
    • ReentrantLock可以获取各种锁的信息
    • ReentrantLock可以灵活的实现多路通知
    • 机制:sync操作对象头中的Mark Word,lock调用Unsafe类的park()方法

sleep和wait的区别

  • 基本的差别
  • sleep是Thread类的方法,wait是Object类中定义的方法
  • sleep()方法可以在任何地方使用
  • wait()方法只能在synchronized方法或synchronized块中使用
  • 最主要的本质区别
  • Thread.sleep只会让出CPU,不会导致锁行为的改变
  • Object.wait不仅让出CPU,还会释放已经占有的同步资源锁

notify和notifyAll的区别

  • notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
  • notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
  • 锁池

假设线程A已经拥有了某个对象(不是类)的锁,而其他线程B、C想要调用这个对象的这个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

  • 等待池

假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就会进入到了该对象的等待池中,进入到等待池中的线程不回去竞争该对象的锁。

yield

当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。

如何中断线程

  • 已经被抛弃的方法
  • 通过调用stop()方法停止线程
  • 通过调用suspend()和resume()方法
  • 目前使用的方法
  • 调用interrupt(),通知线程应该中断了

①如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。(只是改变了中断标志,并没有中断。因此interrupt并不能中断线程,还需要别调用的线程配合中断)

  • 需要被调用的线程配置中断

①在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。

sleep()和wait(),yield()和notify()

  • sleep()是Thread类的一个静态函数,它会使调用线程睡眠(阻塞)一段时间,让其他线程有机会继续执行,但它不释放锁。
  • wait()是Object类的方法,它会使当前线程阻塞,直到调用notify(),则被唤醒,它会释放锁。
  • yield()是Thread类的方法,它会使运行中的线程重新变为就绪状态,让同优先级线程重新竞争。
  • notify()是Object类的方法,它会唤醒单个线程。

分布式锁

一种跨服务器(JVM)控制共享资源访问的互斥机制。在分布式系统环境下,一个方法在同一时间只能被一台机器的一个线程执行。

  1. 基于数据库实现分布式锁:在数据库中创建一张表,表中包含方法名字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

  2. 基于缓存(Redis等)实现分布式锁:获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为当前时间加上锁定时间,释放锁的时候执行delete进行释放。
    Redis实现分布式锁详见小博另一篇博客: 点击查看

  3. 基于Zookeeper实现分布式锁:①创建一个目录dislock;②线程A想获取锁就在dislock目录下创建临时顺序节点;③获取dislock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;④线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;⑤线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

参考博客:https://chung567115.blog.csdn.net/article/details/79772952
分布式锁之Redis实现:https://blog.csdn.net/u012102104/article/details/82966553

猜你喜欢

转载自blog.csdn.net/qq_24095055/article/details/88552539