Java 并发笔记

1 并发编程三要素

Java 并发编程三要素_新猿一马的博客-CSDN博客并发编程三要素学习。https://blog.csdn.net/jack1liu/article/details/124310132

2 死锁产生和解决

死锁以及避免死锁_新猿一马的博客-CSDN博客主要记录死锁是什么,产生死锁的原因以及如何解决死锁问题。https://blog.csdn.net/jack1liu/article/details/97574883

3 创建线程的方式

创建线程的四种方式_新猿一马的博客-CSDN博客_线程的创建四种创建方式及其比较。https://blog.csdn.net/jack1liu/article/details/112973372

线程的生命周期

Java线程生命周期_新猿一马的博客-CSDN博客Java线程生命周期https://blog.csdn.net/jack1liu/article/details/124312463

5 深入理解和运用线程池

深入理解和运用线程池_新猿一马的博客-CSDN博客目录一 什么是线程池二 为什么需要线程池三 创建线程池的四种方式四 execute() 方法执行过程一 什么是线程池 线程池就是线程集合的池子,是一种将任务添加到队列,然后创建线程后自动启动这些任务的流程。二 为什么需要线程池 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能...https://blog.csdn.net/jack1liu/article/details/97046623

6 ThreadLocal 理解和源码分析

ThreadLocal 理解和源码分析_新猿一马的博客-CSDN博客目录一 ThreadLocal 是什么二 ThreadLocal 实现原理三 ThreadLocal 应用场景四 参考文档一 ThreadLocal 是什么 ThreadLocal 本地线程变量,通过 set() 和 get() 方法来维护局部变量。不同线程之间局部变量彼此隔离,互不影响。二 ThreadLocal 实现原理2.1 我们可以先来看看 Th...https://blog.csdn.net/jack1liu/article/details/97614116

单例模式的线程安全性

01-单例还是多例_新猿一马的博客-CSDN博客目录一 什么是单例模式二 使用单例模式的优点三 单例模式的实现方式3.1 懒汉式-基础版3.2懒汉式-线程安全版3.3 懒汉式-双重检查加锁版3.4 饿汉式3.5Holder模式3.6 枚举一 什么是单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点。二 使用单例模式的优点减少了类的频繁创建,降低了系统资源开销。类...https://blog.csdn.net/jack1liu/article/details/101421259

synchronized 的实现原理

[死磕 Java 并发] --- 深入分析synchronized的实现原理 - Java 技术驿站http://cmsblogs.com/?p=2071

8.1 自旋锁

什么是自旋?

执行一段无意义的循环。

为什么需要自旋锁?

许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。 何谓自旋锁? 所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。

自旋锁有啥问题么?

自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,典型的占着茅坑不拉屎,这样反而会带来性能上的浪费。

在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整。

8.2 自适应自旋锁

所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢?

线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。

反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。 有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测会越来越准确,虚拟机会变得越来越聪明。

8.3 锁消除

为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制。

有些情况下,JVM 检测到不可能存在共享数据竞争,JVM 会对这些同步锁进行锁消除。

public void vectorTest(){
    Vector<String> vector = new Vector<String>();
    for(int i = 0 ; i < 10 ; i++){
        vector.add(i + "");
    }

    System.out.println(vector);
}

在运行这段代码时,JVM 可以明显检测到变量 vector 没有逃逸出方法 vectorTest() 之外,所以JVM 可以大胆地将 vector 内部的加锁操作消除。

8.4 锁粗化

我们知道在使用同步锁的时候,需要让同步块的作用范围尽可能小。仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。

在大多数的情况下,上述观点是正确的,LZ 也一直坚持着这个观点。但是如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗。为了减少性能损耗,引入锁粗化的概念。

锁粗话概念比较好理解,就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。如上面实例:vector 每次 add 的时候都需要加锁操作,JVM 检测到对同一个对象(vector)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到 for 循环之外。

volatile的实现原理

[死磕 Java 并发] --- 深入分析volatile的实现原理 - Java 技术驿站http://cmsblogs.com/?p=2092

10 深入分析CAS

[死磕 Java 并发] --- J.U.C之并发工具类:CyclicBarrier - Java 技术驿站http://cmsblogs.com/?p=2235

10.1 CAS缺陷

CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:

循环时间太长、只能保证一个共享变量原子操作、ABA问题

  • 循环时间太长

如果自旋 CAS 长时间地不成功,则会给 CPU 带来非常大的开销。在 JUC 中有些地方就限制了CAS 自旋的次数,例如 BlockingQueue的SynchronousQueue。

  • 只能保证一个共享变量原子操作

看了 CAS 的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了,当然如果你有办法把多个变量整成一个变量,利用 CAS 也不错。例如读写锁中 state 的高地位。

  • ABA问题

CAS 需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是 A,变成了 B,然后又变成了 A,那么在 CAS 检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的 ABA 问题。对于 ABA 问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即 A —> B —> A,变成 1A —> 2B —> 3A。

Java 提供了 AtomicStampedReference 来解决。AtomicStampedReference 通过包装 [E,Integer]的元组来对对象标记版本戳 stamp,从而避免ABA问题。

11 深入分析 AQS

[死磕 Java 并发] --- J.U.C之AQS:AQS简介 - Java 技术驿站Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略(【死磕Java并发】-https://www.cmsblogs.com/article/1391297814356692992[死磕 Java 并发] --- J.U.C之AQS:CLH同步队列 - Java 技术驿站在上篇博客【死磕Java并发】-----J.U.C之AQS:AQS简介中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。CLH同步队列是一个FIFO双向队列,https://www.cmsblogs.com/article/1391297829913366528

AQS 使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。

它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。

12 重入锁 ReentrantLock

[死磕 Java 并发] --- J.U.C之重入锁:ReentrantLock - Java 技术驿站此篇博客所有源码均来自JDK1.8ReentrantLock,可重入锁,是一种递归无阻塞的同步机制。它可以等同于synchronized的使用,但是ReentrantLock提https://www.cmsblogs.com/article/1391297847235842048

12.1 ReentrantLock 与 synchronized 的区别

 首先他们肯定具有相同的功能和内存语义。

  1. 与 synchronized 相比,ReentrantLock 提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
  2. ReentrantLock 还提供了条件 Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合(以后会阐述Condition)。
  3. ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而 synchronized 则一旦进入锁请求要么成功要么阻塞,所以相比synchronized 而言,ReentrantLock 会不容易产生死锁些。
  4. ReentrantLock 支持更加灵活的同步代码块,但是使用 synchronized 时,只能在同一个synchronized 块结构中获取和释放。注:ReentrantLock 的锁释放一定要在 finally 中处理,否则可能会产生严重的后果。
  5. ReentrantLock 支持中断处理,且性能较 synchronized 会好些。

13  常用的并发工具类有哪些

  • CountDownLatch

[死磕 Java 并发] --- J.U.C之并发工具类:CountDownLatch - Java 技术驿站此篇博客所有源码均来自JDK1.8在上篇博客中介绍了Java四大并发工具一直的CyclicBarrier,今天要介绍的CountDownLatch与CyclicBarrier有https://www.cmsblogs.com/article/1391297877455802368

CountDownLatch 主要用来解决一个线程等待多个线程的场景。

  • CyclicBarrier

[死磕 Java 并发] --- J.U.C之并发工具类:CyclicBarrier - Java 技术驿站此篇博客所有源码均来自JDK1.8CyclicBarrier,一个同步辅助类,在API中是这么介绍的:它允许一组线程互相等待,直到到达某个公共屏障点(commonbarrierhttps://www.cmsblogs.com/article/1391297871143374848

CyclicBarrier 是一组线程之间互相等待

13.1 CountDownlatch 与 CyclicBarrier 对比

  1. CountDownLatch 的作用是允许1或 N 个线程等待其他线程完成执行;而 CyclicBarrier 则是允许 N 个线程相互等待。
  2. CountDownLatch 的计数器无法被重置;CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier。

  • Semaphore

[死磕 Java 并发] --- J.U.C之并发工具类:Semaphore - Java 技术驿站此篇博客所有源码均来自JDK1.8信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。Semaphore,https://www.cmsblogs.com/article/1391297883185221632

  • Exchanger

[死磕 Java 并发] --- J.U.C之并发工具类:Exchanger - Java 技术驿站此篇博客所有源码均来自JDK1.8前面三篇博客分别介绍了CyclicBarrier、CountDownLatch、Semaphore,现在介绍并发工具类中的最后一个Exchanhttps://www.cmsblogs.com/article/1391297888105140224

14 乐观锁和悲观锁

乐观锁与悲观锁_还能坚持的博客-CSDN博客_乐观锁和悲观锁1、悲观锁1.1 定义总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。手动加悲观锁:读锁:LOCK tables test_db read,释放锁:https://blog.csdn.net/qq_35091353/article/details/116299465

14.1 悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

  • mysql 的行锁、表锁、读锁、写锁,都是在做操作之前先上锁。
  • Java 中 synchronized 和 Lock 也是悲观锁

14.2 乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下,在此期间有没有别人去更新这个数据,可以使用版本号机制和CAS算法实现。

  • 乐观锁适用于多读的应用类型,这样可以提高吞吐量。
  • 乐观锁一般会使用版本号机制或CAS算法实现。

15 深入理解 ConcurrentHashMap

[死磕 Java 并发] --- J.U.C之Java并发容器:ConcurrentHashMap - Java 技术驿站此篇博客所有源码均来自JDK1.8HashMap是我们用得非常频繁的一个集合,但是由于它是非线程安全的,在多线程环境下,put操作是有可能产生死循环的,导致CPU利用率接近10https://www.cmsblogs.com/article/1391297896166592512

16 Sleep 和 Wait 

16.1 sleep 的理解

sleep 的作用是让线程休眠指定的时间,在时间到达时恢复。也就是说 sleep 将在时间到达事件事恢复线程执行。

16.2 wait 的理解

调用 wait 方法将会将调用者的线程挂起,直到其他线程调用同一个对象的 notify 方法才会重新激活调用者。

16.3 sleep 与 wait 差异

1、来自不同的类:sleep 是 Thread 的静态类方法,wait 是 Object 类的方法。

2、有没有释放锁:sleep 方法没有释放锁,而 wait 方法释放了锁。

3、wait 要等待其他线程调用 notify/notifyAll 唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep 可以用时间指定使它自动唤醒过来,如果时间不到只能调用 interrupt() 强行打断。 

4、sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常。

5、使用范围:wait 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用。

17 其他

多线程同步有哪几种方法?

Synchronized 关键字,Lock 锁实现,分布式锁等。

线程的调度策略

线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:
(1)线程体中调用了 yield 方法让出了对 cpu 的占用权利
(2)线程体中调用了 sleep 方法使线程进入睡眠状态
(3)线程由于 IO 操作受到阻塞
(4)另外一个更高优先级线程出现
(5)在支持时间片的系统中,该线程的时间片用完

怎么唤醒一个阻塞的线程

如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。

不可变对象对多线程有什么帮助

前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行
额外的同步手段,提升了代码执行效率。

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

抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值