Java并发编程的艺术-读书笔记

多线程不一定比单线程快

当累加操作少于百万次时,单线程执行的速度会比多线程执行的速度快,因为线程有创建和上下文切换的开销
vmstat的cs表示每秒上下文切换的次数

如何减少多线程上下文切换次数

使用无锁并发编程,CAS算法,使用最少线程和使用协程

死锁

死锁样例:

public class DeadLockDemo {

    /** A锁 */
	private static String A = "A";
    /** B锁 */
	private static String B = "B";

	public static void main(String[] args) {
		new DeadLockDemo().deadLock();
	}

	private void deadLock() {

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (A) {
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (B) {
						System.out.println("1");
					}
				}
			}
		});

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                    synchronized (A) {
                        System.out.println("2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

现实场景中,比如t1拿到锁之后,因为一些异常情况没有释放锁(死循环)。又或者是t1拿到一个数据库锁,释放锁的时候抛出了异常,没释放掉,就会出现死锁

避免死锁的常用办法

  1. 避免一个线程同时获取多个锁。
  2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
  4. 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

volatile的应用

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性
可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值

volatile定义与实现原理

java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的

volatile会增加Lock前缀指令

Lock前缀指令会引起处理器缓存回写到内存
一个处理器的缓存回写到内存会导致其他处理器的缓存无效

synchronized

JVM基于进入和退出Monitor对象来实现方法同步和代码块同步
代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的

锁的4种状态

无锁,偏向锁,轻量级锁,重量级锁
锁可以升级不能降级,目的是为了提高获得锁和释放锁的效率

单例模式中的双重检测锁

public class UnsafeLazyInitialization {
  private static Instance instance;
  public static Instance getInstance() {
    if (instance == null)			// 1:A线程执行
        instance = new Instance();	// 2:B线程执行
        return instance;
  }
  static class Instance {
  }
}

这样初始化对象时会出现指令重排,导致取得instance对象时没有初始化完成
解决办法有两个,一个是禁止指令重排序,二是让指令重排序不让外面知道
1.instance类加上volatile关键字
2.利用JVM在类的初始化阶段(在class被加载后,且被线程使用之前),会执行类的初始化
在执行类的初始化期间,jvm会获取一个锁,这个锁可以同步多个线程对同一个类的初始化

上一篇:2021最新「阿里」Java高级工程师面试高频题


下一篇:日常新10.26