前言:
在多线程问题中线程安全,线程等待和线程唤醒是至关重要的
在刚开始学习的时候,笔者将 notify wait 和 synchronized 混为一谈,并且为避免使用看了很多博客还是没有看懂的 wait notify 暴力地使用轮询,导致 CPU 占有率过高,电脑风扇呜呜转的直冒烟,本篇笔者通过简单的了解了 notify wait 和 synchronized 之后用于笔记记录,不排除有错误的部分,请谨慎地、批判性的阅读
synchronized
synchronized 关键字是对对象上锁,首先需要澄清一下概念,为什么需要锁?什么是锁?
-
为什么需要锁?
在多个线程并行的过程中,难免会访问一些共有的对象,但是多个线程同时访问(写-写 || 读-写)同一个对象的时候大概率会出现问题,所以需要对该对象进行上锁,即在同一时刻只能有一个线程对该对象进行访问
-
什么是锁?
关于这个问题笔者理解了很久,
笔者一直理解为有钥匙才能开锁,钥匙在哪谁拿着钥匙也不知道怎么确定,最后理解为:锁也是监视器,即程序访问某个对象的时候必须拿到该对象的锁(监视器),对于没有被 synchronized 关键字修饰的对象可以理解为有无数把锁(监视器),任何线程只要想要访问,就可以拿到锁(监视器)去访问,而被 synchronized 关键字修饰的对象限定了该对象只有一把锁(监视器),不同的线程想要同时访问该对象,那就得排队,怎么排队?一个线程完成 synchronized 块中的任务后怎么释放锁?别的线程怎么拿到锁?稍后再细说
synchronized 关键字修饰的对象格式如下:
synchronized (Object o) {
/* do something */
}
在此先介绍对象锁:
对于对象锁,笔者在刚开始学习的时候也有诸多疑问,锁住的对象的作用范围是什么?怎么排队?在何处排队?一个线程完成 synchronized 块中的任务后怎么释放锁?别的线程怎么拿到锁?在一个方法中的锁是否可以与另一个方法中的锁同时锁到同一个对象?笔者接下来将一一介绍:
-
锁住的对象的作用范围是什么?
问题来源于笔者的代码实现,在一个楼房的模型中,应该支持不同的电梯可以同时访问不同的层数,但是不能同时访问同一层,即想要锁楼层。
在最开始的实现中笔者将 synchronized 关键字放在了访问楼层函数的定义上,意为:给这一个函数上锁,并锁住了包含这个函数的整个对象,换言之就是:只能同时有一个线程使用该函数,这与我的设计想法是不同的——如果访问不同的层,是可以进行同时调用该函数的,从而引出我对锁的对象的疑问
在上述问题中:其实实现很简单,即去掉在函数定义中的 synchronized 关键字,将 synchronized 代码块放在函数体,并明确锁的对象为每一层:
public void scanBuilding(EleThread eleThread, int floor) { synchronized (building[floor]) { // 只能有一个线程同时访问该层 /* do something */ } }
届时让笔者比较困惑的点是:
Building 是一个数组,在 synchronized 括号后放 building[floor] 会不会就对整个 building 数组上锁了?答案为否。上锁的作用范围就为括号中的对象,即使对象是比较复杂结构的一部分,锁住该对象仍然支持同时访问该对象处于的复杂结构的其他部分
-
怎么排队?在何处排队?一个线程完成 synchronized 块中的任务后怎么释放锁?别的线程怎么拿到锁?
这几个问题是我将 synchronized 和 wait notify 混淆的重要问题,最后理解为:不同的线程在访问上锁的语句块时,当有线程正在访问时,即进入排队状态,其实笔者是因为有意将"排队"和"等待"区分开,自己定义出了排队的概念,排队的位置即在 synchronized出现的位置,synchronized在函数声明里就在函数入口处排队,在函数内就在函数内 synchronized 的位置处排队,当一个线程退出之后,下一个线程自动进入,上一个线程自动释放锁(不需要人为的 notify,notify 是针对于 wait 的),继续下面的操作,在其他线程排队时,停止其他的操作:相当于被堵住了。
-
在一个方法中的锁是否可以与另一个方法中的锁同时锁到同一个对象?
即现在有两个方法,都对 object 对象上了锁,不同的线程在访问这两个方法的时候如果产生冲突可以依次排队吗?答案是可以的:参考代码:
public class Floor { public int i; public Floor(int i) { this.i = i; } } public class MyThread extends Thread { private int number; public MyThread(int number) { this.number = number; } @Override public synchronized void start() { super.start(); } public void run() { MainClass.visitBuilding(this, number); if(number > 1) { MainClass.visitBuildingPlus(this, number); } } } public class MainClass { public static Floor[] building = new Floor[12]; public static void main(String[] args) { init(); Thread t1 = new MyThread(1); t1.start(); Thread t2 = new MyThread(2); t2.start(); Thread t3 = new MyThread(3); t3.start(); } public static void init() { for (int i = 1; i < 12; i++) { building[i] = new Floor(i); } } public static void visitBuilding(MyThread myThread, int floor) { synchronized (MainClass.building[floor]) { System.out.println("visit " + floor); try { sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("out of " + floor); } } public static void visitBuildingPlus(MyThread myThread, int floor) { synchronized (MainClass.building[floor]) { System.out.println("visitPlus -" + floor); try { sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("outPlus of " + floor); } } }
notify 与 wait
notify 与 wait 是配对出现的,但不一定在同一个代码块中出现,但必须要在同一个锁 (即 synchronized 中的对象是相同的)下出现,notify 的作用是:唤醒一个在此对象监视器上等待的线程
错误的想法:
之前笔者错误的认为 wait 是线程的状态,无论在什么锁下唤醒都可以唤醒…