【Java并发编程】synchronized(三):使用注意事项、死锁示例

1.使用注意(6点)

1)synchronized 加在静态方法(static)时锁的是类,比如 synchronized (A.class)

2)synchronized 的锁粒度应该尽量小,保证原子性即可

public class T {
    
    

    int count = 0;

    synchronized void m1() {
    
    
        // do sth need not sync
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        // 业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
        count ++;

        // do sth need not sync
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

    void m2() {
    
    
        // do sth need not sync
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        // 业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
        // 采用细粒度的锁,可以使线程争用时间变短,从而提高效率
        synchronized(this) {
    
    
            count ++;
        }
        // do sth need not sync
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

3)synchronized 遇到异常时会自动释放锁,需要在catch块中做处理

程序在执行过程中,如果出现异常,默认情况锁会被释放

  • 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
  • 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
  • 因此要非常小心的处理同步业务逻辑中的异常
public class T {
    
    

    int count;

    synchronized void m1(){
    
    
        while (true){
    
    
            System.out.println(Thread.currentThread().getName()+" count="+count++);
            if(count == 5){
    
    
                try {
    
    
                    int i = 1/0; // 出现异常
                    System.out.println(i);
                }catch (Exception e){
    
    
                    throw new RuntimeException("!!!");
                }

            }
        }
    }

    public static void main(String[] args) {
    
    
        new Thread(()->new T().m1(),"t1").start();
        new Thread(()->new T().m1(),"t2").start();
    }

}

4)synchronized 只能锁住堆,不要锁不要以字符串常量作为锁定对象(字符串常量池)

在下面的例子中,m1和m2其实锁定的是同一个对象

public class T {
    
    
    String s1 = "abcd";

    String s2 = "abcd";

    void m1() {
    
    
        synchronized(s1) {
    
    

        }
    }

    void m2() {
    
    
        synchronized(s2) {
    
    

        }
    }
}

这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串“Hello”,但是你读不到源码,所以你在自己的代码中也锁定了"Hello",这时候就有可能发生非常诡异的死锁阻塞,因为你的程序和你用到的类库不经意间使用了同一把锁

5)避免将锁定对象的引用变成另外的对象

锁定某对象o,如果o的属性发生改变,不影响锁的使用;但是如果o变成另外一个对象,则锁定的对象发生改变,

public class T {
    
    
    Object o = new Object();

    void m() {
    
    
        synchronized(o) {
    
    
            while(true) {
    
    
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
    
    
        T t = new T();
        // 启动第一个线程
        new Thread(t::m, "t1").start();

        try {
    
    
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        // 创建第二个线程
        Thread t2 = new Thread(t::m, "t2");

        t.o = new Object(); // 锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会

        t2.start();

    }
}

6)synchronized 是可重入锁,在 synchronized 块中调用 synchronized 方法自动获得锁

一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说 synchronized 获得的锁是可重入的。

这里是继承中有可能发生的情形,子类调用父类的同步方法

public class T {
    
    
    synchronized void m(){
    
    
        System.out.println("m start");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        new TT().m();
    }
}

class TT extends T{
    
    
    @Override
    synchronized void m() {
    
    
        System.out.println("child m start");
        super.m();
        System.out.println("child m end");
    }
}

2.死锁示例

死锁就是,线程1 拿了 A 锁等 B 锁, 与此同时,线程2 拿了 B 锁等 A 锁…

public class DeadLcok {
    
    

    public static void main(String[] args) {
    
    
    	// 共享变量1
        Object A = new Object();
        // 共享变量2
        Object B = new Object();
        
        new Thread(() -> {
    
    
            System.out.println("t1启动。。");
            synchronized (A) {
    
    
                System.out.println("t1 拿到了 A 锁。");
            }
            System.out.println("t1 想拿 B 锁!");
            try {
    
    
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }, "t1").start();

        new Thread(()->{
    
    
            System.out.println("t2启动。。");
            synchronized (B) {
    
    
                System.out.println("t2 拿到了 B 的锁。");
            }
            System.out.println("t2想拿a1的锁!");
            try {
    
    
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"t2").start();
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_43935927/article/details/114048609