亲宝软件园·资讯

展开

浅析Java关键词synchronized的使用

拿了桔子跑-范德依彪 人气:0

1 引入Synchronized

synchronized对线程访问的影响:

2 Synchronized的使用

可以作用在方法上或者方法里的代码块:

2.1 对象锁

Synchronized修饰实例方法或者代码块(锁对象不是*.class),此时生产对象锁。多线程访问该类的同一个对象的sychronized块是同步的,访问不同对象不受同步限制。

2.1.1 Synchronized修饰实例方法

public static void main(String[] args){
        TempTest tempTest = new TempTest();
        Thread t1 = new Thread(() -> {
            tempTest.doing(Thread.currentThread().getName());
        });
        Thread t2 = new Thread(() -> {
            tempTest.doing(Thread.currentThread().getName());
        });
        t1.start();
        t2.start();
    }

    //同一时刻只能被一个线程调用
    private synchronized void doing(String threadName){
        for(int i=0;i<3;i++){
            System.out.println("current thread is : "+threadName);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {}
        }
    }

运行结果:

current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-1
current thread is : Thread-1
current thread is : Thread-1

2.1.2 Synchronized修饰代码块

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();
    @Override
    public void run() {
        // 同步代码块形式:锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
        synchronized (this) {
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}

运行结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

2.2 类锁

synchronize修饰静态方法或指定锁对象为Class,此时产生类锁。多线程访问该类的所有对象的sychronized块是同步的

2.2.1 synchronize修饰静态方法

    public static void main(String[] args){
        TempTest tempTest1 = new TempTest();
        TempTest tempTest2 = new TempTest();
        //虽然创建了两个TempTest实例,但是依然是调用同一个doing方法(因为是个static);因此doing还是会依次执行
        Thread t1 = new Thread(() -> tempTest1.doing(Thread.currentThread().getName()));
        Thread t2 = new Thread(() -> tempTest2.doing(Thread.currentThread().getName()));
        t1.start();
        t2.start();
    }

    //修饰静态方法,则是类锁;
    private static synchronized void doing(String threadName){
        for(int i=0;i<3;i++){
            System.out.println("current thread is : "+threadName);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {}
        }
    }

运行结果:有序输出 【如果去掉static ,则线程会交替执行doing】

current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-0
current thread is : Thread-1
current thread is : Thread-1
current thread is : Thread-1

2.2.2 synchronize指定锁对象为Class

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 所有线程需要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}

结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

3 Synchronized原理分析

3.1 虚拟机如何辨别和处理synchronized

虚拟机可以从常量池中的方法表结构中的ACC_ SYNCHRONIZED访问标志区分一个方法是否是同步方法。

当调用方法时,调用指令将会检查方法的ACC_ SYNCHRONIZED访问标志是否设置,如果设置了,执行线程将先持有同步锁,然后执行方法,最后在方法完成时释放同步锁。

在方法执行期间,执行线程持有了同步锁,其他任何线程都无法再获得同一个锁。

如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的锁将在异常抛到同步方法之外时自动释放。

3.2 虚拟机对synchronized的编译处理

以下代码:

public class Foo {
    void onlyMe(Foo f) {
        synchronized(f) {
            doSomething();
        }
    }
    private void doSomething(){ }
}

编译后,这段代码生成的字节码序列如下:

3.3 虚拟机执行加锁和释放锁的过程

那么重点来了到这里,有几个问题需明确:

1. 什么叫对象的锁?对象的内存结构参考文末补充内容

2. 如果确定锁被线程持有?

3 执行monitorenter后,对象发生什么变化?

4 锁计数值保存在哪里

我还没搞懂。

monitorenter指令执行的过程

4 Synchronized与Lock

synchronized的缺陷

5 使用Synchronized有哪些要注意的

锁对象不能为空,因为锁的信息都保存在对象头里

作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错

在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果有必要,使用synchronized关键,因为代码量少,避免出错

synchronized实际上是非公平的,新来的线程有可能立即获得执行,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。

知识补充

Java内存层面的对象认识 

说明:此分析基于HotSpot虚拟机

1 对象的创建

Java对象的创建方式有三种:

通过new方式的对象创建过程如下:

创建过程说明:

2 对象的内存布局

对象存储的内容分类以及明细如下:

关于对象头的补充说明

关于实例数据的补充说明

关于对齐填充的说明

不一定会存在,因为对象的大小一定是8字节的整数倍,因此需要对齐填充这部分,充当占位符

在32位字长的虚拟机下,对象的内存分布情况如下:

3 对象的访问定位

对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:

3.1句柄访问

说明:

句柄访问方式,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址

3.2 直接指针访问

说明:

使用直接指针来访问最大的好处就是速度更快,只需要一次定位就能找到实例数据,而句柄池则需要两次:(需要先定位句柄池,再定位实例数据)

加载全部内容

相关教程
猜你喜欢
用户评论