synchronized关键字(三)

一、三个结论

对于 synchronized(非 this 对象 x)

  1. 当多个线程同时执行 synchronized(x){} 同步代码块时呈现同步效果
  2. 当其他线程执行 x 对象中 synchronized 同步方法时呈同步效果
  3. 当其他线程执行 x 对象方法里面的 synchronized(this) 代码块时也呈同步效果

需要说的是,虽然有三点,都是本质都是一样的,即每个线程拥有相同的对象锁,可以看一下第2个结论的例子

class MyObject {
	//synchronized 同步方法
    synchronized public void speedPrintString() {
        System.out.println(Thread.currentThread().getName()
                + " speedPrintString begin " + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()
                + " speedPrintString end " + System.currentTimeMillis());
    }

}

class Service1 {

    public void testMethod(MyObject object) {

        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " testMethod begin "
                        + System.currentTimeMillis());
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " testMethod end "
                        + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class ThreadB1 extends Thread {

    private MyObject object;

    public ThreadB1(MyObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        //线程 B 执行 synchronized 同步方法
        object.speedPrintString();
    }

}

public class ThreadA1 extends Thread {

    private Service1 service1;
    private MyObject object;

    public ThreadA1(Service1 service1, MyObject object) {
        this.service1 = service1;
        this.object = object;
    }

    @Override
    public void run() {
        //线程 A 执行 testMethod 中的同步方法块
        service1.testMethod(object);
    }

    public static void main(String[] args) throws InterruptedException {
        Service1 service1 = new Service1();
        MyObject object = new MyObject();

        ThreadA1 threadA1 = new ThreadA1(service1, object);
        threadA1.setName("AAA");
        threadA1.start();

        Thread.sleep(2000);

        ThreadB1 threadB1 = new ThreadB1(object);
        threadB1.setName("BBB");
        threadB1.start();
    }

}

结果是:

AAA testMethod begin 1540385682634
AAA testMethod end 1540385685634
BBB speedPrintString begin 1540385685634
BBB speedPrintString end 1540385687634

结果是同步执行的。同步方法块持有的是 object 对象锁,同步方法也是持有的 object 锁,这时因为两者都是对同一个 object 对象进行处理的。

第三个结论和第二个一样,不会是把同步方法变成了同步代码块,这里不加赘述

二、静态同步方法和synchronized(class)

synchronized 还可以作用于 static 静态方法上,表示对当前的 Class 类进行持锁

class Service3 {

    synchronized public static void printA() {
        try {
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + " 进入 printA ");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + " 离开 printA ");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        try {
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + " 进入 printB");
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + " 离开 printB");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void printC() {
        try {
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + " 进入 printC");
            Thread.sleep(4000);
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + " 离开 printC");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class ThreadB3 extends Thread{

    private Service3 service3;

    public ThreadB3(Service3 service3) {
        this.service3 = service3;
    }

    @Override
    public void run() {
        //调用 static 方法
        service3.printB();
    }
}

class ThreadC3 extends Thread {

    private Service3 service3;

    public ThreadC3(Service3 service3) {
        this.service3 = service3;
    }

    @Override
    public void run() {
        //调用非 static 方法
        service3.printC();
    }
}

public class ThreadA3 extends Thread {

    private Service3 service3;

    public ThreadA3(Service3 service3) {
        this.service3 = service3;
    }

    @Override
    public void run() {
        //调用 static 方法
        service3.printA();
    }

    public static void main(String[] args) {
        Service3 service3 = new Service3();
        ThreadA3 threadA3 = new ThreadA3(service3);
        threadA3.setName("AAA");
        threadA3.start();

        ThreadB3 threadB3 = new ThreadB3(service3);
        threadB3.setName("BBB");
        threadB3.start();

        ThreadC3 threadC3 = new ThreadC3(service3);
        threadC3.setName("CCC");
        threadC3.start();
    }

}

结果是:

AAA 在 1540386724917 进入 printA 
CCC 在 1540386724917 进入 printC
AAA 在 1540386726917 离开 printA 
BBB 在 1540386726917 进入 printB
CCC 在 1540386728917 离开 printC
BBB 在 1540386729931 离开 printB

从结果看出,线程 AAA 和线程 BBB 是同步执行的,而线程 CCC 和他们两个都是异步执行的。因为线程 AAA 和线程 BBB 调用的是同步 static 方法,因此他们俩持有的是类锁,线程 CCC 调用的是同步非 static 方法,因此他持有的是对象锁,持有的锁不同是异步的主要原因

因为线程 AAA 和线程 BBB 持有的是类锁,因此,只要调用的是同一个类,就算调用类的不同的对象,也是同步执行的

//使用两个线程持有相同类锁的情况下调用不同的对象
public static void main(String[] args) {
    Service3 service3 = new Service3();
    Service3 service31 = new Service3();
    ThreadA3 threadA3 = new ThreadA3(service3);
    threadA3.setName("AAA");
    threadA3.start();

    ThreadB3 threadB3 = new ThreadB3(service31);
    threadB3.setName("BBB");
    threadB3.start();
}

结果为:

AAA 在 1540387705925 进入 printA 
AAA 在 1540387707926 离开 printA 
BBB 在 1540387707926 进入 printB
BBB 在 1540387710927 离开 printB

可见类锁对类的所有实例起作用

同步 synchronized(class) 代码块

同步 synchronized(this) 代码块的作用和 synchronized static 方法的作用一样,都是持有类的锁

class Service4 {

    public void printA() {
        synchronized (Service4.class) {
            try {
                System.out.println(Thread.currentThread().getName() + " 在 "
                        + System.currentTimeMillis() + " 进入 printA");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " 在 "
                        + System.currentTimeMillis() + " 离开 printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void printB() {
        synchronized (Service4.class) {
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + "进入 printB");
            System.out.println(Thread.currentThread().getName() + " 在 "
                    + System.currentTimeMillis() + "离开 printB");
        }
    }

}

class ThreadB4 extends Thread{

    private Service4 service4;

    public ThreadB4(Service4 service4) {
        this.service4 = service4;
    }

    @Override
    public void run() {
        service4.printA();
    }
}

public class ThreadA4 extends Thread {

    private Service4 service4;

    public ThreadA4(Service4 service4) {
        this.service4 = service4;
    }

    @Override
    public void run() {
        service4.printB();
    }

    public static void main(String[] args) {
        Service4 service4 = new Service4();
        Service4 service41 = new Service4();
        ThreadA4 threadA4 = new ThreadA4(service4);
        threadA4.setName("AAA");
        threadA4.start();

        ThreadB4 threadB4 = new ThreadB4(service41);
        threadB4.setName("BBB");
        threadB4.start();
    }

}

结果是:

AAA 在 1540387998044进入 printB
AAA 在 1540387998045离开 printB
BBB 在 1540387998045 进入 printA
BBB 在 1540388000045 离开 printA

三、String常量池带来的问题

在使用 synchronized(非 this 对象 x) 的时候,对象 x 最好不要设置成 String 类型的,这时由于 String 有常量池的问题,如果有这样的方法

public void print(String str) {
    synchronized (str) {
        while(true) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

那么当我们在多个线程中运行这个方法,并且以下两个 String 类型参数 a 和 b 时

String a = "A";
String b = "A";

因为 String 常量池的问题,所以对象 a 是 == 对象 b 的,当我们用不同的线程调用 print 方法并且分别传入参数 a 和 b 时,那么这两个线程持有的就是相同的对象锁,造成一个线程在执行,而一个线程用于在等待的情况,结果类似这样:

Thread-0 AAAA
Thread-0 AAAA
Thread-0 AAAA
Thread-0 AAAA
...

所以,大多数情况下,同步 synchronized 代码块都不使用 String 作为锁对象,而使用其他的,比如 Object 等

四、死锁

因为不同的线程都在等待根本不可能被释放的锁,而导致所有的任务都无法继续完成,这个就是死锁

public class DealThread implements Runnable {

    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setFlag(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock2) {
                    System.out.println("按 lock1 -> lock2 代码顺序执行");
                }
            }
        }

        if (username.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock1) {
                    System.out.println("按 lock2 -> lock1 代码顺序执行");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DealThread dealThread = new DealThread();
        dealThread.setFlag("a");

        Thread thread = new Thread(dealThread);
        thread.start();

        Thread.sleep(2000);

        dealThread.setFlag("b");
        Thread thread1 = new Thread(dealThread);
        thread1.start();
    }
}

结果是:

username = a
username = b

可以看到,此时两个线程发生了死锁,具体是这样的:

  1. 线程 A 先持有对象 lock1 的锁,然后在持有 lock1 锁的时候又想去申请 lock2 的锁,此时他还没有释放 lock1 的锁;
  2. 线程 B 先持有对象 lock2 的锁,然后在持有 lock2 锁的时候又想去申请 lock1 的锁,此时他还没有释放 lock2 的锁;
  3. 两方都握有自己的锁不放弃,而又同时申请另一方的锁,所以,此时就造成了死锁

虽然这个实验实验 synchronized 嵌套的代码结构来实现死锁,其实不用嵌套的 synchronized 代码结构也会出现死锁,与嵌套不嵌套没有关系。只要互相等待对方释放锁就有可能出现死锁

五、参考

《Java多线程编程核心技术》

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/83352344