多线程学习笔记--02(对象及变量的并发访问,Object锁,Class锁,Synchronized和volatile)

  1.学习目标    

       什么是Object锁?Object锁的几种方式  优缺点是什么?

       什么是Class锁? Class锁的几种方式

       可重入锁是什么意思? 锁支不支持继承?

       多线程下同步和异步的核心是看什么?

        数据类型为String的常量池的特性

        写一个死锁的程序出来,不要用死循环

     关键字volatile的主要作用

     关键字Synchronized和volatile的主要区别以及使用情况

   先来理解理解什么是对象的发布与逸出?

    发布(publish) 使对象能够在当前作用域之外的代码中使用

    逸出(escape) 当某个不应该发布的对象被发布了

   常见逸出的有下面几种方式:

  • 静态域逸出

  • public修饰的get方法

  • 方法参数传递

  • 隐式的this

静态域逸出:

  

public修饰get方法:

 

逸出就是本不应该发布对象的地方,把对象发布了。导致我们的数据泄露出去了,这就造成了一个安全隐患!理解起来是不是简单了一丢丢?

安全发布对象有几种常见的方式:

  • 在静态域中直接初始化 : public static Person = new Person();

    • 静态初始化由JVM在类的初始化阶段就执行了,JVM内部存在着同步机制,致使这种方式我们可以安全发布对象

  • 对应的引用保存到volatile或者AtomicReferance引用中

    • 保证了该对象的引用的可见性和原子性

  • 由final修饰

    • 该对象是不可变的,那么线程就一定是安全的,所以是安全发布~

  • 由锁来保护

    • 发布和使用的时候都需要加锁,这样才保证能够该对象不会逸出

  Synchronized是什么?有什么作用?

   synchronized是一种互斥锁

  • 一次只能允许一个线程进入被锁住的代码块

   synchronized是一种内置锁/监视器锁

  • Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的!

  有什么作用?

  • synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)

  • synchronized还保证了可见性。(当执行完synchronized之后,修改后的变量对其他的线程是可见的)

Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。

 synchronized的原理是什么?

  

   2. Synchronized对象监视器为Object时的使用

     1.synchronized可以修饰方法,两个线程访问同一个对象(这个对象只new了一次)中的同步方法的时候一定是线程安全的,即按照顺序执行的,也就是说A线程执行完了再执行B线程。如果两个线程访问同一个类的不同的两个实例(即便他的方法是同步的,这会创建两个锁),这其实数据不共享了,不会出现线程安全的问题,两个线程是异步执行的。为什么他们是异步执行?这就说明synchronized取得的锁是对象锁,而不是把一段代码或者方法当作锁。需要注意的是:方法中的变量不存在线程安全的问题,永远都是线程安全的,这是方法内部的私有变量的特性造成的。

    总结:

      A线程先持有object对象的Lock锁(就是A线程调用object的同步方法),这时候B线程可以以异步的方式调用object对象中的非synchronized类型的方法。

      如果A线程先持有object对象的Lock锁(就是A线程调用object的同步方法),B线程这时候再调用object对象中的synchronized类型的方法时则需等待。

    脏读:发生脏读的情况是在读取实力变量时,此值已经被其他线程修改过了。即便赋值的方法是同步的,取值的方法不同步也会出现脏读的情况,解决办法当然是同步。

    Synchronized锁重入:自己可以再次获取自己的内部锁,比如有一条线程获得了某个对象锁,此时这个对象锁还没有被释放,当其再次想要获取这个对象的锁的时候还是可以获取的。比如在一个类中,一个线程同步的方法可以调用另一个线程同步的方法。如果不可重入锁的话就会造成死锁。需要注意的是:可重入锁也支持在父子类继承的环境中,当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。需要注意的是:这里是可重入锁支持继承,而不是同步支持继承,锁具有继承性是指子类调用父类的同步方法,而同步不具有继承性是子类重写父类的同步方法不具有同步的效果。

    2.synchronized同步代码块的使用,Synchronized方法存在的弊端,如果A线程需要执行一个长时间的任务,那么B线程必须等待很长的时间,用同步代码块可以解决这个问题。因为synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象(某一个对象可以是this,非this等等)进行加锁。

     使用同步代码块的好处当然是可以弥补同步方法的缺陷,即:使用同步代码块来加快同步方法的执行时间,就是把可能出现线程不安全的赋值操作等等用synchronized(this)包裹起来。因为当一个线程访问object的一个synchronized同步代码块时。另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。如果同步方法出现无限等待的问题,就是如果一个线程调用的同步方法陷入一个死循环,其他线程永远没有机会执行该对象的其他同步方法。有没有解决的办法呢?当然有,就是把同步方法换成同步代码块代替,这样进入方法的的时候就不会被阻塞。

    总结:

    同步代码块和同步方法的作用:对其他synchronized方法或synchronized(this)同步代码块调用呈阻塞状态。同一时间只有一个线程可执行同步代码块或同步方法。

   synchronized不仅可以锁this对象,可以将任意对象作为对象监视器,这个任意对象大多数是实例变量及方法的参数,使用的格式为synchronized(非this对象),当然多个线程持有对象监视器持为同一个对象的前提下(必须是同一个锁),同一时间只有一个线程可执行synchronized(非this对象)同步代码块中的代码。

   锁非this对象相比锁this对象和同步方法的好处,如果在一个类中有很多个synchronized方法,这时虽然能够实现同步,但是会受到阻塞,所以会影响运行效率,但是使用同步代码块锁非this对象。synchronized(非this)代码块中的程序与同步方法是异步的,不会与其他锁this同步方法争抢this锁,则可大大提高运行效率。再次强调:对象监视器必须监视的是同一个对象(synchronized锁的是同一个对象)如果监视的不是同一个对象,运行的结果就是异步了。

   同步代码块放在非同步的方法中进行声明,并不能保证调用方法的线程的执行同步,也就是说线程调用方法的顺序是无序的。虽然在同步块中执行的顺序是同步的,这样极易出现脏读的问题。

3.Synchronized对象监视器为Class时的使用

  synchronized作用在静态方法上相当于对当前.java文件所对应的Class类进行持锁。而synchronized关键字加到非静态方法是给对象上锁。他们有什么区别呢?由于类是由Class文件生成,如果对Class文件上锁,那么对类的所有实例起作用。

4.数据类型String的常量池特性

  因为JVM中具有String常量池的缓冲功能,所以String a=”a” String b=”a”这样的话a和b是完全相等的。如果此时用synchronized锁住这两个相同的字符串,当多个线程传入相同的字符串进去的时候,只会有一个线程被执行,因为锁是一样的,最好的是锁一个new Object的对象,这样拿到的是不同锁,线程异步执行。

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

写一个死锁的程序  不使用死循环

public class DeadThead extends Thread {
    private String param;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void setParam(String param) {
        this.param = param;
    }

    @Override
    public void run() {
        super.run();
        if (param.equals("a")) {
            synchronized (lock1) {
                System.out.println("param is a");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("a end");
                }
            }

        }
        if (param.equals("b")) {
            synchronized (lock2) {
                System.out.println("param is b");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("b end");
                }
            }
        }
    }
}

    public static void main(String[] args) throws Exception {
        DeadThead deadThead = new DeadThead();
        deadThead.setParam("a");
        Thread thread1 = new Thread(deadThead);
        thread1.start();
        Thread.sleep(100);
        deadThead.setParam("b");
        Thread thread2 = new Thread(deadThead);
        thread2.start();
    }

5.关键字volatile的主要作用

   关键字volatile主要作用是使变量在多个线程之间可见,关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

线程启动的时候线程的变量会存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程的效率,线程一直在私有堆栈中拿值,普通情况下线程修改变量的值只是修改公共堆栈的值,而私有堆栈的值仍未改变,所以解决这个问题的办法就是用volatile修饰变量的值。迫使线程去从公共堆栈取值,达到变量在多个线程之间可见的一个效果。但是volatile最致命的缺点是不支持原子性。

  如何理解volatile不支持原子性,比如volatile修饰的变量i去执行++i,--i等运算的时候,当多个线程来执行这个操作的时候还是会出现不同步的情况,解决的办法当然是使用synchronized来关键字。

  Synchronizedvolatile 的区别?

  1. 首先要明白的一点是volatile解决变量在多个线程之间的可见性。而synchronized是解决多个线程之间访问资源的同步性。
  2. Volatile是线程同步的轻量级实现,效率要比synchronied要好,但是volatile只能修饰变量,而synchronized可以修饰方法和代码块。
  3. 多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
  4. Volatile能保证数据的可见性,但是不能保证数据的原子性。Sychronized既可以保证原子性也可以保证可见性,因为他会将私有内存和公共内存的数据做同步。

 Volatile的本质是JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。

使用了volatile修饰的变量保证了三点

  • 一旦你完成写入,任何访问这个字段的线程将会得到最新的值

  • 在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

  • volatile可以防止重排序(重排序指的就是:程序执行的时候,CPU、编译器可能会对执行顺序做一些调整,导致执行的顺序并不是从上往下的。从而出现了一些意想不到的效果)。而如果声明了volatile,那么CPU、编译器就会知道这个变量是共享的,不会被缓存在寄存器或者其他不可见的地方。

一般来说,volatile大多用于标志位上(判断操作),满足下面的条件才应该使用volatile修饰变量:

  • 修改变量时不依赖变量的当前值(因为volatile是不保证原子性的)

  • 该变量不会纳入到不变性条件中(该变量是可变的)

  • 在访问变量的时候不需要加锁(加锁就没必要使用volatile这种轻量级同步机制了)

   

      

    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空恋旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值