Java - Synchronized(六)

Java - Synchronized(六)

1.Java的锁

  • 在Java中,“synchronized” 是一个关键字,用于实现多线程的同步机制,其含义是“同步”的意思。

    在多线程编程中,当多个线程同时访问共享资源时,可能会导致竞态条件(Race Condition)和数据不一致的问题。为了避免这些问题,可以使用 synchronized 关键字来标记一个代码块或方法,使得同一时间只有一个线程可以进入该代码块或方法,从而保证了对共享资源的互斥访问。

    具体来说,使用 synchronized 关键字有两种方式:

    1. 同步代码块:通过 synchronized 关键字来标记一个代码块,用法如下:

      javaCopy codesynchronized (lockObject) {
          // 在这里编写需要同步的代码
      }
      

      这里的 lockObject 是一个用来控制同步的对象,不同线程在执行到该代码块时,会尝试获取 lockObject 的锁,只有获取到锁的线程才能执行代码块内的内容,其他线程将被阻塞等待。

    2. 同步方法:通过在方法声明上使用 synchronized 关键字,用法如下:

      javaCopy codepublic synchronized void synchronizedMethod() {
          // 在这里编写需要同步的代码
      }
      

      声明为 synchronized 的方法在执行时,会自动获取该方法所属对象的锁(即当前实例对象的锁),从而保证同一时间只能有一个线程调用该方法。

    使用 synchronized 关键字可以确保在多线程环境下,共享资源的读写操作是安全的,不会出现数据不一致或竞态条件问题。然而,需要注意的是,过多地使用 synchronized 可能会导致性能问题,因为它会引入线程的阻塞和切换开销,因此在设计多线程程序时,需要综合考虑线程安全和性能之间的平衡。

    使用上表现为:

  • 同步机制: synchronized是Java同步机制的一种实现,即互斥锁机制,它所获得的锁叫做互斥锁

  • 互斥锁: 指的是每个对象的锁一次只能分配给一个线程,同一时间只能由一个线程占用

  • 作用: synchronized用于保证同一时刻只能由一个线程进入到临界区,同时保证共享变量的可见性、原子性和有序性

  • 使用: 当一个线程试图访问同步代码方法(块)时,它首先必须得到锁,退出或抛出异常时必须释放锁

2Synchronized的使用

2.1 Synchronized的三种应用方式

插入

补充: 使用同步代码块的好处在于其他线程仍可以访问非synchronized(this)的同步代码块

3.Synchronized的使用规则

/**
  * 先定义一个测试模板类
  *     这里补充一个知识点:Thread.sleep(long)不会释放锁
  *     读者可参见笔者的`并发番@Thread一文通`
  */ 
public class SynchronizedDemo {
    public static synchronized void staticMethod(){
        System.out.println(Thread.currentThread().getName() + "访问了静态同步方法staticMethod");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束访问静态同步方法staticMethod");
    }
    public static void staticMethod2(){
        System.out.println(Thread.currentThread().getName() + "访问了静态同步方法staticMethod2");
        synchronized (SynchronizedDemo.class){
            System.out.println(Thread.currentThread().getName() + "在staticMethod2方法中获取了SynchronizedDemo.class");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void synMethod(){
        System.out.println(Thread.currentThread().getName() + "访问了同步方法synMethod");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束访问同步方法synMethod");
    }
    public synchronized void synMethod2(){
        System.out.println(Thread.currentThread().getName() + "访问了同步方法synMethod2");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束访问同步方法synMethod2");
    }
    public void method(){
        System.out.println(Thread.currentThread().getName() + "访问了普通方法method");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束访问普通方法method");
    }
    private Object lock = new Object();
    public void chunkMethod(){
        System.out.println(Thread.currentThread().getName() + "访问了chunkMethod方法");
        synchronized (lock){
            System.out.println(Thread.currentThread().getName() + "在chunkMethod方法中获取了lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void chunkMethod2(){
        System.out.println(Thread.currentThread().getName() + "访问了chunkMethod2方法");
        synchronized (lock){
            System.out.println(Thread.currentThread().getName() + "在chunkMethod2方法中获取了lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void chunkMethod3(){
        System.out.println(Thread.currentThread().getName() + "访问了chunkMethod3方法");
        //同步代码块
        synchronized (this){
            System.out.println(Thread.currentThread().getName() + "在chunkMethod3方法中获取了this");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void stringMethod(String lock){
        synchronized (lock){
            while (true){
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3.1 普通方法与同步方法调用互不关联

当一个线程进入同步方法时,其他线程可以正常访问其他非同步方法

public static void main(String[] args) {
    SynchronizedDemo synDemo = new SynchronizedDemo();
    Thread thread1 = new Thread(() -> {
        //调用普通方法
        synDemo.method();
    });
    Thread thread2 = new Thread(() -> {
        //调用同步方法
        synDemo.synMethod();
    });
    thread1.start();
    thread2.start();
}
---------------------
//输出:
Thread-1访问了同步方法synMethod
Thread-0访问了普通方法method
Thread-0结束访问普通方法method
Thread-1结束访问同步方法synMethod

分析:

通过结果可知,普通方法和同步方法是非阻塞执行的。

3.2 所有同步方法只能被一个线程访问

当一个线程执行同步方法时,其他线程不能访问任何同步方法

public static void main(String[] args) {
    SynchronizedDemo synDemo = new SynchronizedDemo();
    Thread thread1 = new Thread(() -> {
        synDemo.synMethod();
        synDemo.synMethod2();
    });
    Thread thread2 = new Thread(() -> {
        synDemo.synMethod2();
        synDemo.synMethod();
    });
    thread1.start();
    thread2.start();
}
---------------------
//输出:
Thread-0访问了同步方法synMethod
Thread-0结束访问同步方法synMethod
Thread-0访问了同步方法synMethod2
Thread-0结束访问同步方法synMethod2
Thread-1访问了同步方法synMethod2
Thread-1结束访问同步方法synMethod2
Thread-1访问了同步方法synMethod
Thread-1结束访问同步方法synMethod

分析

通过结果可知,任务的执行是阻塞的,显然Thread-1必须等待Thread-0执行完毕之后才能继续执行

3.3 同一个锁的同步代码块同一时刻只能被一个线程访问

当同步代码块都是同一个锁时,方法可以被所有线程访问,但同一个锁的同步代码块同一时刻只能被一个线程访问

public static void main(String[] args) {
    SynchronizedDemo synDemo = new SynchronizedDemo();
    Thread thread1 = new Thread(() -> {
        //调用同步块方法
        synDemo.chunkMethod();
        synDemo.chunkMethod2();
    });
    Thread thread2 = new Thread(() -> {
        //调用同步块方法
        synDemo.chunkMethod();
        synDemo.synMethod2();
    });
    thread1.start();
    thread2.start();
}
---------------------
//输出:
Thread-0访问了chunkMethod方法
Thread-1访问了chunkMethod方法
Thread-0在chunkMethod方法中获取了lock  
...停顿等待...
Thread-1在chunkMethod方法中获取了lock
...停顿等待...
Thread-0访问了chunkMethod2方法
Thread-0在chunkMethod2方法中获取了lock
...停顿等待...
Thread-1访问了chunkMethod2方法
Thread-1在chunkMethod2方法中获取了lock

分析可知:
1.对比18行和19行可知,即使普通方法有同步代码块,但方法的访问是非阻塞的,任何线程都可以自由进入
2.对比20行、22行以及25行和27行可知,对于同一个锁的同步代码块的访问一定是阻塞的

3.4 线程间同时访问同一个锁的多个同步代码的执行顺序不定

  • 线程间同时访问同一个锁多个同步代码的执行顺序不定,即使是使用同一个对象锁,这点跟同步方法有很大差异
public static void main(String[] args) {
    SynchronizedDemo synDemo = new SynchronizedDemo();
    Thread thread1 = new Thread(() -> {
        //调用同步块方法
        synDemo.chunkMethod();
        synDemo.chunkMethod2();
    });
    Thread thread2 = new Thread(() -> {
        //调用同步块方法
        synDemo.chunkMethod2();
        synDemo.chunkMethod();
    });
    thread1.start();
    thread2.start();
}
---------------------
//输出:
Thread-0访问了chunkMethod方法
Thread-1访问了chunkMethod2方法
Thread-0在chunkMethod方法中获取了lock
...停顿等待...
Thread-0访问了chunkMethod2方法
Thread-1在chunkMethod2方法中获取了lock
...停顿等待...
Thread-1访问了chunkMethod方法
Thread-0在chunkMethod2方法中获取了lock
...停顿等待...
Thread-1在chunkMethod方法中获取了lock


分析可知:
现象:对比20行、22行和24行、25行可知,虽然是同一个lock对象,但其不同代码块的访问是非阻塞的
原因:根源在于锁的释放和重新竞争,当Thread-0访问完chunkMethod方法后会先释放锁,这时Thread-1就有机会能获取到锁从而优先执行,依次类推到24行、25行时,Thread-0又重新获取到锁优先执行了
注意:但有一点是必须的,对于同一个锁的同步代码块的访问一定是阻塞的

补充:同步方法之所有会被全部阻塞,是因为synDemo对象一直被线程在内部把持住就没释放过,论把持住的重要性

3.5 不同锁之间访问非阻塞

  • 由于三种使用方式的锁对象都不一样,因此相互之间不会有任何影响

  • 但有两种情况除外:

    • 1.当同步代码块使用的Class对象和类对象一致时属于同一个锁,遵循上面的3.2.3原则
    • 2.当同步代码块使用的是this,即与同步方法使用锁属于同一个锁,遵循上面的3.2.23.2.3原则
public static void main(String[] args) {
    SynchronizedDemo synDemo = new SynchronizedDemo();
    Thread thread1 = new Thread(() -> synDemo.chunkMethod() );
    Thread thread2 = new Thread(() -> synDemo.chunkMethod3());
    Thread thread3 = new Thread(() -> staticMethod());
    Thread thread4 = new Thread(() -> staticMethod2());
    thread1.start();
    thread2.start();
    thread3.start();
    thread4.start();
}
---------------------
//输出:
Thread-1访问了chunkMethod3方法
Thread-1在chunkMethod3方法中获取了this
Thread-2访问了静态同步方法staticMethod
Thread-0访问了chunkMethod方法
Thread-0在chunkMethod方法中获取了lock
Thread-3访问了静态同步方法staticMethod2
...停顿等待...
Thread-2结束访问静态同步方法staticMethod
Thread-3在staticMethod2方法中获取了SynchronizedDemo.class

分析可知:
现象:对比16行、18行和24行、25行可知,虽然是同一个lock对象,但其不同代码块的访问是非阻塞的
原因:根源在于锁的释放和重新竞争,当Thread-0访问完chunkMethod方法后会先释放锁,这时Thread-1就有机会能获取到锁从而优先执行,依次类推到24行、25行时,Thread-0又重新获取到锁优先执行了

你可能感兴趣的:(juc,java,java,jvm,开发语言)