对象和变量的并发访问synchronized解析以及死锁分析排查

一.synchronized
  “非线程安全"是指发生在多个线程对同一个对象中的实例变量并发访问时,产生的”脏读“现象。synchronized同步处理可解决这一问题。
非线程安全问题存在于实例变量中,不存在方法内部的私有变量。
1、synchronized修饰方法的两种情况:
(1).当A线程调用某个对象的synchronized方法,先持有某个对象的锁;这时B线程需要等待A线程执行完毕后释放这个对象锁才可调用这个对象的synchronized方法,即同步。synchronized是一个独占锁,每个锁请求之间是互斥的
(2).当A线程调用某个对象的synchronized方法时,B线程调用这个对象的其他非synchronized方法,不需要等待。
下面是上面两种结论的证明:
/**
 * @author tangquanbin
 * @date 2018/11/26 21:12
 */
public class Service2 {
    /**
     * 同步方法
     */
    public synchronized void printService() {
        System.out.println(Thread.currentThread().getName() + " " + "start printService thread");
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "printService end ");
    }

    /**
     * 同步方法
     */
    public synchronized void printServiceOther() {
        System.out.println(Thread.currentThread().getName() + " " + "start printServiceOther thread");
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "printServiceOther end ");
    }

    /**
     * 非同步方法
     */
    public void printServiceNotSynchronized() {
        System.out.println(Thread.currentThread().getName() + " " + "start printServiceNotSynchronized thread");
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "printServiceNotSynchronized end ");
    }
}

  线程A

public class Syn2ThreadA extends Thread{
    private Service2 service;
    public Syn2ThreadA(Service2 service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.printService();
    }
}

  线程B

public class Syn2ThreadB extends Thread{
    private Service2 service;
    public Syn2ThreadB(Service2 service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.printServiceNotSynchronized();
    }
}

  线程C

public class Syn2ThreadC extends Thread{
    private Service2 service;
    public Syn2ThreadC(Service2 service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.printServiceOther();
    }
}

  测试方法:

public class Syn2Test {
    public static void main(String[] args) {
        Service2 service = new Service2();
        //Syn2ThreadA调用了同步方法
        Syn2ThreadA threadA = new Syn2ThreadA(service);
        threadA.setName("threadA");
        //Syn2ThreadB调用非同步
        Syn2ThreadB threadB = new Syn2ThreadB(service);
        threadB.setName("threadB");
        //Syn2ThreadC调用了同步方法
        Syn2ThreadC threadC = new Syn2ThreadC(service);
        threadC.setName("threadC");
        threadA.start();
        threadB.start();
        threadC.start();
    }
}
可通过执行顺序证明以上结论的正确性。
 
2、synchronized重入
可重入锁:即某个线程可以获得一个它自己已持有的锁。下面的例子在继承关系中子类可以通过可重入锁调用父类的同步方法,提升了加锁行为的封装性。如果没有可重入锁就会产生死锁。
父类
public class Fruit {
    public synchronized void dosomething(){
        System.out.println("printFruit");
    }
}

 子类

public class Apple extends Fruit {
    @Override
    public synchronized void dosomething() {
        super.dosomething();
        System.out.println("apple");
    }
}

  

3.死锁

  那什么是死锁呢? 

      下面是维基百科对死锁的定义:

  死锁(英语:Deadlock),又译为死结,计算机科学名词。当两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称为死锁。
死锁的四个条件是:
  禁止抢占 no preemption - 系统资源不能被强制从一个进程中退出
  持有和等待 hold and wait - 一个进程可以在等待时持有系统资源
  互斥 mutual exclusion - 只有一个进程能持有一个资源
  循环等待 circular waiting - 一系列进程互相持有其他进程所需要的资源
死锁只有在这四个条件同时满足时出现。预防死锁就是至少破坏这四个条件其中一项,即破坏“禁止抢占”、破坏“持有等待”、破坏“资源互斥”和破坏“循环等待”。
下面这张图片描述了死锁情况:

编码说明:

/**
 * @author tangquanbin
 * @date 2018/11/26 22:37
 */
public class TestDeadlock {

    static final String resource1 = "resource1";
    static final String resource2 = "resource2";

    static class ThreadA extends Thread {
        @Override
        public void run() {
            synchronized (resource1) {
                System.out.println("ThreadA: locked resource 1");
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource2) {
                    System.out.println("ThreadA: locked resource 2");
                }
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            synchronized (resource2) {
                System.out.println("ThreadB: locked resource 2");
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource1) {
                    System.out.println("ThreadB: locked resource 1");
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        threadA.setName("====ThreadA====");
        ThreadB threadB = new ThreadB();
        threadB.setName("====ThreadB====");
        threadA.start();
        threadB.start();
    }
}

  运行会产生死锁情况。

4.追踪、分析死锁发生

死锁检查方法,命令窗口运行:
1、jps
2、jstack -l 端口     //-l 选项用于打印锁的附加信息
 
 
下面是部分死锁信息:
 
死锁只有在这四个条件同时满足时出现。预防死锁就是至少破坏这四个条件其中一项,即破坏“禁止抢占”、破坏“持有等待”、破坏“资源互斥”和破坏“循环等待”。

猜你喜欢

转载自www.cnblogs.com/tangquanbin/p/10023807.html