并发编程【八锁问题】

一、场景一

两个同步方法,一部手机,请问先打印邮件还是短信?

class Phone{
    public synchronized void sendEmail() throws Exception{
        System.out.println("*******sendEmail");
    }
    public synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendEmail

sendMs

分析:

因为 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(this对象),而线程 B 无法获取到锁对象,所以线程 A 先执行,然后才是线程 B 执行。

二、场景二

发送邮件的方法睡眠4秒钟,请问先打印邮件还是短信?

class Phone{
    public synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendEmail

sendMs

分析:

因为 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(this对象),而线程 B 无法获取到锁对象,所以线程 A 先执行,A 执行完毕后才是线程 B 执行。

三、场景三

新增一个普通方法 hello(),请问先打印邮件还是 hello?

class Phone{
    public synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
    public void sayHello() throws Exception{
        System.out.println("*****sayHello");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sayHello

endEmail

分析:

因为 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(this对象),而线程 B 调用的 sayHello 方法不需要 获取锁,所以在线程 A 睡眠的时候,线程 B 可以直接运行。

四、场景四

两部手机,请问先打印邮件还是短信?

class Phone{
    public synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
		Phone phone2 = new Phone();
        
        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone2.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendMs

sendEmail

分析:

因为有两个手机实例,所以存在两个 this 对象,因此两个线程获取的锁对象不是同一个,所以线程 A、B 不存在锁的争夺情况,从而在线程 A 睡眠期间,线程 B 可以运行。

五、场景五

两个静态同步方法,同一部手机,请问先打印邮件还是短信?

class Phone{
    public static synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public static synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        
        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendEmail

sendMs

分析:

因为 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(class对象),而线程 B 无法获取到锁对象,所以线程 A 先执行,A 执行完毕后才是线程 B 执行。

六、场景六

两个静态同步方法,两部手机,请问先打印邮件还是短信?

class Phone{
    public static synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public static synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        
        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone2.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendEmail

sendMs

分析:

因为 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(class对象),而线程 B 无法获取到锁对象,所以线程 A 先执行,A 执行完毕后才是线程 B 执行。

补充:两部手机只能说明存在两个 this 对象,但是 class 对象还是只有一个。

七、场景七

一个普通同步方法,一个静态同步方法,一部手机,请问先打印邮件还是短信?

class Phone{
    public static synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        
        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendMs

sendEmail

分析:

尽管 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(class对象);

但是因为线程 B 是调用的同步方法,锁对象为 this 对象,所以两个线程的锁对象不是同一个,不存在争用的情况,所以在线程 A 睡眠的时候,线程 B 可以继续执行。

八、场景八

一个普通同步方法,一个静态同步方法,两部手机,请问先打印邮件还是短信?

class Phone{
    public static synchronized void sendEmail() throws Exception{
    	try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("*******sendEmail");
    }
    public synchronized void sendMs() throws Exception{
        System.out.println("*******sendMs");
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        
        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone2.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

结果:

sendMs

sendEmail

分析:

尽管 Thread.sleep(100); 这一行代码保证了一定会让 A 线程先启动,所以线程 A 先获取到锁对象(class对象);

但是因为线程 B 是调用的同步方法,锁对象为 this 对象,所以两个线程的锁对象不是同一个,不存在争用的情况,所以在线程 A 睡眠的时候,线程 B 可以继续执行。

补充:两部手机只能说明存在两个 this 对象,但因为两个线程中一个用的 class,一个用的 this,所以还是两个不同的锁对象。

九、总结

其实这些都是关于锁对象的问题,只需要正确的分析出锁对象,即可解决问题。

若有错误,欢迎指正!

原文地址:https://www.cnblogs.com/Java-biao/p/14492884.html