java多线程(二)对象及变量的并发访问

1、synchronized同步方法:

            "非线程安全"其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",
        也就取到的数据其实是被更改过的。
            "线程安全"就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
        1> 方法内的变量为线程安全
            "非线程安全"问题存在于实例变量中,如果是方法内部的私有变量,则不存在"非线程安全"问题。        
            方法中的变量不存在非线程安全问题,永远都是线程安全的。方法内部的变量是私有的特性造成的。
        2> 实例变量非线程安全
            如果多个线程共同访问1个对象中的实例变量,则由可能出现"非线程安全",变量值会出现覆盖的情况。
            //这时只需要在方法前加上synchronized关键字即可。
            //两个线程访问同一个对象中的同步方法时一定是线程安全的     
        3> 多个对象多个锁
            //关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁。
            同步的单词为synchronized,异步的单词为asynchronized。
        4> synchronized方法与锁对象
            调用关键字synchronized声明的方法一定是排队进行的,只有共享资源的读写访问才需要同步。
            A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中非synchronized类型的方法。
            A线程先持有object对象的Lock锁,B线程如果再这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。
        5> 脏读
            多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字来实现同步。
            虽然在赋值时进行了同步,但在取值时可能发生意想不到的意外,这种情况就是脏读(dirty Read)。
        6> synchronized锁重入
            关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时
            是可以再次得到该对象的锁的。这也证明在synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。   
            //自己可以再次获取自己的内部锁。
        7> 出现异常,锁自动释放
            当个一个线程执行的代码出现异常时,其所持有的锁会自动释放
        8> 同步不具有继承性
            同步不能继承,所以还的在子类的方法中添加synchronized关键字。

2、synchronized同步语句块

        用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用方法执行一个长时间的任务,那么B线程必须要等待很长的时间,
        在这种情况下可以使用synchronized同步语句块来解决。
        1> synchronized方法的弊端:
            等待耗时比较长。
        2> synchronized同步代码块的使用
            当一个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程
            执行完这个代码块以后才能执行该代码块。
        3> 用同步代码块解决同步方法的弊端
            当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。
        4> 一半异步,一半同步
        5> synchronized代码块间的同步性:
            在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问Object的一个synchronized(this)同步代码块时,
            其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的"对象监视器"是一个。      
        6> 验证同步synchronized(this)代码块时锁定当前对象的
            和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
        7> 将任意对象作为对象监视器:
            多个线程调用同一个对象的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按照顺序执行,也就是同步的,阻塞的的。
            a> synchronized同步方法:
                *对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
                *同一时间只有一个线程可以执行synchronized同步方法中的代码。
            b> synchronized(this)同步代码块:
                *对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
                *同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
        8> 细化验证过3个结论:
            synchronizd(非this对相关x)格式的写法是将x对象本身作为"对象监视器"。
            a> 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
            b> 当其他线程执行x对象中的synchronized同步方法时呈同步效果。
            c> 当其他线程执行x对象方法里的synchronized(this)代码块时也呈现同步效果。
            
        9> 静态同步synchronized方法synchronized(class)代码块
            关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当的*.java文件的对应的Class类进行持锁。 
        10> 数据类型String的常量池特性:
            大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()
            实例化一个Object对象,但它并不放入缓存中。  
        11> 同步synchronized方法无限等待与解决:   
        12> 多线程的死锁:
            不同的线程都在等待根本不可能被释放锁,从而导致所有的任务都无法继续完成。
        13> 内置类与静态内置类  
        14> 内置类与同步:实验1
        15> 内置类与同步:实验2
        16> 锁对象的改变
            在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,
            则这些线程之间就是同步的,如果分别获得锁对象,这些线程就是异步的。           

3、volatile关键字:

        volatile的主要作用是使变量在多个线程间可见。
        1> 关键字volatile与死循环
            运用多线程技术可以解决。
        2> 解决同步死循环
            实现了Runnable后的车需运行在-server服务器模式中的64bit的JVM上时,会出现死循环。
            解决的办法是使用volatile关键字。
            //关键字volatile的作用是强制从公共堆栈中取出变量的值,而不是从线程私有数据栈中取出变量的值。
        3> 解决异步死循环
            是什么原因造成将JVM设置为-server时就出现死循环呢?
                在启动RunThread.java线程时,如变量private boolean isRunning=true;存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程运行的效率,
            线程一直在私有堆栈中取得isRunning的值是true。而代码thread.setRunning(false);虽然被执行,更新的却是公共堆栈中的isRunning变量值false,
            所以一直就是死循环的状态。
                其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用volatile关键字了,
            它主要作用就是当线程访问isRunning这个变量时,强制性从公共堆栈中进行取值。
        
            使用volatile关键字增加了实例变量在多个线程之间的可见性。但volatile最致命的缺点是不支持原子性。
            synchronized和volatile的比较:
                a> 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定要比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,
                    以及代码块。随着jdk新版本的发布,synchronized关键字在执行效率上得到很多提升,在开发中主要使用synchronized。
                b> 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
                c> volatile能保证数据的可见性,但不能保证数据的原子性。而synchronized可以保证原子性,也可以间接保证
                    可见性,因为它会将私有内存和公共内存中的数据做同步。
                d> 关键字volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
        
        4> volatile非原子的特性
            关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。
            
            关键字volatile主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。
        关键字volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。
            但在这里需要注意的是:如果修改实例变量中的数据,比如i++,也就是i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全的。可以用synchronized解决。
            volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。
            volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一实例变量还是需要加锁同步。
            
        5> 使用原子类进行i++操作
            除了在i++操作时使用synchronized关键字实现同步外,还可以使用AtomicInteger原子类进行实现。
            原子操作时不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(automic)类型就是一个原子操作可用的类型,
        它可以在没有锁的情况下做到线程安全)(thread-safe)
                
        6> 原子类也并不完全安全
            变量调用的addAndGet()方法是原子的,但方法和方法之间的调用却不是原子的。解决这样的问题必须要用同步。
        
        7> synchronized代码块有volatile同步的功能
            关键字synchronized可以使多个线程访问同一个资源具有同步性,而且他还具有将工作内存中国的私有
            变量与公共内存中的变量同步的功能。
            
            关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某一个代码块。它包含两个特征:互斥性和可见性。
            同步synchronized不仅可以解决一个线程看到的对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,
            都可以看到又一个锁保护之前所有的修改结果。

猜你喜欢

转载自blog.csdn.net/MyronCham/article/details/82460835
今日推荐