Java同步机制-synchronized使用

synchronized简介

Java平台中的任何一个对象都有一个与之关联的锁。这种锁被称为监视锁或者内部锁。 内部锁是一种排他锁,它能够保障原子性、可见性和有序性。

synchronized使用

synchronized关键字可以用来修饰方法及代码块,它有如下应用方式:

  • 同步普通方法,锁的是当前类实例对象
  • 同步静态方法,锁的是当前类的class对象
  • 同步代码块,锁是括号里面的对象

同步普通方法

同步普通方法时,锁的是实例对象。我们先看下不加Synchronized修饰时情况:

public class SynchronizedDemo {
    public void test(){
        for (int i = 0; i < 3; i++) {
            System.out.println("当前执行线程:" + Thread.currentThread().getName());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(() -> {
            sd.test();
        }, "线程1").start();
        new Thread(() -> {
            sd.test();
        }, "线程2").start();
    }
}

执行结果如下:

当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程1

我们看到方法没有加同步时,两个线程是可以同时执行的。我们把方法加上synchronized修饰:

public synchronized void test(){
       for (int i = 0; i < 3; i++) {
           System.out.println("当前执行线程:" + Thread.currentThread().getName());
           try {
               Thread.sleep(200);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
}

执行结果如下:

当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程2

可以看到这边产生了锁竞争,谁先抢到谁先执行,我们需要注意的是,两个线程调用方法时必须是同一个对象。我们看下下面这种情况:

public class SynchronizedDemo {
    public synchronized void test(){
        for (int i = 0; i < 3; i++) {
            System.out.println("当前执行线程:" + Thread.currentThread().getName());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
    	// 实例对象sd1
        SynchronizedDemo sd1 = new SynchronizedDemo();
        // 实例对象sd2
        SynchronizedDemo sd2 = new SynchronizedDemo();
        new Thread(() -> {
        	// 调用实例sd1的方法
            sd1.test();
        }, "线程1").start();
        new Thread(() -> {
        	// 调用实例sd2的方法
            sd2.test();
        }, "线程2").start();
    }
}

执行结果如下:

当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程1
当前执行线程:线程2

这边线程1和线程2分别调用了同一个类的不同对象,所以他们竞争的是不同的锁,当然可以同时执行喽。

同步静态方法

同步静态方法时,锁的是类对象(Class对象)。下面是静态方法加上synchronized的代码:

public class SynchronizedDemo {
    public synchronized static void testStatic(){
        for (int i = 0; i < 3; i++) {
            System.out.println("当前执行线程:" + Thread.currentThread().getName());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(() -> {
            sd.testStatic();
        }, "线程1").start();
        new Thread(() -> {
            sd.testStatic();
        }, "线程2").start();
    }
}

执行结果显示同步生效了,我们再看下调用不同实例的情况:

public class SynchronizedDemo {
    public synchronized static void testStatic(){
        for (int i = 0; i < 3; i++) {
            System.out.println("当前执行线程:" + Thread.currentThread().getName());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        // 实例对象sd1
        SynchronizedDemo sd1 = new SynchronizedDemo();
        // 实例对象sd2
        SynchronizedDemo sd2 = new SynchronizedDemo();
        new Thread(() -> {
            // 实例对象sd1的静态方法
            sd1.testStatic();
        }, "线程1").start();
        new Thread(() -> {
            // 实例对象sd2的静态方法
            sd2.testStatic();
        }, "线程2").start();
    }
}

执行结果如下:

当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程2

这边两个线程虽然调用是不同一个实例的方法,但别忘了,这边锁的是我们的类对象(Class对象),显然这边两个实例是属于同一个类对象的,所以它们需要竞争同一把锁。

同步代码块

我们看下同步代码块的使用方式:

public class SynchronizedDemo {
    public void test(){
        synchronized(this){
            for (int i = 0; i < 3; i++) {
                System.out.println("当前执行线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(() -> {
            sd.test();
        }, "线程1").start();
        new Thread(() -> {
            sd.test();
        }, "线程2").start();
    }
}

执行结果如下:

当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程

我们这边用到了synchronized(this),其实这边跟同步普通方法类似,锁住的是当前对象。当然我们也可以自定义一个锁对象:

public class SynchronizedDemo {
    Object lock = new Object();
    public void test(){
        synchronized(lock){
            for (int i = 0; i < 3; i++) {
                System.out.println("当前执行线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(() -> {
            sd.test();
        }, "线程1").start();
        new Thread(() -> {
            sd.test();
        }, "线程2").start();
    }
}

效果是一样的:

当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程2

synchronized用法总结

对于synchronized的使用,我们总结如下:

  • 对于同一个类的不同实例对象,调用他们的普通方法(synchronized修饰)时,由于不是同一把锁,所以不会产生竞争。
  • 调用同一个类的静态方法(synchronized修饰)时,由于是同一把锁(类的Class对象),所以会产生竞争。
  • 当一个线程正在调用实例的普通方法(synchronized修饰)时,若另一个线程调用它的其它非同步普通方法时,不需要获取锁,可以同时执行。
发布了8 篇原创文章 · 获赞 3 · 访问量 2213

猜你喜欢

转载自blog.csdn.net/ym572170/article/details/104799204