锁如何使用,有哪些注意事项

参考答案

1.  Java 中常见的锁有

  • synchronized;
  • 可重入锁 java.util.concurrent.lock.ReentrantLock;
  • 可重复读写锁 java.util.concurrent.lock.ReentrantReadWriteLock。

2.  synchronized 有 3种用法

  • 修饰普通方法,执行方法代码,需要获取对象本身 this 的锁;
public class TestSynchronizedNormalMethod {

    private int count = 0;
    
//    private void add1000() {
    private synchronized void add1000() { //使用 synchronized 修饰 add100 方法,即可获得正确的值 30000
        for (int i = 0; i <1000; i++) {
            count++;
        }
    }
    
    //启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
    private void test() throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有线程执行完毕
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
    
    public static void main(String[] args) throws InterruptedException {
        //创建 TestSynchronizedNormalMethod 对象,调用 test 方法
        new TestSynchronizedNormalMethod().test();
    }
}

 

  • 修饰静态方法,执行方法代码,需要获取 class 对象的锁;
public class TestSynchronizedStaticMethod {

    private static int count = 0;
    
    private static void add1000() {
//    private synchronized static void add1000() { //使用 synchronized 修饰 add100 方法,即可获得正确的值 30000
        for (int i = 0; i <1000; i++) {
            count++;
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        
        //启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有线程执行完毕
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
}

 

  • 锁定 Java 对象,修饰代码块,显示指定需要获取的 Java 对象锁。
public class TestSynchronizedCodeBlock {

    private int count = 0;
    
    //锁定的对象
    private final Object obj = new Object();
    
    private void add1000() {
        
        //执行下面的加 1000 的操作,都需要获取 obj 这个对象的锁
        synchronized (obj) {
            for (int i = 0; i <1000; i++) {
                count++;
            }
        }
    }
    
    //启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
    private void test() throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有线程执行完毕
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
    
    public static void main(String[] args) throws InterruptedException {
        //创建 TestSynchronizedNormalMethod 对象,调用 test 方法
        new TestSynchronizedCodeBlock().test();
    }
}

3.  锁的使用注意事项

  • synchronized 修饰代码块时,最好不要锁定基本类型的包装类,如 jvm 会缓存 -128 ~ 127 Integer 对象,每次向如下方式定义 Integer 对象,会获得同一个 Integer,如果不同地方锁定,可能会导致诡异的性能问题或者死锁;
Integer i = 100;
  • synchronized 修饰代码块时,要线程互斥地执行代码块,需要确保锁定的是同一个对象,这点往往在实际编程中会被忽视;
  • synchronized  不支持尝试获取锁、锁超时和公平锁;
  • ReentrantLock 一定要记得在 finally{} 语句块中调用 unlock() 方法释放锁,不然可能导致死锁;
  • ReentrantLock 在并发量很高的情况,由于自旋很消耗 CPU 资源;
  • ReentrantReadWriteLock 适合对共享资源写操作很少,读操作频繁的场景;可以从写锁降级到读锁,无法从读锁升级到写锁。

以上,是Java面试题【锁如何使用,有哪些注意事项】的参考答案。

 

输出,是最好的学习方法

欢迎在评论区留下你的问题、笔记或知识点补充~

—end—

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧