和之前一篇多线程文章类似,这里没有什么逻辑顺序就是一些知识点,也不是很深入,见谅!
1、不是有越多的线程程序运行的越快,因为在并发编程时,会有更多的上下文切换,死锁等问题。
2、上下文切换:CPU通过时间片 分配算法来循环执行任务,当前任务执行完一个时间片之后会切换到下一个任务,但是又要保存上一个任务的状态,以便下一次可以加载到上一状态,所以任务从保存到在加载的过程叫做上下文切换。
3、如何减少上下文切换?
无锁并发编程:多线程因为有所会出现很多竞争,所以尽量避免锁,EG:将数据的ID按照hash算法取模分段,不同的线程处理不同的段的数据。
CAS算法:Java的Atomic包使用CAS算法来更新数据,不需加锁。
使用最少线程:避免创建不需要的线程。
协程:在单线程里实现多任务调度,并在单线程里维持多个任务间的切换。
4.避免死锁的办法:
避免一个线程同时获得多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
Java并发机制的底层实现、
Java代码编译后会变成Java字节码,字节码被类加载器加载到jvm里,jvm执行字节码,最终转换成汇编指令在CPU上运行,Java所使用的并发 机制依赖jvm的实现和CPU的指令。
volatile
volatile的应用:volatile是轻量级的Synchronized,他保证了共享变量的可见性,(可见性是指:当一个线程修改一个共享变量的值时,另一个线程可以读取这个修改的值)、恰当使用volatile会比Synchronize效率高,因为volatile不会有上下文切换和调度。
Volatile的定义:允许线程访问共享变量,为了确保共享变量可以一致的更新,应该通过排它锁单独的获得这个变量。
1.Volatile原理:1.Lock前缀指令会引起处理器缓存写到内存:
2.一个处理器的缓存写到内存会导致其他处理器的缓存无效。
volatile的优化:jdk7中增加了一个队列集合类:LinkedTransferQueue 他在使用volatile变量时,通过追加字节的方法来优化队列的出栈和入栈性能。
那么他是如何优化的呢?原来缓存行的大小是64字节,所以使用追加的方法填充缓存行,这样的话就避免了头结点和尾节点在一行,这样在修改时也不会相互锁定。
在两种情况下不能用这种优化方法:1,当缓存行的大小不是64,2.当没有对共享变量频繁的写
Synchronize
Synchronize也被称为重量级锁,因为在获得锁和释放锁都会带来性能消耗
Synchronize实现锁有三种表现形式:
1.对于普通的同步方法,锁的是当前的实例变量
2.对于静态的同步方法,锁的是当前类的class对象
3.对于同步同步方法块,锁的是{}配置的对象。
锁的升级与对比:为了减少锁申请和释放带来的性能消耗,引入了“偏向锁”“轻量级锁”。
锁一共有四种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。
锁可以升级但是不能降级。
偏向锁:由于大多数情况下锁其实不具有竞争状态,所以出现了该锁,基本的思想是:当一个线程访问一个同步块并且获取锁时,会在对象头和帧栈中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出该同步块时,不需要用CAS来加锁和解锁,只需要简单测试一下对象头里的MarkWord里是否存了指向当前线程的偏向锁,要是有,表示已经获得锁;
偏向锁的撤销,使用了一种等到有竞争才释放的机制,也就是说,当其他线程请求的时候才会释放。同时释放的时候需要等到安全点,即在这个点上没有正在执行字节码。偏向锁是自动开启的,要实现两关闭,则通过-XX:BiasedLockingStartupDelay=0来设置。设置之后就默认进入轻量级锁。
锁的优缺点:
锁 | 优点 | 缺点 | 适用场景 |
偏向锁 | 加锁和解锁不需要额外的开销,和执行费同步方法只有纳秒级的差别。 | 若线程之间存在锁竞争,会带来额外的锁撤销的消耗。 | 适用于只有一个线程访问同步块的场景。 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响速度 | 如果始终得不到锁竞争的线程会陷入自旋,会消耗CPU | 追求响应时间 同步块执行速度非常快 |
重量级锁 | 线程竞争不会自旋 | 线程阻塞,响应时间慢 | 追求吞吐量 同步块执行速度较长 |
原子操作
原子操作是不能被分割的一系列操作。
基于缓存行加锁和总线加锁的方式来实现原子操作
缓存锁锁掉了内存和CPU之间的通信,开销比较大,所以现在用的是缓存行加锁。
但有两种情况不会使用缓存行锁:
操作的数据不能被缓存或则一个缓存行不够存储
有些处理器不支持缓存锁定
Java如何实现原子操作:可以通过锁和循环CAS来实现。