synchronized关键字在同步代码块中的应用(下)

本文详细探讨了Java Class的单例实现,介绍了静态同步方法(synchronized)的应用,涉及锁对象的选择、String常量池的影响、死锁问题及其解决方案,以及内置类同步机制。通过实例演示,揭示了同步代码块在不同场景下的异步与同步行为。
摘要由CSDN通过智能技术生成

类Class的单例性

每一个*.java文件对应Class类的实例都是一个,在内存中是单例的,测试代码如下:

public class Test {
    public static void main(String[] args) {
        MyTest test1 = new MyTest();
        MyTest test2 = new MyTest();
        MyTest test3 = new MyTest();
        MyTest test4 = new MyTest();

        System.out.println(test1.getClass() == test1.getClass());
        System.out.println(test1.getClass() == test2.getClass());
        System.out.println(test1.getClass() == test3.getClass());
        System.out.println(test1.getClass() == test4.getClass());

        SimpleDateFormat format1 = new SimpleDateFormat();
        SimpleDateFormat format2 = new SimpleDateFormat();
        SimpleDateFormat format3 = new SimpleDateFormat();
        SimpleDateFormat format4 = new SimpleDateFormat();

        System.out.println(format1.getClass() == format1.getClass());
        System.out.println(format1.getClass() == format2.getClass());
        System.out.println(format1.getClass() == format3.getClass());
        System.out.println(format1.getClass() == format4.getClass());
    }
}
output:
true
true
true
true
true
true
true
true

Class类用于描述类的基本信息,包括有多少个字段,有多少个构造方法,有多少个普通方法等,为了减少对内存的高占用率,在内存中只需要存在一份Class类对象就可以了,所以被设计成单例的。

静态同步synchronized方法与synchronized(class)代码块

关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前*.java文件对应的Class类对象进行持锁,Class类的对象是单例的,更具体地说,在静态static方法上使用synchronized关键字声明同步方法时,使用当前静态方法所在类对应Class类的单例对象作为锁。

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}
public class Run {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
    }
}
output:
线程名称为:A在1655824945776进入printA
线程名称为:A在1655824948791离开printA
线程名称为:B在1655824948791进入printB
线程名称为:B在1655824948791离开printB

synchronized关键字加到static静态方法上的方式是将Class类对象作为锁,而synchronized关键字加到非static静态方法上的方式是将方法所在类的对象作为锁。

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
    }

    synchronized public void printC(){
        System.out.println("线程名称为:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"进入printC");
        System.out.println("线程名称为:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"离开printC");
    }
}
public class ThreadC extends Thread{
    private Service service;

    public ThreadC(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run(){
        service.printC();
    }
}
output:
线程名称为:A在1655825737790进入printA
线程名称为:C在1655825737790进入printC
线程名称为:C在1655825737790离开printC
线程名称为:A在1655825740805离开printA
线程名称为:B在1655825740805进入printB
线程名称为:B在1655825740805离开printB

异步运行的原因是持有不同的锁,一个是将类Service的对象作为锁,另一个是将Service类对应Class类的对象作为锁,A、B线程和C线程是异步的关系,而A线程和B线程是同步的关系。

同步syn static方法可以对类的所有对象实例起作用

Class锁可以对类的所有对象实例起作用。

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
    }
}
public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printA();
    }
}
public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.printB();
    }
}
output:
线程名称为:A在1655826421956进入printA
线程名称为:A在1655826424967离开printA
线程名称为:B在1655826424967进入printB
线程名称为:B在1655826424967离开printB

同步syn(class)代码块可以对类的所有对象实例起作用

同步synchronized(class)代码块的作用其实和synchronized static方法的作用一样。

String常量池特性与同步相关的问题与解决方案

JVM具有String常量池的功能,所以如下代码结果为true

public class MyTest {
    public static void main(String[] args) {
        String a = "a";
        String b = "a";
        System.out.println(a == b);
    }
}

当synchronized(string)同步块与String联合使用时,要注意常量池会带来一些意外。

public class Service {
    public static void print(String stringParam) {
        try {
            synchronized (stringParam) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.print("AA");
    }
}
public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.print("AA");
    }
}
public class Test {
    public static void main(String[] args) {
        Service service = new Service();

        ThreadA t1 = new ThreadA(service);
        t1.setName("A");
        t1.start();

        ThreadA t2 = new ThreadA(service);
        t2.setName("B");
        t2.start();
    }
}
output:
A
A
A
A
A
A
A
A
A
A

出现这种情况的原因是String的两个值都是AA,两个线程持有相同的锁,造成B线程不能执行。这就是String常量池所带来的问题,所以大多数情况下同步synchronized代码块不适用String作为锁对象,而改用其他,例如,new Object()实例化一个新的Object对象,它并不放入缓存池中,或者执行new String()创建不同的字符串对象,形成不同的锁。

public class Service {
    public static void print(Object obj) {
        try {
            synchronized (obj) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
output:
A
B
A
B
B
A
B
A
A
B
B
A

同步synchronized方法无限等待问题与解决方案

使用同步方法会导致锁资源被长期占用,得不到运行的机会,如下:

public class Service {
    synchronized public void methodA(){
        System.out.println("methodA begin");
        boolean isContinueRun = true;
        while(isContinueRun){}
        System.out.println("methodA end");
    }

    synchronized public void methodB(){
        System.out.println("methodB begin");
        System.out.println("methodB end");
    }
}
output:
methodA begin

B线程永远得不到运行的机会,这时可以使用同步块来解决这样的问题:

public class Service {
    Object o1 = new Object();
    public void methodA(){
        synchronized (o1){
            System.out.println("methodA begin");
            boolean isContinueRun = true;
            while(isContinueRun){}
            System.out.println("methodA end");
        }
    }

    Object o2 = new Object();
    public void methodB(){
        synchronized (o2){
            System.out.println("methodB begin");
            System.out.println("methodB end");
        }
    }
}
output:
methodA begin
methodB begin
methodB end

多线程的死锁

java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中,“死锁”是必须避免的,因为这会造成线程“假死”。

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(3000);
                } 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(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

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

            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Service service = new Service();

        ThreadA t1 = new ThreadA(service);
        t1.setName("A");
        t1.start();

        ThreadB t2 = new ThreadB(service);
        t2.setName("B");
        t2.start();
    }
}
output:
username = a
username = b

可以使用jdk自带工具来监测是否有死锁现象,执行jps命令和jstack命令
image

image

死锁是程序设计的bug,在设计程序时要避免双方互相持有对方的锁,只要互相等待对方释放锁,就有可能出现死锁。

内置类与同步

  • 内置类中有两个同步方法,但使用的是不同的锁,输出结果也是异步的。
  • 同步代码块synchronized(lock)对lock上锁后,其他线程只能以同步的方式调用lock中的同步方法。

锁对象改变导致异步执行

在将任何数据类型作为同步锁时,需要注意是否有多个线程同时争抢锁对象。如果多个线程同时争抢相同的对象,则这些线程之间就是同步的;如果多个线程分别获得自己的锁,则这些线程之间就是异步的。

通常情况下,一旦持有锁后就不再对锁对象进行更改,因为一旦更改就有可能出现一些错误。

public class Service {
    private String lock = "123";

    public void testMethod() {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
                lock = "456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();

        ThreadA a = new ThreadA(service);
        a.setName("a");

        ThreadB b = new ThreadB(service);
        b.setName("b");

        a.start();
        Thread.sleep(50);
        b.start();
    }
}
output:
a begin 1656170359345
b begin 1656170359408
a end 1656170361360
b end 1656170361423

50ms后,B线程取得的锁是“456”。

去掉50ms后,可能出现同步,也可能出现异步。

锁对象不改变依然同步执行

如果锁是一个User对象,其中一个线程持有该锁后,修改了User对象的属性。与其他线程依然是同步执行,因为持有的锁对象为同一个,仅仅对象的属性改变了,但对象未发生改变。

同步写法案例比较多

使用关键字synchronized的写法比较多,常用的有以下几种:

public class MyService {
    synchronized public static void testMethod1(){}
    
    public void testMethod2(){
        synchronized (MyService.class){}
    }
    
    synchronized public void testMethod3(){}
    
    public void testMethod4(){
        synchronized (this){}
    }
    
    public void testMethod5(){
        synchronized ("abc"){}
    }
}

上面代码出现了3种类型的锁对象:

  • testMethod1()和testMethod2()持有的锁是同一个,即MyService.java对应Class类的对象。
  • testMethod3()和testMethod4()持有的锁是同一个,即MyService.java类的对象。
  • testMethod5()持有的锁是字符串abc。

相同的锁是同步关系,不相同的锁是异步关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值