【JUC基础】03. 几段代码看懂synchronized

简介: 程序员经常听到“并发锁”这个名词,而且实际项目中也确实避免不了要加锁。那么什么是锁?锁的是什么?今天文章从8个有意思的案例,彻底弄清这两个问题。

 1、前言

程序员经常听到“并发锁”这个名词,而且实际项目中也确实避免不了要加锁。那么什么是锁?锁的是什么?今天文章从8个有意思的案例,彻底弄清这两个问题。

2、什么是synchronized

Java中的一个保留字(关键字)。加了synchronized表明该段代码为同步块,也称之为同步锁。synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。JDK1.5之前synchronized是一个重量级锁,相对于JUC中的Lock,它会显得那么笨重,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么重了。

简单的使用示例代码为:

public synchronized void doSomething(){
    ...   
}
public void doSomething() {
    synchronized(this){
        ...    
    }
}

3、代码一:单实例synchronized调用

eat():普通synchronized方法

sleep():普通synchronized方法

思考问题:控制台先打印什么?先吃饭?还是先睡觉?

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        new Thread(() -> dog.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog.sleep()).start();
    }
}
class Dog {
    public synchronized void eat(){
        TimeUnit.SECONDS.sleep(2);
        System.out.println("先吃饭");
    }
    public synchronized void sleep(){
        System.out.println("先睡觉");
    }
}

image.gif

执行结果:

image.png

原因:

普通方法加了synchronized,而synchronized锁的是该方法的调用者,也就是dog实例。这时候两个线程都由同一个dog实例调用,因此sleep()方法需要等待。

这里的eat()方法为什么也睡了2s,原因是为了排除程序打印”先吃饭“是因为方法被先调用的干扰。

4、代码二、多实例synchronized调用

eat():普通synchronized方法

sleep():普通synchronized方法

思考问题:控制台先打印什么?先吃饭?还是先睡觉?

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog1 = new Dog();
        Dog dog2 = new Dog();
        new Thread(() -> dog1.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog2.sleep()).start();
    }
}
class Dog {
    public synchronized void eat(){
        TimeUnit.SECONDS.sleep(2);
        System.out.println("先吃饭");
    }
    public synchronized void sleep(){
        System.out.println("先睡觉");
    }
}

image.gif

执行结果:

image.png

原因:

普通方法加了synchronized,而synchronized锁的是该方法的调用者,也就是dog实例。这时候两个线程由不同的两个实例dog1、dog2调用,因此相互之间锁不会等待,而eat()睡了2s,因此就先输出了“先睡觉”。

5、代码三、单实例synchronized和普通方法

eat():普通synchronized方法

sleep():普通synchronized方法

这里增加一个普通方法drink()。

思考问题:控制台先打印什么?先吃饭?还是先喝水?

public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        new Thread(() -> dog.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog.drink()).start();
    }
}
class Dog {
    public synchronized void eat(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("先吃饭");
    }
    public synchronized void sleep(){
        System.out.println("先睡觉");
    }
    public void drink(){
        System.out.println("先喝水");
    }
}

image.gif

执行结果:

image.png

原因:

drink()方法是普通方法,没有加锁,因此不会收到锁影响。

6、代码四、多实例synchronized和普通方法

eat():普通synchronized方法

sleep():普通synchronized方法

这里增加一个普通方法drink()。

思考问题:控制台先打印什么?先吃饭?还是先喝水?

public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog1 = new Dog();
        Dog dog2 = new Dog();
        new Thread(() -> dog1.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog2.drink()).start();
    }
}
class Dog {
    public synchronized void eat(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("先吃饭");
    }
    public synchronized void sleep(){
        System.out.println("先睡觉");
    }
    public void drink(){
        System.out.println("先喝水");
    }
}

image.gif

执行结果:

image.png

原因:

drink()方法是普通方法,没有加锁,因此不会收到锁影响。

7、代码五、单实例static和synchronized调用

eat():静态synchronized方法

sleep():静态synchronized方法

这里eat()和sleep()添加static修饰。

思考问题:控制台先打印什么?先吃饭?还是先睡觉?

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        new Thread(() -> dog.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog.sleep()).start();
    }
}
class Dog {
    public static synchronized void eat(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("先吃饭");
    }
    public static synchronized void sleep(){
        System.out.println("先睡觉");
    }
}

image.gif

执行结果:

image.png

原因:

这里加了static之后,synchronized锁的就不再是调用者实例了。由于static特性,这里锁定的是整个class类,而不是实例。

8、代码六、多实例static和synchronized调用

eat():静态synchronized方法

sleep():静态synchronized方法

这里eat()和sleep()添加static修饰。

思考问题:控制台先打印什么?先吃饭?还是先睡觉?

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog1 = new Dog();
        Dog dog2 = new Dog();
        new Thread(() -> dog1.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog2.sleep()).start();
    }
}
class Dog {
    public static synchronized void eat(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("先吃饭");
    }
    public static synchronized void sleep(){
        System.out.println("先睡觉");
    }
}

image.gif

执行结果:

image.png

原因:

这里加了static之后,synchronized锁的就不再是调用者实例了。由于static特性,这里锁定的是整个class类,而不是实例。因此这里跟多实例调用无关。

9、代码七、单实例static同步方法和普通方法调用

eat():静态synchronized方法

sleep():普通synchronized方法

思考问题:控制台先打印什么?先吃饭?还是先睡觉?

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        new Thread(() -> dog.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog.sleep()).start();
    }
}
class Dog {
    public static synchronized void eat(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("先吃饭");
    }
    public synchronized void sleep(){
        System.out.println("先睡觉");
    }
}

image.gif

执行结果:

image.png

原因:

eat()这里加了static之后,synchronized锁的是整个class类,而sleep()锁的是调用者实例dog;因此锁互不影响。

10、代码八、多实例static同步方法和普通方法调用

eat():静态synchronized方法

sleep():普通synchronized方法

思考问题:控制台先打印什么?先吃饭?还是先睡觉?

/**
 * @author Shamee loop
 * @date 2023/4/9
 */
public class LockDemo {
    public static void main(String[] args) throws InterruptedException {
        Dog dog1 = new Dog();
        Dog dog2 = new Dog();
        new Thread(() -> dog1.eat()).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> dog2.sleep()).start();
    }
}
class Dog {
    public static synchronized void eat(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("先吃饭");
    }
    public synchronized void sleep(){
        System.out.println("先睡觉");
    }
}

image.gif

执行结果:

image.png

原因:

eat()这里加了static之后,synchronized锁的是整个class类,而sleep()锁的是调用者实例dog;因此锁互不影响。

11、结论

因此,对于锁。我们需要搞懂锁是谁持有的,通常一般属于调用者持有。调用者又分两类:实例调用(new),类调用(static)。这里用最简单的代码来理解锁。当然锁的机制远比这里的复杂很多,这里只是入门。

相关文章
|
6月前
|
安全 Java
Java并发编程:Synchronized及其实现原理
Java并发编程:Synchronized及其实现原理
53 4
|
3月前
|
存储 Java 程序员
synchronized的原理以及与ReentrantLock的区别
`synchronized`和`ReentrantLock`均为Java线程同步机制,确保共享资源的单一时刻独占访问。`synchronized`关键字直接嵌入JVM,可通过修饰方法或代码块实现对象锁或监视器锁,具备可重入性,依赖Mark Word进行锁状态管理。`ReentrantLock`则需显式调用`lock()`和`unlock()`,提供更灵活控制,如公平锁、尝试锁及条件变量。两者在语法、灵活性和异常处理上有所差异,但均支持可重入性。性能方面,随JDK优化,`synchronized`在某些场景下甚至优于`ReentrantLock`。选择使用哪个取决于具体需求和上下文。
|
6月前
|
Java
从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁
【5月更文挑战第6天】从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁
37 1
|
6月前
|
安全 Java
并发编程之synchronized的详细解析
并发编程之synchronized的详细解析
44 0
|
缓存 Java
深入源码解析 ReentrantLock、AQS:掌握 Java 并发编程关键技术(一)
深入源码解析 ReentrantLock、AQS:掌握 Java 并发编程关键技术
115 0
|
Java API 调度
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
83 0
|
算法 Java
【JUC基础】05. Synchronized和ReentrantLock
前面两篇中分别讲了Synchronized和ReentrantLock。两种方式都能实现同步锁,且也都能解决多线程的并发问题。那么这两个有什么区别呢? 这个也是一个高频的面经题。
109 0
|
安全 Java
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(二)
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(二)
|
Java 开发者
JUC系列学习(三):ReentrantLock的使用、源码解析及与Synchronized的异同
`ReentrantLock`同`Synchronized`一样可以实现线程锁的功能,同样具有可重入性,除此之外还可以实现公平锁&非公平锁,其底层是基于`AQS`框架实现的。
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(三)
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(三)