Java中Synchronized的使用

Synchronized是Java语言的关键字,可以使用它来修饰方法或者代码块,Synchronized能够保证被它修饰的方法或代码块在同一时刻最多只有一个线程执行该段代码,这里我将介绍由它来分别修饰静态方法,实例方法,以及代码块是如何使用的。

1、修饰静态方法

Synchronized修饰静态方法它的锁就是当前的class对象,那么如果当该类有多个Synchronized修饰的静态方法时,一次只有一个线程能执行其中的一个Synchronized修饰的静态方法,同时Synchronized还是可重入锁,在一个线程拿到了锁,它可以再去调用该类中其他Synchronized修饰的静态方法。

示例代码:

public class TestStaticSync {

    public static void main(String[] args) {
        test1();
//        test2();
    }

    /**
     * 两个线程同时抢占执行同一个方法
     */
    public static void test1() {
        //启用两个线程
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //执行加法
        executorService.execute(() -> {
            TestStaticSync.add(1, 1);
        });
        //在执行加法
        executorService.execute(() -> {
            TestStaticSync.add(2, 2);
        });
        executorService.shutdown();
    }

    /**
     * synchronized 的可重入演示
     */
    public static void test2() {
        //启用两个线程
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //执行加法 在执行减法
        executorService.execute(() -> {
            TestStaticSync.add(1, 1);
            TestStaticSync.sub(1, 1);
        });
        //执行减法 在执行加法
        executorService.execute(() -> {
            TestStaticSync.sub(2, 2);
            TestStaticSync.add(2, 2);
        });
        executorService.shutdown();
    }

    synchronized public static void add(int a, int b) {
        //模拟耗时
        sleep();
        System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行加法:" + a + "+" + b + "=" + (a + b));
    }

    synchronized public static void sub(int a, int b) {
        //模拟耗时
        sleep();
        System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行减法:" + a + "-" + b + "=" + (a - b));
    }

    public static void sleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

两个线程同时执行同一个类的静态方法,查看效果,我们可以看到它是在一个线程执行完成了才进行下一个的,执行test1方法输出:

pool-1-thread-1在1616168594406执行加法:1+1=2
pool-1-thread-2在1616168595410执行加法:2+2=4

两个线程同时执行两个静态方法,查看效果,我们可以看到它是顺序的执行完成的,哪怕两个线程中的调用的方法顺序不一样,也是等一个线程执行完了,下一个才能开始执行,执行test2方法输出:

pool-1-thread-1在1616168620580执行加法:1+1=2
pool-1-thread-1在1616168621583执行减法:1-1=0
pool-1-thread-2在1616168622588执行减法:2-2=0
pool-1-thread-2在1616168623593执行加法:2+2=4

2、修饰实例方法

Synchronized修饰实例方法它的锁就是this当前的实例,同时类中也可以有多个由Synchronized修饰的实例方法,和修饰静态方法一样,对同一个实例,一次只有有一个线程来执行一个由Synchronized修饰的实例方法,同时也是可以重入的。

public class TestInstantiateSync {

    public static void main(String[] args) {
        test1();
//        test2();
    }

    /**
     * 两个线程同时抢占执行同一个实例方法
     */
    public static void test1() {
        TestInstantiateSync testSync = new TestInstantiateSync();
        //启用两个线程
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //执行加法
        executorService.execute(() -> {
            testSync.add(1, 1);
            testSync.sub(1, 1);
        });
        //在执行加法
        executorService.execute(() -> {
            testSync.sub(2, 2);
            testSync.add(2, 2);
        });
        executorService.shutdown();
    }

    /**
     * 两个线程同时执行两个实例对象的方法
     */
    public static void test2() {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        TestInstantiateSync testSync1 = new TestInstantiateSync();
        TestInstantiateSync testSync2 = new TestInstantiateSync();
        executorService.execute(() -> {
            testSync1.add(1, 1);
            testSync1.sub(1, 1);
        });
        executorService.execute(() -> {
            testSync2.sub(2, 2);
            testSync2.add(2, 2);
        });
        executorService.shutdown();
    }


    synchronized public void add(int a, int b) {
        //模拟耗时
        sleep();
        System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行加法:" + a + "+" + b + "=" + (a + b));
    }

    synchronized public void sub(int a, int b) {
        //模拟耗时
        sleep();
        System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行减法:" + a + "-" + b + "=" + (a - b));
    }

    public static void sleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

两个线程同时执行同一个实例的两个由synchronized修饰的方法,查看效果,我们可以看到它是在一个线程执行完成了才进行下一个的,同时它重入了,执行test1方法输出:

pool-1-thread-1在1616169847582执行加法:1+1=2
pool-1-thread-1在1616169848584执行减法:1-1=0
pool-1-thread-2在1616169849587执行减法:2-2=0
pool-1-thread-2在1616169850587执行加法:2+2=4

两个线程同时执行两个不同实例的方法synchronized修饰的方法,查看效果,可以看到两个线程是同时执行的,所以不同的实例他们的锁是不一样的,执行test2方法输出:

pool-1-thread-1在1616169940129执行加法:1+1=2
pool-1-thread-2在1616169940129执行减法:2-2=0
pool-1-thread-2在1616169941131执行加法:2+2=4
pool-1-thread-1在1616169941131执行减法:1-1=0

2、修饰代码块

Synchronized修饰代码块设置指定的锁,可以设置一个成员变量为锁,当该类每次实例化时,也会生成新的锁,也就是不同的实例用的锁是不一样的。

思考🤔:为什么我要使用new String(“lock”)的对象为锁,而不是直接赋值的方式?使用直接赋值的方式的字符串为锁有什么效果?提示:JVM内存分配

public class TestCodeSync {

    private String lock = new String("lock");

//    private String lock = "lock";

    public static void main(String[] args) {
//        test1();
        test2();
    }

    public static void test1() {
        //启用两个线程
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        TestCodeSync testSync = new TestCodeSync();
        executorService.execute(() -> {
            testSync.add(1, 1);
            testSync.sub(1, 1);
        });
        executorService.execute(() -> {
            testSync.sub(3, 2);
            testSync.add(1, 0);
        });
        executorService.shutdown();
    }

    public static void test2() {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        TestCodeSync testSync1 = new TestCodeSync();
        TestCodeSync testSync2 = new TestCodeSync();
        executorService.execute(() -> {
            testSync1.add(1, 1);
            testSync1.sub(1, 1);
        });
        executorService.execute(() -> {
            testSync2.sub(3, 2);
            testSync2.add(1, 0);
        });
        executorService.shutdown();
    }


    public void add(int a, int b) {
        synchronized (lock) {
            sleep();
            System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行加法:" + a + "+" + b + "=" + (a + b));
        }
    }

    public void sub(int a, int b) {
        synchronized (lock) {
            sleep();
            System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行减法:" + a + "-" + b + "=" + (a - b));
        }
    }

    public static void sleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

两个线程同时执行同一个实例的两个由synchronized修饰代码块的方法,查看效果,我们可以看到它是在一个线程执行完成了才进行下一个的,同时它重入了,执行test1方法输出:

pool-1-thread-1在1616254845316执行加法:1+1=2
pool-1-thread-1在1616254846319执行减法:1-1=0
pool-1-thread-2在1616254847324执行减法:3-2=1
pool-1-thread-2在1616254848325执行加法:1+0=1

两个线程同时执行两个不同实例的方法synchronized修饰代码块的方法,查看效果,可以看到两个线程是同时执行的,所以不同的实例他们的锁是不一样的,执行test2方法输出:

pool-1-thread-1在1616255036160执行加法:1+1=2
pool-1-thread-2在1616255036160执行减法:3-2=1
pool-1-thread-1在1616255037162执行减法:1-1=0
pool-1-thread-2在1616255037162执行加法:1+0=1

总结:Synchronized修饰静态方法时,锁就是当前的class,这个锁就是一个类锁,属于非常重的锁,因为其他的线程访问该类的其他的Synchronized修饰的静态方法时将会被阻塞;Synchronized修饰实例方法时,锁就是当前的实例对象,不同的实例锁是不一样的,对不同的实例不会存在锁的竞争,相对静态方法来说较轻;Synchronized修饰代码块时,只有执行到修饰的代码块时才会被阻塞,可以更细粒度的控制锁,不同的实例也可以拥有不同的锁(非静态常量作为锁),相对实例化的Synchronized修饰的方法性能更好。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
synchronized 关键字是 Java 的一个关键字,用于实现并发编程的同步。它可以用于方法、代码块和静态方法上。 1. 用法一:修饰实例方法 当一个线程访问一个对象的 synchronized 实例方法时,其他线程不能访问该对象的其他 synchronized 实例方法,但可以访问该对象的非 synchronized 实例方法。这种方式的锁是当前实例对象。 示例代码: ```java public synchronized void syncMethod() { // synchronized 代码块 } ``` 2. 用法二:修饰代码块 synchronized 代码块可以用来同步访问共享资源,它必须接收一个对象作为参数,该对象被称为锁对象。当线程进入 synchronized 代码块时,它必须获得锁对象的锁,其他线程在此时无法获得锁对象的锁,只有等待锁被释放。 示例代码: ```java public void syncBlock() { synchronized(this) { // synchronized 代码块 } } ``` 3. 用法三:修饰静态方法 当一个线程访问一个类的 synchronized 静态方法时,其他线程不能访问该类的其他 synchronized 静态方法,但可以访问该类的非 synchronized 静态方法。这种方式的锁是当前类的 Class 对象。 示例代码: ```java public static synchronized void syncStaticMethod() { // synchronized 代码块 } ``` 需要注意的是,使用 synchronized 关键字会降低程序的性能,因为当一个线程获得锁时,其他线程必须等待该线程释放锁才能继续执行,这会增加线程的上下文切换和调度开销。因此,在进行并发编程时,应该尽量避免使用 synchronized 关键字,而是使用更高级别的同步机制,如 Lock、Semaphore、CountDownLatch 等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值