JAVA synchronized关键字

概述

synchronized关键字,被synchronized修饰的区域,只有一个线程 可以访问,用来实现 线程同步,处理 多线程 访问修改 共享资源。也就是:线程安全

synchronized通过锁机制实现

使用方式

  1. 修饰方法
  2. 修饰代码块
    通过修饰this实现
  3. 修饰对象
    通过修饰代码块实现。通过修饰 其他对象 实现
  4. 修饰类
    通过修饰代码块实现。通过修饰 当前类名.class 实现
  5. 修饰静态方法

使用注意

  • 在 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