java并发

作者: 浩林Leon | 来源:发表于2017-12-30 17:17 被阅读68次

    并发核心理论

    • 1.共享性: 数据共享是线程安全问题的主要问题之一

    • 2.互斥性: 资源互斥指同一时间内值允许一个访问者访问.通常情况修改数据是互斥性,读数据不要求.因此我们有共享锁和互斥锁.也叫读锁和写锁.

    • 3.原子性:指操作是一个独立,不可分割的整体.操作不会中断,数据不会执行一半的时候被其他操作修改.例如一条指令就是最基本的原子.但是 i++,就分了好几次操作:

      读取i=1值 --> 2.i=1+1 --->3.存储i=2

    • 4.可见性:


      image.png

      每个线程都有自己的工作内存,对于共享变量,先拿到变量的副本,对副本进行操作,在某一个时间在把副本的值同步到共享变量.这样导致,有可能线程1修改了共享变量的值,某一时间线程2可能拿不到最新的值.

    • 5.有序性:为了提高性能,编译器和处理器可能会对指令进行重排.

    synchronized及其实现原理

    synchronized 作用:

    • 1.确保线程互斥的访问同步代码
    • 2.保证共享变量能及时可见
    • 3.解决重排序问题

    synchronize 的一般用法,对象锁,类锁.(synchronized Object,synchronized 方法)
    对class文件反编译后的汇编语言如下:


    image.png
    image.png

    说明synchronized 是在对象前面加了 monitor .
    Monitorenter : 对象都享有一个monitor
    1.线程一旦进入monitor,monitor值1,线程为monitor拥有者.
    2.其他线程到了这里,会进行阻塞,直到monitor的值=0.
    Monitorexit:monitor 变回0.说明其他线程可以拥有.

    对方法加synchronized

    image.png
    image.png
    Synchronized底层优化(偏向锁、轻量级锁)
    优点 缺点 适用场景
    偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 适用于只有一个线程访问同步块场景。
    轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度。 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 追求响应时间。同步块执行速度非常快。
    重量级锁 线程竞争不使用自旋,不会消耗CPU。 线程阻塞,响应时间缓慢。 追求吞吐量。同步块执行时间较长。

    锁的状态: 无锁状态,偏向锁 ,轻量级锁,重量级锁
    随着锁的竞争,锁的状态可以升级,但是是单向的.从低到高,不会出现锁的降级.
    偏向锁<轻量级锁<重量级锁


    image.png

    轻量级锁:本意使用操作系统互斥量解决传统的重量级锁的性能问题.
    使用场景:线程交替执行同步块.如果同一时刻访问同一锁的情况,轻量级锁会升级重量级锁.
    偏向锁:用于一个线程执行同步块是提高性能.一旦出现多线程竞争,升级为轻量级锁.
    总结 :JDk中采用轻量级锁和偏向锁等对Synchronized的优化,但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。下面是这几种锁的对比:

    优点 缺点 适用场景
    偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 适用于只有一个线程访问同步块场景。
    轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度。 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 追求响应时间。同步块执行速度非常快。
    重量级锁 线程竞争不使用自旋,不会消耗CPU。 线程阻塞,响应时间缓慢。 追求吞吐量。同步块执行时间较长。

    Java并发编程:线程间的协作(wait/notify/sleep/yield/join)

    线程有5中状态:
    新建(New)-->准备状态(Runnable)-->运行状态(Running)-->阻塞(Blocking)-->死亡(Dead)

    new :创建 new Thread();
    Runnable:调用start(),进入就绪状态,等待cpu分配资源,有系统运行时线程来调度
    Running:开始执行Run方法
    Blocking: 阻塞
    Dead
    Wait/notify/notifyAll()
    wait() 当前线程挂起,直到有其他线程调用notify(), notifyAll().
    wait(long timeout) 当前线程挂起,直到有其他线程调用notify(), notifyAll();或者等到timeout
    wait(long timeout,int nanos)
    notify() 唤醒指定线程 , notifyAll() 唤醒所有的线程
    注意:wait 必须在synchronized 块之内.
    Thread.sleep(long sleeptime)
    当前线程暂停指定的时间(毫秒),而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁.而wait方法则需要释放锁,(意味其他线程可以拿到锁,进行操作)

    package jni.test.leon.javatest;
    /**
     * Created by leon on 17-12-11.
     */
    public class SleepTest {
        public synchronized void sleepMethod() {
            System.out.println("Sleep start-----");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Sleep end-----");
        }
        public  void waitMethod() {
            System.out.println("Wait start-----");
            synchronized (this) {
                try {
                    wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Wait end-----");
        }
        public static void main(String[] args) {
            final SleepTest test1 = new SleepTest();
            for (int i = 0; i < 3; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test1.sleepMethod();
                    }
                }).start();
            }
            try {
                Thread.sleep(10000);//暂停十秒,等上面程序执行完成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----分割线-----");
            final SleepTest test2 = new SleepTest();
            for (int i = 0; i < 3; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test2.waitMethod();
                    }
                }).start();
            }
        }
    }
    //output-----
    Connected to the target VM, address: '127.0.0.1:38691', transport: 'socket'
    Sleep start-----
    Sleep end-----
    Sleep start-----
    Sleep end-----
    Sleep start-----
    Sleep end-----
    -----分割线-----
    Wait start-----
    Wait start-----
    Wait start-----
    Wait end-----
    Disconnected from the target VM, address: '127.0.0.1:38691', transport: 'socket'
    Wait end-----
    Wait end-----
    
    Process finished with exit code 0
    

    Thread.yeild()
    当前线程暂停,让其他线程有机会执行.(用的场景比较少,主要是调试)

    package jni.test.leon.javatest;
    /**
     * Created by leon on 17-12-11.
     */
    public class YieldTest implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                Thread.yield();
            }
        }
        public static void main(String[] args) {
            YieldTest runn = new YieldTest();
            Thread t1 = new Thread(runn, "FirstThread");
            Thread t2 = new Thread(runn, "SecondThread");
            t1.start();
            t2.start();
        }
    }
    //--output
    FirstThread: 0
    FirstThread: 1
    SecondThread: 0
    FirstThread: 2
    SecondThread: 1
    FirstThread: 3
    SecondThread: 2
    FirstThread: 4
    SecondThread: 3
    SecondThread: 4
    

    Thread.join()/Thread.join(long waittime)/Thread.join(long waittime,int nano)
    作用:父线程等待子线程执行完之后在执行,可以达到异步子线程,最后的同步.

    package jni.test.leon.javatest;
    /**
     * Created by leon on 17-12-11.
     */
    public class JoinTest implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName() + " start-----");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " end------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            Thread test = null;
            for (int i = 0; i < 5; i++) {
                test = new Thread(new JoinTest());
                test.start();
            }
            //这里是阻塞
            try {
                test.join(); //调用join方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Finished~~~");
        }
    }
    //output------
    Thread-0 start-----
    Thread-1 start-----
    Thread-2 start-----
    Thread-3 start-----
    Thread-4 start-----
    Thread-0 end------
    Thread-1 end------
    Thread-2 end------
    Thread-3 end------
    Thread-4 end------
    Finished~~~
    

    总结:
    因为wait() /notify () 是对象monitor,所以当wait的时候就monitorEnter,必须等待有monitorExit 才能释放对象的拥有权.所以,一旦wait 了,其他的线程是无法获得拥有权,也就不能进入.

    而sleep, yield这些是Thread级别的方法,只是让出cpu执行权join,调用的是wait 方法,所以是会进行对象级别的monitor.

    volatile的使用及其原理

    我们知道 可见性,有序性,原子性 的问题.synchronized 就是用来解决这些问题的,但是synchronized是比较重量级,volatile是一个轻量级的解决有序性,可见性,原子性的方案.
    原理:
    1.对有序性:
    在解释这个问题前,我们先来了解一下Java中的happen-before规则,JSR 133中对Happen-before的定义如下:

    Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.

    通俗一点说就是如果a happen-before b,则a所做的任何操作对b是可见的。(这一点大家务必记住,因为happen-before这个词容易被误解为是时间的前后)。我们再来看看JSR 133中定义了哪些happen-before规则:

    • Each action in a thread happens before every subsequent action in that thread.
    • An unlock on a monitor happens before every subsequent lock on that monitor.
    • A write to a volatile field happens before every subsequent read of that volatile.
    • A call to start() on a thread happens before any actions in the started thread.
    • All actions in a thread happen before any other thread successfully returns from a join() on that thread.
    • If an action a happens before an action b, and b happens before an action c, then a happens before c.

    翻译过来为:

    • 同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。
    • 监视器上的解锁操作 happen-before 其后续的加锁操作.(Synchronized 规则)
    • 对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则)
    • 线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则)
    • 线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。
    • 如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。
      这里我们主要看下第三条:volatile变量的保证有序性的规则<<Java并发编程:核心理论>>

    2.可见性
    使用 Volatile 关键字
    1.修改时会强制修改主内存的数据
    2.修改变量后导致其他线程工作中内存的值失效,所以需要重新读取主内存的数据
    volatile 的使用场景比较有限:
    1.该变量写操作不依赖当前值
    2.该变量没有包含在具有其他变量的不变式中(这个变量只有原子性操作,简单说来就是 进行赋值)

    常用:
    1.状态标记量

    A:
    volatile boolean flag = false;
     
    while(!flag){
        doSomething();
    }
     
    public void setFlag() {
        flag = true;
    }
    
    B:
    volatile boolean inited = false;
    //线程1:
    context = loadContext();  
    inited = true;            
     
    //线程2:
    while(!inited ){
    sleep()
    }
    doSomethingwithconfig(context);
    

    2.double check

    class Singleton{
        private volatile static Singleton instance = null;
     
        private Singleton() {
     
        }
     
        public static Singleton getInstance() {
            if(instance==null) {
                synchronized (Singleton.class) {
                    if(instance==null)
                        instance = new Singleton();
                }
            }
            return instance;
        }
    }
    

    相关文章

      网友评论

        本文标题:java并发

        本文链接:https://www.haomeiwen.com/subject/adfngxtx.html