interview

并发篇

1. 线程状态

要求

六种状态及转换

image-20210831090722658

分别是

其它情况(只需了解)

五种状态

五种状态的说法来自于操作系统层面的划分

image-20210831092652602

2. 线程池

要求

七大参数

  1. corePoolSize 核心线程数目 - 池中会保留的最多线程数
  2. maximumPoolSize 最大线程数目 - 核心线程+救急线程的最大数目
  3. keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
  4. unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
  5. workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  6. threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  7. handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
    1. 抛异常 java.util.concurrent.ThreadPoolExecutor.AbortPolicy
    2. 由调用者执行任务 java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy
    3. 丢弃任务 java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
    4. 丢弃最早排队任务 java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy

image-20210831093204388

代码说明

day02.TestThreadPoolExecutor 以较为形象的方式演示了线程池的核心组成

3. wait vs sleep

要求

一个共同点,三个不同点

共同点

不同点

4. lock vs synchronized

要求

三个层面

不同点

公平锁

ReentrantLock可重入锁

条件变量

代码说明

5. volatile

要求

原子性

可见性

有序性

代码说明

6. 悲观锁 vs 乐观锁

要求

对比悲观锁与乐观锁

// –add-opens java.base/jdk.internal.misc=ALL-UNNAMED public class SyncVsCas { static final Unsafe UNSAFE_9 = Unsafe.getUnsafe(); // CAS 操作需要知道要操作的字段的内存地址 static final long BALANCE = UNSAFE_9.objectFieldOffset(Account.class, “balance”); // 在指定的类中查找指定的字段,并返回该字段在内存中的偏移量 Unsafe.getUnsafe().objectFieldOffset(Account.class, “balance”)

static class Account { // 进行 CAS 操作时通常需要加 volatile 修饰变量,保证该字段的可见性和有序性。 volatile int balance = 10; // 余额 }

private static void showResult(Account account, Thread t1, Thread t2) { try { t1.start(); t2.start(); t1.join(); t2.join(); LoggerUtils.get().debug(“{}”, account.balance); } catch (InterruptedException e) { e.printStackTrace(); } }

public static void sync(Account account) { Thread t1 = new Thread(() -> { synchronized (account) { int old = account.balance; int n = old - 5; account.balance = n; } },”t1”);

Thread t2 = new Thread(() -> {
  synchronized (account) {
    int o = account.balance;
    int n = o + 5;
    account.balance = n;
  }
},"t2");

showResult(account, t1, t2);   }

public static void cas(Account account) { Thread t1 = new Thread(() -> { // 乐观锁 while (true) { int o = account.balance; int n = o - 5; // 比较和设置CAS if (UNSAFE_9.compareAndSetInt(account, BALANCE, o, n)) { break; } } },”t1”);

Thread t2 = new Thread(() -> {
  while (true) {
    int o = account.balance;
    int n = o + 5;
    if (UNSAFE_9.compareAndSetInt(account, BALANCE, o, n)) {
      break;
    }
  }
},"t2");

showResult(account, t1, t2);   }

private static void basicCas(Account account) { while (true) { int o = account.balance; int n = o + 5; if(UNSAFE_9.compareAndSetInt(account, BALANCE, o, n)){ break; } } System.out.println(account.balance); }

public static void main(String[] args) { Account account = new Account(); cas(account); } }

该方法会比较 expectedValue 和当前引用所指向的值,如果相同,则将该引用设置为 newValue。
```java
import java.util.concurrent.atomic.AtomicReference;

public class OptimisticLock {
    private AtomicReference<Object> value = new AtomicReference<>(new Object());

    public Object getValue() {
        return value.get();
    }

    public boolean update(Object expectedValue, Object newValue) {
        return value.compareAndSet(expectedValue, newValue);
    }
}
import groovy.lang.GroovyShell;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
// -XX:MaxMetaspaceSize=24m
// 模拟不断生成类, 但类无法卸载的情况
public class TestOomTooManyClass {

//    static GroovyShell shell = new GroovyShell();

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();
        while (true) {
            try (FileReader reader = new FileReader("script")) {
                GroovyShell shell = new GroovyShell();
                shell.evaluate(reader);
                System.out.println(atomicInteger.incrementAndGet());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

monitor监控(线程阻塞状态)

代码说明

7. Hashtable vs ConcurrentHashMap

要求

更形象的演示,见资料中的 hash-demo.jar,运行需要 jdk14 以上环境,进入 jar 包目录,执行下面命令

java -jar --add-exports java.base/jdk.internal.misc=ALL-UNNAMED hash-demo.jar

Hashtable 对比 ConcurrentHashMap

ConcurrentHashMap 1.7

ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)

ConcurrentHashMap 1.8

8. ThreadLocal

要求

作用

原理

每个线程内有一个 ThreadLocalMap 类型的成员变量,用来存储资源对象

ThreadLocalMap 的一些特点

弱引用 key

ThreadLocalMap 中的 key 被设计为弱引用,原因如下

内存释放时机

记录线程池lambda

public class TestThreadPoolExecutor {

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger c = new AtomicInteger(1);
        // 数组阻塞队列
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                3,
                0, // 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
                TimeUnit.MILLISECONDS,
                queue,
                // ThreadFactory 是一个接口,它提供了一个 newThread 方法,用来创建新的线程。 lambda 表达式实际上就是实现了这个方法。
                // 其中 new Thread(r, "myThread" + c.getAndIncrement()) 将runnable r作为参数传入,创建了一个新线程并启动了它。
                r -> new Thread(r, "myThread" + c.getAndIncrement()),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        Thread thread = new Thread("myThread" + c.getAndIncrement());

        // Thread(Runnable target, String name)

        // 声明具体接口类型 确定lambda表达式所表示的具体抽象方法
        Runnable rr = () -> {};

        // 错误的写法,传入的参数Runnable r没有起到任何作用。实现该接口时应该使用方法里的固定的参数(Runnable r)实现约定的抽象方法(返回Thread对象)
        ThreadFactory threadFactory1 = r -> new Thread(() -> {},"");
        // 同样是错误的
        ThreadFactory threadFactory11 = r -> new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println();
            }
        }, "");


        // 正确的写法t2和t22。对于有参数的抽象方法可省略小括号(直接定义参数名r箭头指向->方法体),一行代码返回时可省略大括号{}和里面的return。
        ThreadFactory t2 = r -> {System.out.println(true);return new Thread(r, "");};
        ThreadFactory t22 = new ThreadFactory() {
            @Override
            public Thread newThread(@NotNull Runnable r) {
                return new Thread(r ,"");
            }
        };

        Thread thread1 = new Thread("");
        ThreadFactory threadFactory3 = r -> new Thread("");

        // Thread(Runnable target, String name) {
        Thread t222 = new Thread(() -> {logger1.debug("before waiting"); },"");

        showState(queue, threadPool);
        threadPool.submit(new MyTask("1", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("2", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("3"));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("4"));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("5", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("6"));
        showState(queue, threadPool);
    }

    private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) {
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        List<Object> tasks = new ArrayList<>();
        for (Runnable runnable : queue) {
            try {
                Field callable = FutureTask.class.getDeclaredField("callable");
                callable.setAccessible(true);
                Object adapter = callable.get(runnable);
                Class<?> clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter");
                Field task = clazz.getDeclaredField("task");
                task.setAccessible(true);
                Object o = task.get(adapter);
                tasks.add(o);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        main.debug("pool size: {}, queue: {}", threadPool.getPoolSize(), tasks);
    }

    static class MyTask implements Runnable {
        private final String name;
        private final long duration;

        public MyTask(String name) {
            this(name, 0);
        }

        public MyTask(String name, long duration) {
            this.name = name;
            this.duration = duration;
        }
        @Override
        public void run() {
            try {
                LoggerUtils.get("myThread").debug("running..." + this);
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Override
        public String toString() {
            return "MyTask(" + name + ")";
        }
    }
}