为了保证同一数据在不同线程中被访问的正确性,Java 提供了访问时的锁机制 synchronized,当一个线程获得对象的排他锁,该线程就可以独占该资源,其他线程必须等待,直至有用锁的线程释放锁。但是存在以下问题:
- 一个线程持有锁会导致其他所有需要锁的线程挂起
- 在多线程竞争下,加锁和释放锁会导致比较多的上下文切换和调度延时,降低程序性能
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引发性能问题
1 synchronized 关键字
private关键字帮助我们保证了数据只能被对象的方法访问。synchronized关键字是为了保护对象属性的。保护synchronized 关键字有两种用法:
- synchronized 方法
public synchronized void method(int args) {} - synchronized 代码块
synchronized (Obj) { ... }
1.1 synchronized 方法
synchronized 方法 :public synchronized void method(int args) {}
- 该方法控制对“对象”的访问,每个对象对应一把锁
- 每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则就会阻塞
若将一个大方法声明为
synchronized将会影响效率。所以明确代码临界区后,将临界区代码抽象出来作为**synchronized**方法是较好的做法。public class TestSync implements Runnable {private int numTickets = 10;private boolean flag = true;public static void main(String[] args) {TestSync testSync = new TestSync();new Thread(testSync, "lrt").start();new Thread(testSync, "dhr").start();new Thread(testSync, "yyds").start();}@Overridepublic void run() {while (flag) {try {buy();} catch (InterruptedException e) {e.printStackTrace();}}}private void buy() throws InterruptedException {if (numTickets <= 0) {flag = false;return;}Thread.sleep(100);// Buy a ticket.System.out.println(Thread.currentThread().getName() + " 拿到了第 " + numTickets-- + " 张票");}}
执行结果:
dhr 拿到了第 9 张票yyds 拿到了第 8 张票lrt 拿到了第 10 张票lrt 拿到了第 6 张票 // 重复了yyds 拿到了第 7 张票dhr 拿到了第 6 张票 // 重复了yyds 拿到了第 5 张票dhr 拿到了第 3 张票lrt 拿到了第 4 张票yyds 拿到了第 2 张票 // 重复了lrt 拿到了第 2 张票 // 重复了dhr 拿到了第 1 张票
可以发现出现了线程不安全的情况。
使用synchronized关键字,只需要将临界区方法用synchronized关键字修饰就可以,即将上面的buy()方法改成private synchronized void buy() ...即可。
使用 synchronized 方法的执行结果:
lrt 拿到了第 10 张票lrt 拿到了第 9 张票lrt 拿到了第 8 张票lrt 拿到了第 7 张票lrt 拿到了第 6 张票lrt 拿到了第 5 张票lrt 拿到了第 4 张票lrt 拿到了第 3 张票yyds 拿到了第 2 张票yyds 拿到了第 1 张票
这里发现了一个问题,线程之间基本不会交替运行,而我最开始的几次测试时只有“lrt”线程执行了,其他的线程一直没有得到执行。这其实跟 CPU 的性能有关,如果 CPU 性能比较好,则需要将numTickets变量值增加才能看到线程的交替。
1.2 synchronized 代码块
synchronized 代码块:synchronized (Obj) { ... }Obj被称为同步监视器
- 可以是任何对象,但推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为其监视器就是
this,或者是class(static 方法)
2 Lock 锁

