加锁前要清楚锁和被保护的对象是不是一个层面的

  • 静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁就可以保护


加锁要考虑锁的粒度和场景问题

  • 不要滥用synchronized
  • 如果精细化考虑了锁应用范围后,性能还无法满足需求的话,我们就要考虑另一个维度的粒度问题了,即:区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁。
  • 一般业务代码中,很少需要进一步考虑这两种更细粒度的锁,几个概括的结论:
    • 对于读写比例差异明显的场景,考虑使用 ReentrantReadWriteLock 细化区分读写锁,来提高性能。
    • 如果你的 JDK 版本高于 1.8、共享资源的冲突概率也没那么大的话,考虑使用 StampedLock 的乐观读的特性,进一步提高性能。
    • JDK 里 ReentrantLock 和 ReentrantReadWriteLock 都提供了公平锁的版本,在没有明确需求的情况下不要轻易开启公平锁特性,在任务很轻的情况下开启公平锁可能会让性能下降上百倍。

多把锁要小心死锁问题

  • wrk 压力测试工具,需要自己下载并编译
  • wrk -c 2 -d 10s url 对指定url进行压力测试
  • 死锁问题解决后,wrk的TPS也有明显提升

思考 -线程会不会退出?

我们开启了一个线程无限循环来跑一些任务,有一个 bool 类型的变量来控制循环的退出,默认为 true 代表执行,一段时间后主线程将这个变量设置为了 false。如果这个变量不是 volatile 修饰的,子线程可以退出吗?你能否解释其中的原因呢?

  • 一般情况下,不会退出
  • 如果是volatile修饰的,可以退出
  • 如果希望可以退出,列出ABC中方法
  • 每种方法可以保证主线程的数据变更,可以及时同步到线程内存中。 ``` @Slf4j public class Test { public static void main(String[] args) throws Exception{
    1. NoExit noExit = new NoExit();
    2. new Thread(noExit).start();
    3. TimeUnit.SECONDS.sleep(1);
    4. noExit.flag = false;
    5. //子线程不退出,主线程也不会主动退出
    6. System.out.println("test end");
    } }

class NoExit implements Runnable { boolean flag = true; //代码段A,使用了 volatile关键字,保证可见性 // volatile boolean flag = true; int i = 0;

  1. @Override
  2. public void run() {
  3. while (flag) {
  4. i++;
  5. //代码段B,让线程sleep也可以使工作内存更新,猜测是sleep完成,阻塞 ->就绪 -> runnig 状态会更新工作内存吧

/ try{ TimeUnit.SECONDS.sleep(2); }catch (Exception e){ e.printStackTrace(); }/

  1. //代码段C,输出语句中的println源码加锁了,sync会让当前线程的工作内存失效。

// System.out.println(i++); } System.out.println(“exiting, i = “ + i); } }

```