并发

同一个对象被多个线程同时操作

线程同步

解决方案:队列 + 锁
线程同步其实是一种等待机制,多个需要同时访问该对象的线程进入 这个对象的 等待池,形成队列。等待前面线程使用完毕,下一个线程再使用。

关键词 synchronized

存在的问题:

  • 导致性能下降,一个线程持有锁,会导致其他所有需要此锁的线程挂起
  • 多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待优先级低的线程释放锁,会导致优先级倒挂,引起性能问题。
  1. // 用在代码块上
  2. synchronized() {
  3. // 同步代码块
  4. }
  5. // 用在方法声明上, 一定是用this对象锁,表示整个方法体都需要同步。
  6. // 可能会无故扩大同步范围,导致程序执行效率降低,所以不太常用。
  7. public synchronized void withdraw() {
  8. ...
  9. }
  10. // 在静态方法上使用synchronized,找的是类锁
  11. // 一个对象一把锁,100个对象100把对象锁。但是100个对象,只有一个类锁
  12. public synchronized static void withdraw() {}

synchronized后面小括号中的数据是多线程共享的数据,才能达到多线程排队。
哪些变量

  • 实例变量:堆中
  • 静态变量:方法区中
  • 局部变量:栈中
  • 常量:不可修改

常量不可修改,局部变量不共享,所以局部变量和常量永远不会有 线程安全问题。但是实例变量和静态变量可能存在线程安全问题。
同步范围越小越好

所以在局部变量时,建议使用StringBuilder,因为局部变量不存在线程安全问题,StringBuilder也没有同步机制

等待/通知机制

需要注意的是等待/通知机制使用的是必须使用同一个对象锁,如果你两个线程使用的是不同的对象锁,那它们之间是不能用等待/通知机制通信的

synchronized版

  1. package com.liangwei.kuang.demo02;
  2. /**
  3. * 线程间通信问题:生产者、消费者模型! 等待唤醒,通知唤醒
  4. * 线程交替执行,生产者 和 消费者 操作同一个变量 num=0
  5. * 生产者 生产 使 num++
  6. * 消费者 消费 使 num--
  7. */
  8. public class Demo02 {
  9. public static void main(String[] args) {
  10. Data data = new Data();
  11. new Thread(()->{
  12. for (int i = 0; i < 10; i++) {
  13. try {
  14. data.increment();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }, "producer").start();
  20. new Thread(()->{
  21. for (int i = 0; i < 10; i++) {
  22. try {
  23. data.decrease();
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }, "consumer").start();
  29. }
  30. }
  31. /**
  32. * 生产者、消费者模型:等待、业务、通知
  33. */
  34. class Data {
  35. private int num = 0;
  36. public synchronized void increment() throws InterruptedException {
  37. if(num != 0) { // 如果num不为0,就等待,num为0就工作
  38. // 等待
  39. this.wait();
  40. }
  41. num++;
  42. System.out.println(Thread.currentThread().getName() + "->" + num);
  43. // 通知
  44. this.notify();
  45. }
  46. public synchronized void decrease() throws InterruptedException {
  47. if(num == 0) { // 如果num为0就等待,不为0就开始工作
  48. // 等待
  49. this.wait();
  50. }
  51. num--;
  52. System.out.println(Thread.currentThread().getName() + "->" + num);
  53. // 通知
  54. this.notify();
  55. }
  56. }

问题:如果有四个线程的情况下A,B, C, D,两个生产两个消费 —-》 虚假唤醒问题

image.png

CPU底层的唤醒实现机制,注定了虚假唤醒是存在的。因此,在 if 语句下,如果线程被虚假唤醒了,但是又没有获得锁,会被