Java基础之synchronized

synchronized是Java语言的关键字,可用来给对象方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。百度百科

看下面的代码,使用synchronized锁定方法的方式,锁定了method1方法,期望同一时间只有一个线程可以访问该方法。

public class SynchronizedTest implements Runnable{
    
    public synchronized void method1(String threadName) throws InterruptedException {
        System.out.println("线程" + threadName + ":开始!");
        Thread.sleep(3000);
        System.out.println("线程" + threadName + ":结束!");
    }

    @Override
    public void run() {
        try {
            method1(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    public static void main(String[] args) {
        for(int i = 0; i < 4; i++) {
            SynchronizedTest st = new SynchronizedTest();
            Thread t = new Thread(st);
            t.start();
        }
    }
}

运行结果

线程Thread-0:开始!
线程Thread-2:开始!
线程Thread-1:开始!
线程Thread-0:结束!
线程Thread-2:结束!
线程Thread-1:结束!

开始结束没有成对儿输出,这显然不是我们想要的结果,下面换成synchronized锁定代码块的方式。

public void method1(String threadName) throws InterruptedException {
    synchronized(this) {
        System.out.println("线程" + threadName + ":开始!");
        Thread.sleep(3000);
        System.out.println("线程" + threadName + ":结束!");
    }
}

运行结果

线程Thread-0:开始!
线程Thread-1:开始!
线程Thread-2:开始!
线程Thread-0:结束!
线程Thread-2:结束!
线程Thread-1:结束!

仍然不是我们期望的结果,为什么这两种方式都没能达到我们的预期呢?

原因是在创建多个线程时,创建了多个实例对象,也就是说上面两种方式只有在同一个实例的时候才会生效。

修改main方法

    public static void main(String[] args) {
        SynchronizedTest st = new SynchronizedTest();
        for(int i = 1; i < 4; i++) {
            Thread t = new Thread(st);
            t.start();
        }
    }

运行结果

线程Thread-0方法1:开始!
线程Thread-0方法1:结束!
线程Thread-2方法1:开始!
线程Thread-2方法1:结束!
线程Thread-1方法1:开始!
线程Thread-1方法1:结束!

可以看到现在结果正确了,下面考虑一下不同实例的情况该如何实现呢?

public class SynchronizedTest implements Runnable{
    
    public static synchronized void method1(String threadName) throws InterruptedException {
        System.out.println("线程" + threadName + "开始!");
        Thread.sleep(1000);
        System.out.println("线程" + threadName + "结束!");
    }
    
    @Override
    public void run() {
        try {
            method1(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    public static void main(String[] args) {
        for(int i = 1; i < 4; i++) {
            SynchronizedTest st = new SynchronizedTest();
            Thread t = new Thread(st);
            t.start();
        }
    }
}

运行结果

线程Thread-0:开始!
线程Thread-0:结束!
线程Thread-2:开始!
线程Thread-2:结束!
线程Thread-1:开始!
线程Thread-1:结束!

可以看到,在同步方法上加上static关键字就对多实例线程起作用了,那么下面再看看代码块的方式如何实现?

    public void method1(String threadName) throws InterruptedException {
        synchronized(SynchronizedTest.class) {
            System.out.println("线程" + threadName + "开始!");
            Thread.sleep(1000);
            System.out.println("线程" + threadName + "结束!");
        }
    }

    public void method1(String threadName) throws InterruptedException {
        synchronized("obj") {
            System.out.println("线程" + threadName + "开始!");
            Thread.sleep(1000);
            System.out.println("线程" + threadName + "结束!");
        }
    }

以上两种写法都是正确的,就不再展示运行结果。现在对上面五种写法做一个总结:


image.png

再思考一个问题,如果有两个synchronized修饰的方法或代码段,那么这两个方法在同一时刻是否可以分别被不同的线程访问?下面修改原来的方法,再新增一个method2

public class SynchronizedTest implements Runnable{

    public void method1(String threadName) throws InterruptedException {
        synchronized(SynchronizedTest .class) {
            System.out.println("线程" + threadName + "方法1:准备睡觉!");
            Thread.sleep(1000);
            System.out.println("线程" + threadName + "方法1:睡觉结束!");
        }
    }
    
    public void method2(String threadName) throws InterruptedException {
        synchronized(SynchronizedTest .class) {
            System.out.println("线程" + threadName + "方法2执行!");
        }
    }

    @Override
    public void run() {
        try {
            method1(Thread.currentThread().getName());
            method2(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    public static void main(String[] args) {
        for(int i = 1; i < 4; i++) {
            SynchronizedTest st = new SynchronizedTest();
            Thread t = new Thread(st);
            t.start();
        }
    }

}

运行结果

线程Thread-0方法1:准备睡觉!
线程Thread-0方法1:睡觉结束!
线程Thread-2方法1:准备睡觉!
线程Thread-2方法1:睡觉结束!
线程Thread-1方法1:准备睡觉!
线程Thread-1方法1:睡觉结束!
线程Thread-2方法2执行!
线程Thread-0方法2执行!
线程Thread-1方法2执行!

在方法1睡觉的过程中,没有方法2的输出,这说明在线程锁定方法1的时候方法2同样是被锁定的,那么反之,非同步方法或代码块在其他线程没有获取锁的情况下还是可以访问的。下面去掉方法2的synchronized锁:

    public void method2(String threadName) throws InterruptedException {
        System.out.println("线程" + threadName + "方法2执行!");
    }

运行结果

线程Thread-0方法1:准备睡觉!
线程Thread-0方法1:睡觉结束!
线程Thread-0方法2执行!
线程Thread-1方法1:准备睡觉!
线程Thread-1方法1:睡觉结束!
线程Thread-2方法1:准备睡觉!
线程Thread-1方法2执行!
线程Thread-2方法1:睡觉结束!
线程Thread-2方法2执行!

  • 彩蛋

看看下面两种写法是否效果一样?

String obj = "obj";
    public void method1(String threadName) throws InterruptedException {
        synchronized(obj) {
            System.out.println("线程" + threadName + "开始!");
            Thread.sleep(1000);
            System.out.println("线程" + threadName + "结束!");
        }
    }
String obj = new String("obj");
    public void method1(String threadName) throws InterruptedException {
        synchronized(obj) {
            System.out.println("线程" + threadName + "开始!");
            Thread.sleep(1000);
            System.out.println("线程" + threadName + "结束!");
        }
    }

上面验证过第一种写法是正确的,贴一下第二个的运行结果

线程Thread-0开始!
线程Thread-2开始!
线程Thread-1开始!
线程Thread-0结束!
线程Thread-2结束!
线程Thread-1结束!

区别就在于String s = "s";会被存在常量区,而String s = new String("s");是对象会放在堆中,这样的话第二种写法就变成锁定的是实例对象了,故而结果是错的。想要了解更多关于String的知识还请搜索引擎。

参考:
synchronized锁住的是代码还是对象
让你彻底理解Synchronized

你可能感兴趣的:(Java基础之synchronized)