java 并发 上下文切换,volatile,synchronized
程序员文章站
2022-05-31 08:33:37
...
1.上下文切换
上下文切换说的是CPU在执行不同任务之间的切换过程叫做上下文切换(若是体现在线程上就是线程状态变更那么就是一次上下文切换)。上下文切换是需要耗费时间的,这就是我们在并发编程中要考虑的情况若是上下文切换时间太长那么多线程反而变慢了。
下面是测试代码:
/** * Created by coffice on 2017/10/20. */ public class SimpleTest { private static final long count = 100000000; private static void concurrency () throws InterruptedException { long start = System.currentTimeMillis(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { int a = 0; for (long i = 0; i < count; i++) { a += 5; } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { int a = 0; for (long i = 0; i < count; i++) { a += 5; } } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); long end = System.currentTimeMillis(); System.out.println("concurrency: " + (end-start) ); } private static void serial () { long start = System.currentTimeMillis(); int a = 0; for (long i = 0; i < count; i++) { a += 5; } int b = 0; for (long i = 0; i < count; i++) { b += 5; } long end = System.currentTimeMillis(); System.out.println("serial: " + (end-start) ); } public static void main (String args[]) throws InterruptedException { concurrency(); serial(); } }测试结果
循环次数 | 串行执行时间 | 并发执行时间 | 并发比执行 |
1亿 | 74 | 40 | 快 |
1千万 | 11 | 7 | 快 |
1百万 | 6 | 5 | 慢 |
10万 | 3 | 3 | 一样 |
1万 | 0 | 1 | 慢 |
当运行次数很少的时候,上下文切换影响比重就大很多,那么如何解决这个问题,很显然就需要减少上下文切换的次数。线程从wating->runnable就要切换一次上下文,只要减少线程的waiting即可避免多的上下文切换。
- 无锁并发
- CAS 算法代替锁(比较并交换算法)
AtomicInteger integer = new AtomicInteger(); final boolean b = integer.compareAndSet(1, 2);
- 线程不能创建太多,所以多数以线程池来管理
3. synchronized
3.1 偏向锁
它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用)因此 流程是这样的 偏向锁->轻量级锁->重量级锁
3.2 轻量级锁
3.2.1 自旋
遇到竞争时我先不阻塞,我先自我陶醉一下,看看在陶醉时间内能不能获得竞争锁,不成功再阻塞(这对那些执行时间很短的代码块来说有非常重要的性能提高,在这短时间内自旋能够获取竞争锁),尽量降低阻塞的可能性进而减少上下文切换,提高效率,因此自旋的时间应当小于一次上下文切换时间。
1.6以后引入了 "偏向锁"和 "轻量级锁"加上重量级锁一共3种锁,锁一共4种状态 由低到高:无状态锁、偏向锁状态、轻量级锁和重量级锁,这几个状态随着竞争逐渐升级。
锁 | 优点 | 缺点 | 使用场景 |
偏向锁 | 加锁和解锁不需要额外消耗,和执行非同步方法相比仅存在纳秒级差距 | 若线程存在竞争则会带来额外的锁撤销的消耗 | 适用于一个线程访问同步模块 |
轻量级锁 | 竞争的线程不会阻塞,提高线程访问速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应时间,同步块执行速度快 |
重量级锁 | 竞争线程不使用它自旋,不消耗CPU | 线程阻塞响应时间缓慢,同步块执行时间长 | 追求吞吐量 |