线程同步

线程安全问题:

如何解决:当一个线程a在操作某一资源时,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作资源。这种情况即使线程a出现了阻塞,也不能被改变。

在Java中,我们通过同步机制,来解决线程的安全问题。

同步代码块

同步代码块的格式:

  1. Synchronized(同步监视器)
  2. {
  3. 需要被同步的代码;
  4. }

线程同步与通信 - 图1

  • 这个同步监视器(锁)可以由object或this来填充,
  • 同步生效的前提是程序的多个线程共用这同一把锁
  • Synchronized确保了同一时刻只有一个对象能运行同步代码块中的代码。

同步方法

使用Synchronized作为修饰符进行修饰的函数,同样可以做到同步代码块的效果。
则这个方法运行时,即为synchronized的

线程同步与通信 - 图2

同步函数的锁有两种情况:
1) 非静态函数:所采用的锁是this
2) 静态函数:所采用的锁是该函数所属字节码文件对象,可以使用obj.getClass()或 class_name.class 获取。

Lock锁

Java还可以通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

  • ReentrantLock类实现了Lock,它拥有与 synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁、释放锁。

步骤:
1、实例化一个锁
private ReetrantLock lock = new ReetrantLock();

2、加锁(在该方法后面的代码相当于被加了synchronized)
lock.lock();

3、解锁
lock.unlock();


线程间通讯

线程间通讯方法:

  1. wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  2. notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  3. notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

注意:这些方法都必须定义在同步中,因为这些方法都用于操作线程状态的方法。
因为必须要明确到底操作的是哪个锁上的线程(进来的线程是那个,锁的对象就是哪个)

线程间通讯使用场景:

多个线程在处理统一的资源,但是任务却不同。
线程同步与通信 - 图3

生产者消费者模型


版本v0.1_单实例生产者消费者模型

线程同步与通信 - 图4
采用方法:synchronized、obj.notify、obj.wait
这样解决了单实例(一个生产者一个消费者的问题)



版本v0.2_多实例生产者消费者模型

线程同步与通信 - 图5
当多个生产者消费者时,由于此前使用的notify(),具有随机性
所以当使用的是if判断时,我们会发现,当需要唤醒输出方,却把本方线程唤醒了
就会导致线程安全问题



版本v0.3_while+notifyAll循环版

线程同步与通信 - 图6

  • 由于if只判断一次,线程苏醒时,不需要再次判断,就会导致线程安全问题,而使用while,苏醒后,继续判断flag,就解决了问题
  • notify由于其不确定性,改为使用notifyAll,虽然会使得所有线程都苏醒,但配合while就没啥影响





总结:


wait(),notify(),notifyAll(),用来操作线程为什么定义在Object类中?而不是所属的类(Thread)中?

  • 这些方法存在与同步中:
  • 使用这些方法时必须要标识所属的同步的锁。
  • 锁可以是任意对象,所以任意对象调用的方法一定定义Oblect类中