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多线程编程核心技术》

1. synchronized关键字在使用层面的理解 synchronized关键字是Java中用来实现线程同步的关键字,可以修饰方法和代码块。当线程访问被synchronized修饰的方法或代码块时,需要获取对象的锁,如果该锁已被其他线程获取,则该线程会进入阻塞状态,直到获取到锁为止。synchronized关键字可以保证同一时刻只有一个线程能够访问被锁定的方法或代码块,从而避免了多线程并发访问时的数据竞争和一致性问题。 2. synchronized关键字在字节码中的体现 在Java代码编译成字节码后,synchronized关键字会被编译成monitorenter和monitorexit指令来实现。monitorenter指令对应获取锁操作,monitorexit指令对应释放锁操作。 3. synchronized关键字在JVM中的实现 在JVM中,每个对象都有一个监视器(monitor),用来实现对象锁。当一个线程获取对象锁后,就进入了对象的监视器中,其他线程只能等待该线程释放锁后再去竞争锁。 synchronized关键字的实现涉及到对象头中的标志位,包括锁标志位和重量级锁标志位等。当一个线程获取锁后,锁标志位被设置为1,其他线程再去获取锁时,会进入自旋等待或者阻塞等待状态,直到锁标志位被设置为0,即锁被释放后才能获取锁。 4. synchronized关键字在硬件方面的实现 在硬件层面,锁的实现需要通过CPU指令和总线锁来实现。当一个线程获取锁时,CPU会向总线发送一个锁请求信号,其他CPU收到该信号后会进入自旋等待状态,直到锁被释放后才能获取锁。总线锁可以保证多个CPU之间的原子操作,从而保证锁的正确性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值