多线程编程学习二(对象及变量的并发访问).

一、概念

非线程安全:会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是取到的数据其实是被更改过的.

线程安全:获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

二、synchronized 同步方法

1、非线程安全的问题存在于实例变量中,如果变量是方法内部的私有变量,则不存在"非线程安全"的问题,永远是线程安全的,这是方法内部的变量是私有的特性造成的。

2、如果访问的是类的实例变量,并且方法没有加synchronized,则会造成多个线程误修改了同一个变量值,导致线程不安全的问题,这个问题上一篇博文已经提到过了。

3、调用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本没有同步的需要。也就是说,如果不同的线程,访问的都不是同一个实例变量,那么连线程对资源的争抢都不存在,哪里来的线程不安全的问题呢?所以也没有必要进行同步了。

4、synchronized 方法的锁 为这个类实例对象所持有,也就是说,一个Object对象中的不同synchronized方法 实际上持有的同一把锁,同属于Object的实例:

(1) A线程先持有object对象的Lock的锁,B线程可以以异步的方式调用object对象中的非synchronized 类型的方法、

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

5、脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。

6、"可重入锁":自己可以再次获取自己的内部锁,也就是在synchronized时,当一个线程得到一个对象锁时,再次请求此对象锁时是可以再次得到该对象的锁的。另外,可重入锁也支持在父子类继承的环境中,同一个对象锁不同的synchronized方法执行的顺序按照调用的顺序执行。

7、当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

8、同步不具有继承性,也就是说当子类继承父类的synchronized方法时,子类的方法是不具有同步性、不是线程安全的,synchronized关键字不能继承,如果子类的方法需要同步性,则需要手动加上synchronized关键字。

三、synchronized同步语句块

1、synchronized 同步方法存在一定的弊端,synchronized 同步方法中 没有对实例变量操作的那部分代码也需要进行线程等待,也带有锁机制。可是对程序来说,那部分代码完成可以异步执行,减少等待时间,提高运行效率,这样就有了synchronized同步语句块。

2、当两个并发线程访问同一个对象Object 中的synchronized(this)同步代码块时,一段时间只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块后才执行该代码块。

3、和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。当然,Java还支持对“任意对象” 作为锁对象 来实现同步的功能。这个“任意对象”大多数是实例变量 及方法的参数,使用格式为synchronized(非 this 对象)。

  • 多个线程的锁对象 为同一个 非this 对象时,同一时间只有一个线程可以执行synchronized(非 this 对象)同步块中的代码。
  • 多个线程的锁对象 不为同一个 非this 对象时,synchronized(非 this 对象)中的代码是可以异步执行的。

锁非this对象的优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序和同步方法是异步的,不与其它锁this对象同步方法争抢this锁,则可以大大提高运行效率。

4、判断多线程是同步还是异步执行synchronized 的依据就是:(只要对象没变,即使对象的属性被改变,运行的结果还是同步的。)

  • 多线程如果持有相同的锁对象,则这些线程之间就是同步的。
  • 多线程如果分别获得锁对象,则这些线程之间就是异步的。

5、关键字synchronized 还可以应用在static静态方法上,这样就是对当前的*.java文件对应的Class类进行持锁。这个可以参考我的这篇博客

6、要特别注意String常量池缓存的功能,因为可能两个String对象引用的是同一段内存空间。因此在大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object() 实例化一个Object对象,但他不放入缓存中。

7、程序中应避免出现死锁,死锁出现的原因是因为存在锁之间的交叉引用,两个线程都在等待对方释放锁:

四、volatile 关键字

1、多线程中存在私有堆栈中的值 和 公共堆栈中的值不同步的问题。什么意思呢?可能线程在一个地方修改了内存中变量的值,而其它地方线程却从私有堆栈中去读取不一致的变量值。

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

volatile private boolean running=false; //这样定义一个变量后,强调多线程running的读取是直接从内存读

3、synchronized 和 volatile的区别?

  • 关键字volatile 是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能用于修饰变量,而synchronized 可以修饰方法以及代码块。
  • 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
  • volatile 能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会把私有内存和公有内存中的数据做同步。(原子性:原子操作是不可分割的整体,没有其他线程能够中断或检查正在原子操作中的变量,可以在没有锁的情况下保证安全)
  • 总之,关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

4、关键字volatile 出现非线程安全的原因:

  • read 和 load 阶段:从主存复制变量到当前线程工作内存。
  • use 和 assign 阶段:执行代码,改变共享变量值。
  • store 和 write 阶段:用工作内存数据刷新主存对应变量的值。

    在多线程环境中,use和assign 是多次出现的,但这一操作并不是原子性,也就是说在read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,也就是私有内存和公有内存的变量不同步,所以计算出来的结果和预期不一样,也就出现了非线程安全的问题。

    对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的,例如线程 1 和线程 2 在进行read 和load 的操作中,发现主内存中count的值都是5,name就会加载这个最新的值,也就是说,volatile关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一个实例变量还是需要加锁同步。

5、除了使用synchronized关键字外,还可以使用 AtomicInteget 原子类实现同步。但是在具有逻辑性的情况在,原子类也并不完全 安全,原因在于虽然原子类的方法是原子的,但是方法和方法之间的调用却不是原子的(这个时候仍然需要synchronized进行同步)。

public class AddCountThread extends Thread {
    private AtomicInteger count =new AtomicInteger(0);

    @Override
    public void run() {
        super.run();
        for (int i=0;i<10000;i++){
            System.out.println(count.incrementAndGet());
        }
    }
}

6、关键字synchronized 不仅可以使多个线程访问同一个资源具有同步性,而且他还具有将线程工作内存中的私有变量和公共内存的变量进行同步的功能。它包含两个特征:互斥性和可见性。

7、学习多线程并发,要着重“外练互斥,内修可见”。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏待你如初见

Java多线程

1243
来自专栏nnngu

017 Java中的静态代理、JDK动态代理、cglib动态代理

一、静态代理 代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理。 静态代理由业务实现类、业务代理类两部分组...

3363
来自专栏技术博客

菜菜从零学习WCF六(数据协定)

  --默认情况下,Windows Communication Foundation(WCF)使用称为数据协定序列化程序的序列化引擎对数据进行序列化和反序列化(...

501
来自专栏木木玲

“类加载机制”详解

2751
来自专栏Micro_awake web

vue的计算属性computed和监听器watch

2833
来自专栏黑泽君的专栏

java基础学习_多线程02_多线程、设计模式_day24总结

791
来自专栏java思维导图

深入了解Java之虚拟机内存

用来指示程序执行哪一条指令,这跟汇编语言的程序计数器的功能在逻辑上是一样的。JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要...

863
来自专栏三木的博客

Linux shell 程序设计4——shell变量

1、shell变量没有类型,所有变量都被当作字符串来处理。 2、shell变量的命名和c语言相同。 3、shell变量赋值和c语言略有不同,shell赋值要求等...

2206
来自专栏Python爬虫与数据挖掘

一篇文章助你理解Python3中字符串编码问题

前几天给大家介绍了unicode编码和utf-8编码的理论知识,以及Python2中字符串编码问题,没来得及上车的小伙伴们可以戳这篇文章:浅谈unicode编码...

1082
来自专栏康怀帅的专栏

PHP 执行 Shell 命令

主要有 exec() shell_exec() system()。 exec() string exec ( string $command [, array ...

4317

扫码关注云+社区

领取腾讯云代金券