acautomaton
acautomaton
Published on 2025-03-05 / 13 Visits
0
0

Java synchronized 与 volatile

synchronized关键字可以保证并发编程的三大特性:原子性、可见性、有序性;而volatile关键字只能保证可见性和有序性,不能保证原子性,也称为是轻量级的synchronized

synchronized

synchronized 可以实现的锁:

  • 悲观锁:每次访问共享资源时都会上锁。

  • 非公平锁:线程获取锁的顺序并不一定是按照线程阻塞的顺序。

  • 可重入锁:即已经获取锁的线程可以再次获取锁。

  • 独占锁(排他锁):该锁只能被一个线程所持有,其他线程均被阻塞。

使用方式

修饰普通同步方法
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread thread1 = Thread.ofVirtual().unstarted(test::run);
        Thread thread2 = Thread.ofVirtual().unstarted(test::run);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(Test.i);
    }
}

class Test {
    public static Integer i = 0;

    public void increment() {
        i++;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            increment();
        }
    }
}
15794

不使用 synchronized 时,显然会出现线程不安全的问题。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread thread1 = Thread.ofVirtual().unstarted(test::run);
        Thread thread2 = Thread.ofVirtual().unstarted(test::run);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(Test.i);
    }
}

class Test {
    public static Integer i = 0;

    public synchronized void increment() {
        i++;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            increment();
        }
    }
}
20000

使用 synchronized 限定后,同一时间只有一个线程能执行 test 对象中被 synchronized 限定的方法。

修饰静态方法
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = Thread.ofVirtual().unstarted(new Test()::run);
        Thread thread2 = Thread.ofVirtual().unstarted(new Test()::run);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(Test.i);
    }
}

class Test {
    public static Integer i = 0;

    public synchronized void increment() {
        i++;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            increment();
        }
    }
}
15528

increment()虽然也使用 synchronized修饰了,但是因为两次 new Test()操作建立的是两个不同的对象,也就是说存在两个不同的对象锁,线程 thread1thread2使用的是不同的对象锁,所以不能保证线程安全。那这种情况应该如何解决呢?因为每次创建的实例对象都是不同的,而类对象却只有一个,所以将 synchronized作用于类对象,即用 synchronized修饰静态方法即可。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = Thread.ofVirtual().unstarted(new Test()::run);
        Thread thread2 = Thread.ofVirtual().unstarted(new Test()::run);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(Test.i);
    }
}

class Test {
    public static Integer i = 0;

    public static synchronized void increment() {
        i++;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            increment();
        }
    }
}
20000
修饰同步方法代码块

如果某些情况下,整个方法体比较大,需要同步的代码只是一小部分,如果直接对整个方法体进行同步,会使得代码性能变差,这时只需要对一小部分代码进行同步即可。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread thread1 = Thread.ofVirtual().unstarted(test::run);
        Thread thread2 = Thread.ofVirtual().unstarted(test::run);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(Test.i + ", " + Test.j);
    }
}

class Test {
    public static Integer i = 0;
    public static Integer j = 0;

    public void increment() {
        synchronized (this) {  // or sychronized (Test.class) {}
            i++;
        }
        j++;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            increment();
        }
    }
}
20000, 18821

synchronizedLock 的区别

  • Lock 是显式锁,需要手动开启和关闭。 synchronized 是隐式锁,可以自动释放锁。

  • Lock 是一个接口,是 JDK 实现的。 synchronized 是一个关键字,是依赖 JVM 实现的。

  • Lock 是可中断锁, synchronized 是不可中断锁,需要线程执行完才能释放锁。

  • 发生异常时, Lock 不会主动释放占有的锁,必须通过 unlock() 进行手动释放,因此可能引发死锁。 synchronized 在发生异常时会自动释放占有的锁,不会出现死锁的情况。

  • Lock 可以判断锁的状态, synchronized 不可以判断锁的状态。

  • Lock 实现锁的类型是可重入锁、公平锁。 synchronized 实现锁的类型是可重入锁,非公平锁。

  • Lock 适用于大量同步代码块的场景, synchronized 适用于少量同步代码块的场景。

volatile

volatile 不保证原子性,只保证可见性以及禁止指令重排(有序性)。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread thread1 = Thread.ofVirtual().unstarted(test::run);
        Thread thread2 = Thread.ofVirtual().unstarted(test::run);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(Test.i);
    }
}

class Test {
    public static volatile Integer i = 0;

    public void increment() {
        i++;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            increment();
        }
    }
}
12403

参考资料

https://zhuanlan.zhihu.com/p/377423211


Comment