Java多线程 基础篇

第一章 线程简介

多线程问题:

  1. 安全性问题
    1. 由于多线程的交替执行,造成结果不对,称之为竞态条件,多个线程并发执行,可能会修改或访问其他线程正在使用的变量,即共享变量。
  2. 活跃性问题
    1. 无限循环,造成后面代码无法执行
  3. 性能问题
    1. 线程切换会有上下文切换操作,开销极大:保存和恢复上下文,丢失局部性。

多线程优势:

  1. 发挥多处理器的能力
  2. 容易建模
  3. 异步任务的处理更简单

 

第二章 线程的安全性

 

对象的状态:存储在状态变量中的数据。

 

线程安全性

多线程并发访问某个类时,不管什么情况,该类都能表现出正确的行为。

  • 无状态的类一定是线程安全的

 

 

什么时候线程不安全:

 

1.原子性遭到破坏:

竞态条件

当某个计算结果取决于多线程的交替执行时,就会发生竞态条件

 

  • 先检查后执行,即通过一个可能失效的观测结果决定下一步动作
  • 延迟初始化

2.复合操作:

即便是多个原子操作的复合操作,线程也不一定是安全的 存在竞态条件时,就会破坏线程安全性

 

 

加锁机制

 

1.内置锁,即同步代码块,synchronized关键字

性能低 java对象作为锁

 

2.重入——内置锁是可重入的

获取内置锁的线程可多次获取锁对象

一种实现方法是:为锁关联一个计数器和锁的持有者线程,持有+1,释放-1.

 

3.当执行较长的计算或者可能无法快速完成的操作时,不要持有锁。

 

死锁和饥饿

死锁:A锁要B锁,B锁要A锁,你等我 我等你 AB永远都获得不到锁

饥饿:一直占用不到要获取的资源,但是有可能会被执行,和死锁最大的区别就在于死锁一定死掉

悲观锁和乐观锁

悲观锁认为不同线程间会发生并发问题,所以每个线程占用共享资源时都加锁,synchronized关键字

乐观锁则认为不同线程间发生并发问题的几率极小,不会加锁,但乐观锁是CAS锁,先比较再赋值,如果检测到了冲突 则回滚数据,不会发生数据并发问题。Automic包下的基本数据类型

线程中断interrupt

Thread.interrupt()方法并不能让线程自己中断,他只是给线程一个中断标记,以让线程在运行的时候根据该标记判断线程是否应该终止

当线程调用sleep方法时,会抛出Interrupt异常,当调用interrupt方法时,给线程一个中断标记,此时因为线程在睡眠触发了异常,那么进入cathch代码块,并清除了线程的中断标记,所以要想在此还想中断线程,就需要再调用一次Interfrupt方法

多线程优化

1.减少锁的占用时间

尽量减少锁执行的代码量,如果有些代码不需要加锁不涉及资源的竞争 就没有必要存在于加锁范围内,所以应该减少使用在方法上的synchronized使用

2.锁细化(锁的代码尽量短)

ConcurrentHashMap的实现原理,就是将数据分段,在put方法时,根据key值查找对应的 分段,然后对该分段进行加锁,而不是全局加锁,默认可以支持16个分段。 也就是说最大可支持16个线程并发。 但是缺点是当需要对全局加锁的时候就比较麻烦。需要对16个分段依次加锁 修改然后释放锁, 比如ConcurrentHashMap的size方法

3.使用读写锁

在读远远大于写的时候,可以使用读写锁

4.锁分离

LinkeBlockingQueue 中的put和take实现 是用的两个不同的锁,由于LinkeBlockingQueue 是链表结构,操作头和尾的时候理论上并不冲突,做到take和put的并行操作 至于原理是为什么 需要进一步研究 ArrayBlockingQueue 就不是这么实现的必须加同一把锁 源码很明了

5.锁粗化

上面讲了要锁细化,现在又说锁粗化,很矛盾。 锁粗化是指:当很多锁对同一资源进行了加锁,还不如在同一个锁中进行操作。 所以需要看情况加锁啊

 

虚拟机对锁优化的贡献

1.锁偏向

不太理解它的作用和机制

说是若一个线程获得了锁,则下次获得锁就不用做任何操作,减少了申请锁的消耗

背景:jdk1.6对synchronize锁进行了优化,hotspot虚拟机的作者发现在锁竞争的时候很少会出现真的竞争,且总是由同一线程多次获得。

 

锁的对象头中会记录一些关于锁的数据信息,这个对象是指被作为锁的那个对象,synchronize加载方法上就是当前对象实例,静态方法中就是class对象,代码块中就是指定的那个对象

 

当一个线程尝试获取锁时,先要去MarK World中查找是否有偏向锁指向了本线程(对象头记录线程了id)

  • 若无 则再去判断此时锁的粒度,是否是偏向锁的记录是否为1
    • 若是 则尝试使用CAS方式将偏向锁指向本线程
    • 若不是 则尝试使用cas方式获取锁
  • 若有 则说明线程已经持有锁 不需cas操作 放行即可

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

2.轻量级锁

若偏向锁失败,对象头部会有一个指针指向线程堆栈内部,以判定线程是否持有对象的锁 如果轻量级锁加锁失败,则膨胀为重量级锁,需要阻塞等待

3.自旋锁

膨胀后,为了避免线程挂起(阻塞),尝试自旋

4.锁消除

当一个场景必然不会出现资源竞争时,加了锁,那就没有必要,JVM会将此种加锁干掉 比如在方法内部使用StringBuffer,Vector 原理为:逃逸分析

 

volatile的实现原理

Java代码: instance = new Singleton();//instance是volatile变量 汇编代码: 0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);

有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情。

  • 将当前处理器缓存行的数据会写回到系统内存。
  • 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值