JAVA synchronized关键字 作者:马育民 • 2020-01-29 12:01 • 阅读:10069 # 概述 synchronized关键字,被synchronized修饰的区域,**只有一个线程** 可以访问,用来实现 **线程同步**,处理 多线程 访问修改 共享资源。也就是:**线程安全** synchronized通过锁机制实现 ### 使用方式 1. 修饰方法 2. 修饰代码块 通过修饰this实现 4. 修饰对象 通过修饰代码块实现。通过修饰 其他对象 实现 3. 修饰类 通过修饰代码块实现。通过修饰 当前类名.class 实现 4. 修饰静态方法 ### 使用注意 - 在 `synchronized` 代码块内做判断,否则判断可能会失效,如:多卖票 - 不能在 `synchronized` 方法中执行循环,会造成只有一个线程循环执行,其他线程不执行 # 锁 ### 类锁 用来锁类的,一个类的所有对象共享一个class对象,共享一组静态方法,类锁的作用就是使持有者可以同步地调用静态方法。 当synchronized指定修饰 **静态方法** 或者 **class对象** 时,拿到的就是类锁 类锁是所有对象共同争抢一把锁 ### 对象锁 用来锁对象的,虚拟机为每个的非静态方法和非静态域都分配了自己的空间 所以synchronized修饰 **非静态方法** 或者 **this** 的时候拿到的就是对象锁,对象锁是每个对象各有一把的 # 修饰方法 **同一个对象** 的方法,只有一个线程可以访问,其作用范围是整个方法 **注意** - 接口方法时不能使用synchronized关键字; - 构造方法不能使用synchronized关键字,但可以使用`synchronized` 代码块进行同步; - `synchronized` 关键字无法继承; - 如果在父类中的某个方法使用了 `synchronized` 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的。 - 子类方法同步的解决方案 1. 子类方法也加上 `synchronized` 关键字 2. 子类方法中调用父类同步的方法,例如:使用 super.xxxMethod()调用父类方法 # 例子-修饰对象 ### 有bug代码-Runnable方式 ``` public class RunnableImpl7 implements Runnable { private Object lock = new Object(); public final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (ticket <= COUNT) { // 有多个线程等待执行此代码块,只有一个线程在执行,该线程执行后,假设票号码是100,但其他线程还会继续执行此代码块,导致多卖票了 synchronized (lock) { System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Runnable r=new RunnableImpl7(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r,"窗口2"); t1.start(); t2.start(); } } ``` 执行结果: ``` 窗口1-售出票号:96 窗口1-售出票号:97 窗口1-售出票号:98 窗口1-售出票号:99 窗口2-售出票号:100 窗口2-售出票号:101 // 卖票超出上限 窗口1-售出票号:102 // 卖票超出上限 ``` ### 无bug-Runnable方式 **关键:** 要在 `synchronized` 代码块的里面加判断 ``` public class T3 implements Runnable { private Object o1 = new Object(); public final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (true) { synchronized (o1) { if(ticket > COUNT){ // 只有一个线程执行此代码,判断成功就继续往下执行,否则就终止,不会多卖票 break; } System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Runnable r=new T3(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r,"窗口2"); Thread t3=new Thread(r,"窗口3"); t1.start(); t2.start(); t3.start(); } } ``` ### 有bug-Thread方式 ``` package test; public class 线程同步bug extends Thread{ private Object lock = new Object(); public final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (ticket <= COUNT) { // 创建多个对象,每个对象都有 lock 锁对象,每个线程使用自己的 lock 锁,所以线程同步失败,应该共用一个锁 synchronized (lock) { System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { // 创建多个对象,每个对象都有 lock 锁对象 Thread t1=new 线程同步bug(); Thread t2=new 线程同步bug(); t1.start(); t2.start(); } } ``` ### 有bug-Thread方式 ``` package test; public class Thread线程同步bug2 extends Thread{ // 多个对象,共用一把锁 private static Object lock = new Object(); public final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (ticket <= COUNT) { // 有多个线程等待执行此代码块,只有一个线程在执行,该线程执行后,假设票号码是100,但其他线程还会继续执行此代码块,导致多卖票了 synchronized (lock) { System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Thread t1=new Thread线程同步bug2(); Thread t2=new Thread线程同步bug2(); t1.start(); t2.start(); } } ``` ### 无bug-Thread方式 ``` package test; public class Thread线程同步无bug extends Thread{ private static Object lock = new Object(); public final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (true) { synchronized (lock) { if(ticket > COUNT){ // 只有一个线程执行此代码,判断成功就继续往下执行,否则就终止,不会多卖票 break; } System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Thread t1=new Thread线程同步无bug(); Thread t2=new Thread线程同步无bug(); t1.start(); t2.start(); } } ``` # 例子-修饰方法 ### 有bug的代码1 定义线程接口类 ``` public class T1 implements Runnable { public final int COUNT=100; private int ticket=1; @Override public void run() { while(ticket<=COUNT){ handle();//可能有多个线程在同时执行到此行,但只有一个线程进入该方法,该线程执行后,假设票号码已经等于100,其他线程还会继续进入该方法,卖票超出上限 }; } private synchronized void handle(){ System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { T1 ri=new T1(); Thread t1=new Thread(ri,"窗口1"); Thread t2=new Thread(ri,"窗口2"); t1.start(); t2.start(); } } ``` 执行结果: ``` 窗口1-售出票号:96 窗口1-售出票号:97 窗口1-售出票号:98 窗口1-售出票号:99 窗口2-售出票号:100 窗口2-售出票号:101 // 卖票超出上限 窗口1-售出票号:102 // 卖票超出上限 ``` ### 有bug的代码2 线程接口类 ``` public class RunnableImpl2 implements Runnable { public final int COUNT=100; private int ticket=1; @Override public void run() { handle(); } // 同一时刻只有一个线程访问该方法,所以该方法会一直被当前线程执行,不能在 `synchronized` 方法中执行循环 private synchronized void handle(){ while(ticket<=COUNT){ System.out.println(Thread.currentThread().getName()+"-售出票号:"+ticket); ticket++; try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 执行结果:**只有一个窗口卖票** 当 `synchronized` 修饰的方法时,同一时刻只有一个线程访问该方法,所以该方法会一直被当前线程执行 **注意:** 临界区的选择 ### 无 bug 代码 **关键:** 要在 `synchronized` 代码块的里面加判断 ``` public class T1 implements Runnable { public final int COUNT=100; private int ticket=1; @Override public void run() { while(ticket<=COUNT){ handle();//可能有多个线程在同时执行到此行,但只有一个线程进入该方法,该线程执行后,假设i的值已经等于100,其他线程还会继续进入该方法 } } private synchronized void handle(){ if(ticket<=COUNT) {//只有一个线程执行此代码,判断成立就继续执行,否则就不执行,才有效控制,不会多卖票 System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { T1 ri=new T1(); Thread t1=new Thread(ri,"窗口1"); Thread t2=new Thread(ri,"窗口2"); Thread t3=new Thread(ri,"窗口2"); t1.start(); t2.start(); t3.start(); } } ``` ### 有bug代码3 在上面正确的代码中,修改测试类,如下: ``` public class Main3 { public static void main(String[] args) { RunnableImpl3 ri=new RunnableImpl3(); RunnableImpl3 ri2=new RunnableImpl3(); Thread t1=new Thread(ri,"窗口1"); Thread t2=new Thread(ri2,"窗口2"); t1.start(); t2.start(); } } ``` 执行结果: ``` 窗口1-售出票号:1 窗口2-售出票号:1 窗口2-售出票号:2 窗口1-售出票号:2 窗口1-售出票号:3 ``` 窗口1、窗口2会卖相同的票 当 synchronized 修饰普通方法时,**同一个对象** 的方法,只有一个线程可以访问,但创建2个对象,就无法控制了。 可以用 **修饰类** 或 **修饰静态方法** 解决 # 修饰代码块-修饰this **同一个对象** 的 代码块,只有一个线程可以访问,其作用范围是代码块 ### 有bug代码1 ``` public class RunnableImpl4 implements Runnable { public final int COUNT=100; private int ticket=1; @Override public void run() { while(ticket<=COUNT){ synchronized(this) {//有多个线程等待执行此代码块,只有一个线程在执行,该线程执行后,假设i是100,但其他线程还会继续执行此代码块 System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }; } public static void main(String[] args) { Runnable r=new RunnableImpl4(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r,"窗口2"); Thread t3=new Thread(r,"窗口3"); t1.start(); t2.start(); t3.start(); } } ``` 执行结果: ``` 窗口1-售出票号:96 窗口1-售出票号:97 窗口1-售出票号:98 窗口1-售出票号:99 窗口2-售出票号:100 窗口2-售出票号:101 // 卖票超出上限 窗口1-售出票号:102 // 卖票超出上限 ``` ### 有bug的代码2 ``` public class RunnableImpl4 implements Runnable { public final int COUNT=100; private int ticket=1; @Override public void run() { synchronized(this) { while(ticket<=COUNT){ System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }; } } ``` 执行结果: 只有一个窗口卖票。 当 `while` 代码块在 `synchronized` 代码块内部时,同一时刻只有一个线程访问该代码块,也就是这个循环,一直被当前线程执行 **注意:** 临界区的选择 ### 无 bug **关键:** 要在 `synchronized` 代码块的里面加判断 ``` package que; public class T2 implements Runnable { public final int COUNT=100; private int ticket=1; @Override public void run() { while(true){ synchronized(this) {//有多个线程等待执行此代码块,只有一个线程在执行 if(ticket>COUNT) {//在单线程环境中进行判断 break; } System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }; } public static void main(String[] args) { Runnable r=new T2(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r,"窗口2"); Thread t3=new Thread(r,"窗口3"); t1.start(); t2.start(); t3.start(); } } ``` # 修饰类 通过 修饰代码块 实现。要修饰 当前类名.class 特点:可以创建多个接口对象 ### 无bug ``` public class T4 implements Runnable { public final int COUNT=100; private static int ticket=1; @Override public void run() { while(true){ synchronized(T4.class) { if(ticket>COUNT){ break; } System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }; } public static void main(String[] args) { //创建多个Runnable对象,但执行结果仍然正确,是因为 synchronized 修饰类,该类创建的 所有对象,**只有一个线程执行该代码块** Runnable r=new T4(); Runnable r2=new T4(); Runnable r3=new T4(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r2,"窗口2"); Thread t3=new Thread(r3,"窗口3"); t1.start(); t2.start(); t3.start(); } } ``` ### 有bug代码 ``` public class RunnableImpl5 implements Runnable { public final int COUNT=100; private static int ticket=1; @Override public void run() { while(ticket<=COUNT){ synchronized(RunnableImpl5.class) { System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }; } public static void main(String[] args) { Runnable r=new T4(); Runnable r2=new T4(); Runnable r3=new T4(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r2,"窗口2"); Thread t3=new Thread(r3,"窗口3"); t1.start(); t2.start(); t3.start(); } } ``` # 修饰静态方法 ### 无bug ``` public class T5 implements Runnable { public static final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (ticket <= COUNT) { handle(); } } public synchronized static void handle() { if(ticket <= COUNT) { System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { // 创建多个Runnable对象,但执行结果仍然正确,是因为 synchronized 修饰静态方法,该类创建的 所有对象,**只有一个线程执行该静态方法** Runnable r=new T5(); Runnable r2=new T5(); Runnable r3=new T5(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r2,"窗口2"); Thread t3=new Thread(r3,"窗口3"); t1.start(); t2.start(); t3.start(); } } ``` ### 有bug代码 ``` public class RunnableImpl6 implements Runnable { public final int COUNT = 100; private static int ticket = 1; @Override public void run() { while (ticket <= COUNT) { handle(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized static void handle() { System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket); ticket++; } public static void main(String[] args) { Runnable r=new RunnableImpl5(); Runnable r2=new RunnableImpl5(); Thread t1=new Thread(r,"窗口1"); Thread t2=new Thread(r2,"窗口2"); t1.start(); t2.start(); } } ``` 感谢:https://blog.csdn.net/qq_38011415/article/details/89047812 原文出处:http://malaoshi.top/show_1EF4t0Koy5qL.html