Java并发编程相关

目录

 

1.J.U.C

2.线程执行情况

3.JVM内存模型(JMM)

4.Java并发编程三大特性

5.volatile关键词

6.等待唤醒方法(配合synchronized使用)

7.线程状态

8.join与yield、sleep区别

9.中断线程方法

10.synchronized关键词

11.ReentranLock可重入锁


1.J.U.C

  • java并发包java.util.concurrent,简称J.U.C
  • 按照功能大致划分
    • juc-locks 锁框架
    • juc-atomic 原子类框架
    • juc-sync 同步器框架、工具类
    • juc-collections 集合框架
    • juc-executors 执行器框架

2.线程执行情况

  • jps:查看java程序进程id信息
  • jstack:查看指定进程栈信息
    • jstack+进程id
  • 如何避免死锁
    1. 避免一个线程同时获取多个锁。
    2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
    3. 尝试使用定时锁,使用lock .tryLock (timeout )来替代使用内部锁机制。
  • 上下文切换:CPU 通过时间片分配算法来循环执行任务所以任务从保存到再加载的过程就是一次上下文切换
  • 减少上下文切换
    • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash 算法取模分段,不同的线程处理不同段的数据
    • CAS算法JavaAtomic包使用CAS算法来更新数据,而不需要加锁
    • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
    • 使用协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

3.JVM内存模型(JMM)

  • JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据。而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问
  • 线程对变量的操作(读取赋值等)必须在工作内存中进行
  • 首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝

 

4.Java并发编程三大特性

  • 原子性:相关操作不会中途被其他线程干扰,一般通过同步机制实现。
    • synchronized关键字保证原子性
  • 可见性:一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的。
    • 使用volatile保证多线程场景下的可见性
  • 有序性:保证线程内串行语义,避免指令重排。
    • 有序就是要保证代码按照既定的顺序依次执行。但是CPU(或编译器)出于性能优化的目的,在保证不会对程序运行结果产生影响的前提下,代码的执行顺序可能会和我们既定的顺序不一致
    • 在关键字上有volatilesynchronized可以禁用重排序

5.volatile关键词

  • 轻量级的synchronized,用以实现变量的可见性
  • 如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的

6.等待唤醒方法(配合synchronized使用)

  1. wait() 等待,使当前线程阻塞
    1. 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态
    2. wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒
    3. wait(long timeout)
  2. notity()、notityAll() 唤醒
    1. notify(),唤醒正在等待的单个线程,选择哪个线程取决于操作系统对多线程管理的实现
    2. notifyAll(),唤醒所有等待线程,哪一个线程将会第一个处理取决于操作系统的实现
  3. 注意: wait和notify必须是在同步代码块中,使用锁对象调用
  4. notify wait 的顺序不能错
  5. 为什么wait和notify方法放在Object
  • 因为wait和notify需要使用锁对象来调用,而任何对象都可以作为锁,所以放在Object类中
  1. sleep和wait的区别
  •    sleep睡眠的时候不会释放锁
  •    wait等待的时候会释放锁

7.线程状态

  • NEW
    • 尚未启动的线程处于此状态。
  • RUNNABLE
    • 在Java虚拟机中执行的线程处于此状态。
  • BLOCKED
    • 被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    • 正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING
    • 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    • 已退出的线程处于此状态。

 

8.join与yield、sleep区别

  • join
    • t.join()方法 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程
    • 线程调用了join方法,那么就要一直运行到该线程运行结束,才会运行其他进程
    • 内部使用了synchronized,会占用锁,线程结束,锁释放
    • join(long millis)
      1. 如果为0表示永远等待,其实是等到t结束后。A timeout of {@code 0} means to wait forever.
      2. 传入指定的时间会调用wait(millis), 时间到锁释放。不再等待
  • yield
    • thread.yield() CPU的时间片尽量切换其他线程去执行,作用不大
    • 使正在运行中的线程重新变成就绪状态,并重新竞争 CPU 的调度权。它可能会获取到,也有可能被其他线程获取到
  • yield和sleep的区别

1)优先级:sleep暂停线程后,会给其他线程执行机会,不考虑线程的优先级问题;yield暂停后只有优先级高于或等于当前线程的线程才有执行机会。

2)状态:sleep当前线程由运行态进入阻塞态;yield使当前线程由运行态到就绪态。

3)异常:sleep方法在声明时抛出InterruptedException异常,所以在使用时要么try捕获要么throws抛出;而yield没有声明异常。

4)移植性:sleep的移植性更好。

  • 线程优先级
    • 范围1-10,默认为5
    • 线程的优先级说明在程序中该线程的重要性。系统会根据优先级决定首先使用哪个线程,但这并不意味着优先级低的线程得不到运行
    • t1.setPriority(Thread.MAX_PRIORITY)

 

9.中断线程方法

  • interrupt()
    • 调用该方法的线程的状态将被置为中断状态
    • 注意:线程中断仅仅是置线程的中断状态位,不会停止线程
  • intterupted()
    • 静态方法,查询当前线程的中断状态,并且清除原状态
    • 如果一个线程被中断了,第一次调用 #interrupted() 方法则返回 true ,第二次和后面的就返回 false
  • isInterrupted()
    • 查询线程的中断状态,不会清除原状态
  • 安全终止线程方法
    • 使用标识状态与boolean类型变量一起判断实现

10.synchronized关键词

  • Synchronized通过对象内部的一个叫==监视器锁==来实现的,但是监视器锁本质又是依赖底层的操作系统的Mutex(互斥) Lock来实现
  • 操作系统实现线程之间的切换会造成大量的CPU资源浪费,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因,因此这种依赖于操作系统Mutex Lock所实现的锁我们称之为:==重量级锁==
  • jdk1.5后引入偏向锁和轻量级锁进行优化
  • 三种用法
    • 对象锁
      • 当使用synchronized修饰类普通方法时,那么当前加锁的级别就是实例对象,当多个线程并发访问该对象的同步方法、同步代码块时,会进行同步。
    • 类锁
      • 当使用synchronized修饰类静态方法时,那么当前加锁的级别就是类,当多个线程并发访问该类(所有实例对象)的同步方法以及同步代码块时,会进行同步。
    • 同步代码块
      • 当使用synchronized修饰代码块时,那么当前加锁的级别就是synchronized(X)中配置的x对象实例,当多个线程并发访问该对象的同步方法、同步代码块以及当前的代码块时,会进行同步。
      • 使用同步代码块时要注意的是不要使用String类型对象,因为String常量池的存在,所以很容易导致出问题。
  • 同步代码块
    • 语法:

synchronized(锁对象){

线程安全问题代码

}

  • 哪些对象可以作为锁
    • 任意对象
  • 注意事项
    • 多线程并发方法同步代码块,需要锁同一个对象
  • synchronized中的锁的作用
    • 同步代码块中有锁的线程进入,无锁的线程需要等待
  • 同步方法
    • 普通同步方法,对当前一个实例对象加锁,多线程操作统一个对象实例进行同步操作
    • 静态同步方法,对类加锁,多线程操作当前类所有实例对象进行同步操作

11.ReentranLock可重入锁

  • 对比synchronized
    • synchronized是关键字,lock是接口
    • synchronized 是内部锁,自动化的上锁与释放锁,而lock是手动的,需要人为的上锁和释放锁,lock比较灵活,但是代码相对较多
    • lock接口异常的时候不会自动的释放锁,同样需要手动的释放锁,所以一般写在finally语句块中,而synchronized则会在异常的时候自动的释放锁
    • lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
    • 通过lock可以知道有没有成功获取锁
    • lock可以通过tryLock(timeout)让等待锁的线程超时响应中断,而synchronized却不行
    • 性能上来说,在资源竞争不激烈的情形下,Lock性能稍微比synchronized差点
    • 但是当同步非常激烈的时候,synchronized的性能就会下降几十倍。而ReentrantLock确还能维持常态。
    • 可选择实现公平锁或非公平锁(默认非公平锁)
    • 公平锁底层由AQS实现(面试问)
  • API方法
    • lock()
      • 获取锁,如果所被其他线程获取,处于等待状态。必须主动释放锁,通常Lock操作早于try catch进行,在finally中释放锁,防止死锁发生
    • tryLock()
      • 有返回值,表示尝试获取锁,如果成功,返回true,失败返回false(锁已经被别的线程获取)。优点:获取不到锁不会一直等待
    • tryLock(Long time,TimeUnit unit)
      • 在等待时间内获取到锁返回true,超时返回false
    • unlock()
      • 释放锁,在finally中执行
    • lockInterruptibly()
      • 通过这个这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
  • LockSupport
    • 构建同步组件的基础工具,提供基本的线程阻塞和唤醒功能
    • LockSupport的park和unpark方法都是通过sun.misc.Unsafe类的park和unpark方法实现的
    • 与wait、notify区别
    1. park不需要获取某个对象的锁
    1. 因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理
    1. park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用。
    2. unpark可以唤醒特定线程
  • AQS队列同步器AbstractQueuedSynchronizer
    • 来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作
    • 底层结构:双向链表

 

  • 队列中的线程实现等待唤醒:通过LockSupport
  • AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch
  • Condition
    • 将Object类的wait、notify、notifyAll等操作转化为相应的条件对象操作await、signal、signalAll,将复杂的同步操作转变为直观可控的对象行为
  • 读写锁ReentrantReadWriteLock
    • 一个用来获取读锁,一个用来获取写锁,将文件的读写操作分开
      • 读锁使用共享锁
      • 写锁使用独占锁(排他锁)
    • 优势
      • 大大提升读效率
      • 适用于读多写少的情景
    • 注意
      • 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁
      • 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值