Synchronized 锁的使用

283 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Synchronized 锁的使用

synchronized的三种应用方式

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  1. 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  2. 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  3. 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

1.普通方法

1.1 两个线程访问同一个加锁方法
package com.jzj.study.learn0513.jzj0516;

public class NormalMethod {

    private int count;

    /**
     * Synchronized 加锁普通方法
     */
    private synchronized void add() {
        count++;
    }

    private Integer getCount() {
        return count;
    }

    public static void main(String[] args) {
        NormalMethod nm = new NormalMethod();

        //开启一个线程 count++
        new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                nm.add();
            }
        }).start();

        //开启第二个线程 count++;
        new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                nm.add();
            }
        }).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + nm.getCount());
    }


}

执行结果 result:10000,没问题,多个线程针对Synchronized 普通方法需要加锁 在这里插入图片描述

1.2 两个线程访问同一对象方法,一个加锁,一个不加锁
package com.jzj.study.learn0513.jzj0516;

public class NormalMethodDiff {

    /**
     * Synchronized 加锁Write方法
     */
    private synchronized void write() {
        System.out.println(Thread.currentThread().getName() + " write begin");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " write end");
    }

    /**
     * 不加锁方法
     */
    private void read() {
        System.out.println(Thread.currentThread().getName() + " read begin");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " read end");
    }


    public static void main(String[] args) {
        NormalMethodDiff normalMethodDiff = new NormalMethodDiff();

        //开启线程1 访问 锁write方法
        new Thread(() -> normalMethodDiff.write()).start();

        //开启线程2 访问 锁write方法
        new Thread(() -> normalMethodDiff.read()).start();

        //开启线程3 访问 锁write方法
        new Thread(() -> normalMethodDiff.write()).start();
    }
}

执行结果,同一个类中存在加锁和不加锁的方法,线程获取加锁方法的同时,另一个线程可以获取不加锁的方法,不影响 在这里插入图片描述

1.3 两个个线程访问同一对象多个Sync加锁方法

表面上Synchronized是加在方法上,现在尝试多个线程访问同一对象的多个Synchronized方法,表面看来,t1获取A方法,t2获取B方法,应该是可以的,但是想一下底层,我们知道普通方法上的Synchronized 关键字是加在对象上的,对象的锁只有一个,所以是不能获取同一个对象的多个Sync方法的,必须等前对象释放锁

package com.jzj.study.learn0513.jzj0516;

public class NormalMethodManyOne {

    /**
     * Synchronized 加锁Write方法
     */
    private synchronized void write() {
        System.out.println(Thread.currentThread().getName() + " write begin");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " write end");
    }

    /**
     * Synchronized 加锁Read方法
     */
    private synchronized void read() {
        System.out.println(Thread.currentThread().getName() + " read begin");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " read end");
    }


    public static void main(String[] args) {
        NormalMethodManyOne normalMethodManyOne = new NormalMethodManyOne();

        //开启线程1 访问 锁write 方法
        new Thread(() -> normalMethodManyOne.write()).start();

        //开启线程2 访问 另一个锁Read 方法
        new Thread(() -> normalMethodManyOne.read()).start();
    }
}

执行结果:两个线程访问同一个对象的不同Sync加锁方法,虽然是不同方法,但是锁是锁在对象的,依旧要等前一个方法释放锁,才能获取锁 在这里插入图片描述

1.4 多个线程访问不同对象多个Sync加锁方法

我们知道普通方法上的Synchronized 关键字是加在对象上的,现在尝试 多个对象访问多个对象的Synchronized方法

package com.jzj.study.learn0513.jzj0516;

public class NormalMethodManyMany {

    /**
     * Synchronized 加锁Write方法
     */
    private synchronized void write() {
        System.out.println(Thread.currentThread().getName() + " write begin");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " write end");
    }

    /**
     * 不加锁方法
     */
    private void read() {
        System.out.println(Thread.currentThread().getName() + " read begin");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " read end");
    }


    public static void main(String[] args) {
        NormalMethodManyMany normalMethodManyManyOne = new NormalMethodManyMany();
        NormalMethodManyMany normalMethodManyManyTwo = new NormalMethodManyMany();

        //开启线程1 访问 锁write方法
        new Thread(() -> normalMethodManyManyOne.write()).start();

        //开启线程2 访问 锁write方法
        new Thread(() -> normalMethodManyManyTwo.write()).start();
    }
}

执行结果:可以看到线程1和线程2分别获取One对象和Two对象的两个加锁方法,同时获取,互不干扰 在这里插入图片描述

2.静态同步方法

2.1 多个线程调用多个对象的static加锁Sync方法

Synchronized修饰静态Static方法,其实锁是加载Class类上的,和你新建了多少个对象,对象调用方法没任何关系,即使有再多的对象,用的还都是一把锁

package com.jzj.study.learn0513.jzj0516;

public class StaticMethod {

    /**
     * Synchronized 修饰静态方法
     */
    public static synchronized void write() {
        System.out.println(Thread.currentThread().getName() + " write begin");

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " write end!");

    }

    public static void main(String[] args) {
        //开启线程1 执行锁 write 方法
        new Thread(StaticMethod::write).start();
        //开启线程2 执行锁 write 方法
        new Thread(StaticMethod::write).start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("这个要是不明显的话,我们看下下面的 创建两个对象");
        //新建对象One,执行方法write
        StaticMethod smOne = new StaticMethod();
        new Thread(() -> smOne.write()).start();

        //新建对象Two,执行方法write
        StaticMethod smTwo = new StaticMethod();
        new Thread(() -> smTwo.write()).start();
    }

}

执行结果:可以看到,前面的线程不释放锁,后面时没办法拿到锁的,即使有多个对象One、Two用的依旧时一把锁,需要互斥等待锁的释放 在这里插入图片描述

3.修饰代码块

有时候我们方法冗余了很多业务操作,对方法加锁的话太重了,我们需要细化粒度 此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。

3.1 修饰代码块用法
  1. 修饰具体的对象
    private static CodePartSync instance = new CodePartSync();
      synchronized (instance) {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        }
    }
  1. 修饰this关键字
        synchronized (this){
           for (int i = 0; i < 50000; i++) {
               count++;
           }
       }
  1. 修饰Class类对象
        synchronized (CodePartSync.class){
           for (int i = 0; i < 50000; i++) {
               count++;
           }
       }
3.2 修饰代码块,多个线程访问加锁代码块
package com.jzj.study.learn0513.jzj0516;

public class CodePartSync {
    private static CodePartSync instance = new CodePartSync();

    private static int count = 0;

    private void add() {
        //处理业务逻辑
        System.out.println(Thread.currentThread().getName() + " 处理业务逻辑");

        //加锁 instance对象 正确
        synchronized (instance) {
            //加锁 this,指当前对象 正确
//        synchronized (this){
            //加锁 class,类对象 正确
//        synchronized (CodePartSync.class){
            //加锁新建的对象 new Obj() 错误!!!!!!!!!
//        synchronized (new CodePartSync()) {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        }
    }

    public static void main(String[] args) {

        CodePartSync codePartSync = new CodePartSync();

        //开启线程1 执行累加
        new Thread(() -> codePartSync.add()).start();

        //开启线程2 执行累加
        new Thread(() -> codePartSync.add()).start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }

}

执行结果:

  • synchronized (instance) 加锁 instance对象 正确
  • synchronized (this) 加锁 this,指当前对象 正确
  • synchronized (CodePartSync.class) 加锁 class,类对象 正确 在这里插入图片描述

!!!!! 错误的方法

只有你同步的新建的对象 比如synchronized (new CodePartSync()) 这样的时候,才是错误的,因为每个线程的锁都是新的对象,根本无法锁住代码块 !!!!! 错误的结果 在这里插入图片描述

*** 关于Synchronized 关键字的用法就到此为止了