Java并发编程

  在调用wait方法时,线程必须要持有被调用对象的锁,当调用wait方法后,线程就会释放掉对象的锁(monitor);在调用Thread类的sleep方法时,线程是不会释放掉对象的锁的(阻塞)。

package com.msz;

public class msz_001_test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            object.wait();
        }
    }
}

关于wait与notify和notifyAll方法的总结:

  ①当调用wait时,首先需要确保调用了wait方法的线程已经持有了对象的锁;
  ②当调用wait后,该线程就会释放掉这个对象的锁,然后进入到等待状态(wait set);
  ③当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或者notifyAll方法来使自己被唤醒;
  ④一旦这个线程被其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争),只有当该线程获取到了这个对象的锁后,线程才会继续往下执行;
  ⑤调用wait方法的代码片段需要放在一个synchronized块或是synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到了对象的锁;
  ⑥当调用对象的notify方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁;
  ⑦当调用对象的notifyAll方法时,它会唤醒该对象等待集合(wait set)中的所有线程,这些线程被唤醒后,又开始竞争对象的锁;
  ⑧在某一时刻,只有唯一 一个线程可以拥有对象的锁。


public class Test {
	public synchronized void method_1() {
	}
	
	public synchronized void method_2() {
	}
}

Test test = new Test();
  当某一个线程执行这个Test对象的method_1方法时但是还没有执行完,此时其他线程是不能再执行method_2方法的,因为对于同一个对象来说它的所有synchronized方法的锁的对象或者monitor是一个东西;当一个线程执行了method_1方法获取到对象锁时,其他线程是无论如何也不能执行method_2方法的。


Test test = new Test();
Test test_1 = new Test();
  第一个对象执行method_1方法,第二个对象执行method_2方法;是能够行得通的,因为是两个对象,它们是两个不同的锁或者monitor,第一个线程获取第一个对象的锁,第二个线程获取第二个对象的锁,相互之间没有影响的。


public class Test {
	public synchronized void method_1() {
	}
	public static synchronized void method_2() {
	}
}

Test test = new Test();
  一个线程执行上面这个对象的method_1和method_2方法是行得通的;因为method_1方法synchronized锁的是Test对象,而method_2方法synchronized锁的是Test对象所对应的class对象。一个是当前对象,一个是当前对象对应的class对象,所以锁的对象或者monitor不是一个东西;故而一个线程可以同时执行这两个方法;两个不同的对象拥有自己不同的锁或者monitor。


题目:编写一个多线程程序,实现目标:

  ①存在一个对象,该对象有一个int类型的成员成员变量counter,该成员变量的初始值为0;
  ②创建两个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减1;
  ③输出该对象成员变量counter每次变化后的值;
  ④最终输出的结果应为:1010101010…

package com.msz;

public class msz_002_MyObject {
    private int counter;
    public synchronized void increase() {
        if (counter  != 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter++;
        System.out.println(counter);
        notify();
    }
    
    public synchronized void decrease() {
        if (counter == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter--;
        System.out.println(counter);
        notify();
    }
}
package com.msz;

public class msz_003_IncreaseThread extends Thread {
    private msz_002_MyObject myObject;
    public msz_003_IncreaseThread(msz_002_MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        for (int i=0; i<30; ++i) {
            try {
                Thread.sleep((long)Math.random() * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myObject.increase();
        }
    }
}
package com.msz;

public class msz_004_DecreaseThread extends Thread{
    private msz_002_MyObject myObject;
    public msz_004_DecreaseThread(msz_002_MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        for (int i=0; i<30; ++i) {
            try {
                Thread.sleep((long)Math.random() * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myObject.decrease();
        }
    }
}
package com.msz;
import sun.misc.InnocuousThread;

public class msz_005_Client {
    public static void main(String[] args) {
        msz_002_MyObject myObject = new msz_002_MyObject();
        Thread increaseThread = new msz_003_IncreaseThread(myObject);
        Thread decreaseThread = new msz_004_DecreaseThread(myObject);
        increaseThread.start();
        decreaseThread.start();
    }
}

package com.msz;
import sun.misc.InnocuousThread;

public class msz_005_Client {
    public static void main(String[] args) {
        msz_002_MyObject myObject = new msz_002_MyObject();
        Thread increaseThread = new msz_003_IncreaseThread(myObject);
        Thread increaseThread2 = new msz_003_IncreaseThread(myObject);
        Thread decreaseThread = new msz_004_DecreaseThread(myObject);
        Thread decreaseThread2 = new msz_004_DecreaseThread(myObject);
        increaseThread.start();
        increaseThread2.start();
        decreaseThread.start();
        decreaseThread2.start();
    }
}

  当启动四个线程时程序出现两个问题:
  ①输出结果不正确;
  ②程序不退出(挂起状态)
  
  分析原因:当程序启动两个线程情况下,假如一个线程执行一个线程等待,当这个执行线程执行到notify时,唤醒的必然是另外一个等待的线程,以此相互【等待唤醒】【唤醒等待】,故能够完成题目中的需求。当线程启动四个或者更多的时候线程被唤醒时需要再次先判断一下counter然后再执行逻辑,所以使用while,并且在执行过程中可能多个线程处于等待状态,所以唤醒用notifyAll。原程序执行四个线程出现的问题耐心逐步分析便可找到事发原由。下面是正确的程序代码:

package com.msz;

public class msz_002_MyObject {
    private int counter;
    public synchronized void increase() {
        while (counter  != 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter++;
        System.out.println(counter);
        notifyAll();
    }
    
    public synchronized void decrease() {
        while (counter == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter--;
        System.out.println(counter);
        notifyAll();
    }
}

synchronized关键字:

  如果一个对象里面有若干个synchronized方法,那么在某一时刻只能有唯一的一个线程进入到这里面的其中一个synchronized方法(那么在某一时刻这些synchronized方法当中只会有唯一的一个synchronized方法被某一个线程访问,而其他的线程即便想要访问另外的synchronized方法,也要去等待);因为当一个对象里面有若干个synchronized方法的时候,线程要执行其中一个方法需要尝试获取“锁”,这个锁是当前对象的锁,而当前对象只有唯一的一把锁。
  当一个线程去访问某一个对象的
synchronized static这样一个方法的时候它需要尝试获取的锁并不是当前对象的锁,而是当前对象所对应的class对象的锁。本质上来说,一个静态的方法并不属于当前对象,它是属于当前对象所对应的class对象。方法执行完或者抛出异常都会释放锁。

  当我们使用synchronized关键字来修饰代码块时,字节码层面上是通过monitorentermonitorexit指令来实现的锁的获取与释放动作。
  当线程进入到monitorenter指令后,线程将会持有Monitor对象,退出monitorenter指令后,线程将会释放Monitor对象。

  对于synchronized关键字修饰方法来说,并没有出现monitorentor与moitorexit指令,而是出现了一个ACC_SYNCHRONIZED标志。JVM使用了ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥有ACC_SYNCHRONIZED标志,如果有,那么执行线程将会先持有方法所在对象的Monitor对象,然后再去执行方法体;在该方法执行期间,其他任何线程均无法再获取到这个Monitor对象,当线程执行完该方法后,它会释放掉这个Monitor对象。

  JVM中的同步是基于进入与退出监视器对象(管程)(Monitor)来实现的。每个对象实例都会有一个Monitor对象,Monitor对象会和Java对象一同创建并销毁。Monitor对象是由C++来实现的。

  当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合中,处于阻塞状态的线程都会被放到列表当中。接下来,当线程获取到对象的Monitor时,Monitor是依赖于底层操作系统的mutex lock来实现互斥的,线程获取mutex成功,则会持有该mutex,这时其他线程就无法再获取到该mutex。
  如果线程调用了wait方法,那么该线程就会释放掉所持有的mutex,并且该线程会进入到WaitSet集合(等待集合)中,等待下一次被其他线程调用notify / notifyAll唤醒。如果当前线程顺利执行完毕方法,那么它也会释放掉所持有的mutex。

  总结:
  同步锁在这种实现方式当中,因为Monitor是依赖于底层的操作系统实现,这样就存在用户态与内核态之间的切换,所以会增加性能开销。通过对象互斥锁的概念来保证共享数据操作完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。
  那些处于EntryList与WaitSet中的线程均处于阻塞状态,阻塞操作是由操作系统来完成的,在linux下是通过pthread_mutex_lock函数实现的。线程被阻塞后便会进入到内核调度状态,这会导致系统在用户态与内核态之间来回切换,严重影响锁的性能。
  解决上述问题的办法便是自旋(Spin),其原理是:当发生对Monitor的争用时,若Owner能够在很短的时间内释放掉锁,则那些正在争用的线程就可以稍微等待一下(即所谓的自旋),在Owner线程释放锁之后,争用线程可能会立刻获取到锁,从而避免了系统阻塞。不过,当Owner运行的时间超过了临界值后,争用线程自旋一段时间后依然无法获取到锁,这时争用线程则会停止自旋而进入到阻塞状态。所以总体的思想是:先自旋,不成功再进行阻塞,尽量降低阻塞的可能性,这对那些执行时间很短的代码块来说有极大的性能提升。显然,自旋在多处理器(多核心)上才有意义。


互斥锁与Monitor对象特性:

互斥锁的属性:
  ①PTHREAD_MUTEX_TIMED_NP:这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将会形成一个等待队列,并且在解锁后按照优先级获取到锁。这种策略可以确保资源分配的公平性。
  ②PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许一个线程对同一个锁成功获取多次,并通过unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新进行竞争。
  ③PTHREAD_MUTEX_ERRORCHECK_NP:检错锁,如果一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同,这样就保证了当不允许多次加锁时不会出现最简单情况下的死锁。
  ④PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,动作最简单的锁类型,仅仅等待解锁后重新竞争。


锁升级与偏向锁深入解析:

  在JDK1.5之前,我们若想实现线程同步,只能通过synchronized关键字这一种方式来达成;底层,Java也是通过synchronized关键字来做到数据的原子性维护的;synchronized关键字是JVM实现的一种内置锁,从底层角度来说,这种锁的获取与释放都是由JVM帮助我们隐式实现的。
  从JDK1.5开始,并发包引入了Lock锁,Lock同步锁是基于Java来实现的,因此锁的获取与释放都是通过Java代码来实现与控制的;然而,synchronized是基于底层操作系统的Mutex Lock来实现的,每次对锁的获取与释放动作都会带来用户态与内核态之间的切换,这种切换会极大地增加系统的负担;在并发量较高时,也就是说锁的竞争比较激烈时,synchronized锁在性能上的表现就非常差。
  从JDK1.6开始,synchronized锁的实现发生了很大的变化;JVM引入了相应的优化手段来提升synchronized锁的性能,这种提升涉及到偏向锁、轻量级锁、重量级锁等,从而减少锁的竞争所带来的用户态与内核态之间的切换;这种锁的优化实际上是通过Java对象头中的一些标志位来去实现的;对于锁的访问与改变实际上都与Java对象头息息相关。
  从JDK1.6开始,对象实例在堆当中会被划分为三个组成部分:对象头、实例数据与对齐填充。
  对象头主要也是由三块内容来构成:①Mark Word;②指向类的指针;③数组长度。其中Mark Word(它记录了对象、锁及垃圾回收相关的信息,在64位的JVM中,其长度也是64bit)的位信息包括了如下组成部分:①无锁标记;②偏向锁标记;③轻量级锁标记;④重量级锁标记;⑤GC标记。
  对于synchronized锁来说,锁的升级主要都是通过Mark Word中的锁标志位与是否是偏向锁标志位来达成的;synchronized关键字所对应的锁都是先从偏向锁开始,随着锁竞争的不断升级,逐步演化至轻量级锁,最后则变成了重量级锁。
  对于锁的演化来说,它会经历如下阶段:无锁–>偏向锁–>轻量级锁–>重量级锁
  偏向锁:针对一个线程来说的,它的主要作用就是优化同一个线程多次获取一个锁的情况;如果一个synchronized方法被一个线程访问,那么这个方法所在的对象就会在其Mark Word中将偏向锁进行标记,同时还会有一个字段来存储该线程的ID;当这个线程再次访问同一个synchronized方法时,它会检查这个对象的Mark Word的偏向锁标记以及是否指向了其他线程ID,如果是的话,那么该线程就无需再去进入管程(Monitor)了,而是直接进入到该方法体中。如果是另外一个线程访问这个synchronized方法,那么实际情况会如何呢?偏向锁会被取消掉。
  轻量级锁:若第一个线程已经获取到了当前对象的锁,这时第二个线程又开始尝试争抢该对象的锁,由于该对象的锁已经被第一个线程获取到,因此它是偏向锁,而第二个线程在争抢时,会发现该对象头中的Mark Word已经是偏向锁,但里面存储的线程ID并不是自己(是第一个线程),那么它会进行CAS(Compare and Swap),从而获取到锁,这里面存在两种情况:①获取锁成功:那么它会直接将Mark Word中的线程ID由第一个线程变成自己(偏向锁标志位保持不变),这样该对象依然会保持偏向锁的状态;②获取锁失败:则表示这时可能会有多个线程同时在尝试争抢该对象的锁,那么这时偏向锁就会进行升级,升级为轻量级锁。
  自旋锁:若自旋失败(依然无法获取到锁),那么锁就会转化为重量级锁,在这种情况下,无法获取到锁的线程都会进入到Monitor(即内核态)。自旋最大的一个特点就是避免了线程从用户态进入到内核态。
  重量级锁:线程最终从用户态进入到了内核态。


锁粗化与锁消除:

  编译器对于锁的优化措施:JIT编译器(Just In Time编译器)可以在动态编译同步代码时,使用一种叫做逃逸分析的技术,来通过该项技术判别程序中所使用的锁对象是否只被一个程序所使用,而没有散布到其他线程当中;如果情况就是这样的话,那么JIT编译器在编译这个同步代码时就不会生成synchronized关键字所标识的锁的申请与释放机器码,从而消除了锁的使用流程。(锁消除技术)例如下面msz_3_004_MyTest4类代码中object做为Object对象的引用是属于局部变量(每个线程独有一份),此处便有了锁的消除技术。
  锁粗化:JIT编译器在执行动态编译时,若发现前后相邻的synchronized块使用的是同一个锁对象,那么它就会把这几个synchronized块给合并为一个较大的同步块,这样做的好处在于线程在执行这些代码时,就无需频繁申请与释放锁了,从而达到申请与释放锁一次,就可以执行完全部的同步代码块,从而提升了性能。(msz_3_005_MyTest5类代码)

package com.msz_3;

public class msz_3_004_MyTest4 {
    public void method() {
        Object object = new Object();
        synchronized (object) {
            System.out.println("hello world");
        }
    }
}
package com.msz_3;

public class msz_3_005_MyTest5 {
	private Object object = new Object();
    public void method() {
        synchronized (object) {
            System.out.println("hello world");
        }
        synchronized (object) {
            System.out.println("welcome");
        }
        synchronized (object) {
            System.out.println("person");
        }
    }
}

死锁

  死锁:线程1等待线程2互斥持有的资源,而线程2也在等待线程1互斥持有的资源,两个线程都无法继续执行。(msz_3_006_MyTest6类代码)
  活锁:线程持续重试一个总是失败的操作,导致无法继续执行。
  饿死:线程一直被调度器延迟访问其赖以执行的资源,也许是调度器先于低优先级的线程而执行高优先级的线程,同时总是会有一个高优先级的线程可以执行,饿死也叫做无限延迟。

package com.msz_3;

public class msz_3_006_MyTest6 {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    public void myMethod1() {
        synchronized (lock1) {
            synchronized (lock2) {
                System.out.println("myMethod1 invoked");
            }
        }
    }

    public void myMethod2() {
        synchronized (lock2) {
            synchronized (lock1) {
                System.out.println("myMethod2 invoked");
            }
        }
    }

    public static void main(String[] args) {
        msz_3_006_MyTest6 myTest6 = new msz_3_006_MyTest6();
        Runnable runnable1 = () -> {
            while (true) {
                myTest6.myMethod1();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                }
            }
        };
        Thread thread1 = new Thread(runnable1,"myThread1");
        Runnable runnable2 = () -> {
            while (true) {
                myTest6.myMethod2();
                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {

                }
            }
        };
        Thread thread2 = new Thread(runnable2,"myThread2");
        thread1.start();
        thread2.start();
    }
}

Lock锁机制深入详解

  

package com.msz_4;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class msz_4_001_MyTest1 {
    private Lock lock = new ReentrantLock();//可重入锁
    public void myMethod1() {
        try {
            lock.lock();
            System.out.println("myMethod1 invoked");
        } finally {
            lock.unlock();
        }
    }

    public void myMethod2() {
        try {
            lock.lock();
            System.out.println("myMethod2 invoked");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        msz_4_001_MyTest1 myTest1 = new msz_4_001_MyTest1();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                myTest1.myMethod1();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                myTest1.myMethod2();
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

关于Lock与synchronized关键字在锁的处理上的重要差别

  ①锁的获取方式:前者是通过程序代码的方式由开发者手工获取,后者是通过JVM来获取(无需开发者干预)
  ②具体实现方式:前者是通过Java代码的方式实现,后者是通过JVM底层来实现(无需开发者关注)
  ③前者务必通过unlock()方法在finally块中手工释放,后者是通过JVM来释放(无需开发者关注)
  ④锁的具体类型:前者提供了多种,如公平锁、非公平锁,后者与前者均提供了可重入锁


  传统上,我们可以通过synchronized关键字 + wait + notify + notifyAll来实现多线程之间的协调与通信,整个过程都是由JVM来帮助我们实现的;开发者无需(也是无法)了解底层的实现细节。
  从JDK 5开始,并发包提供了Lock,Condition(await与signal / signalAll)来实现多个线程之间的协调与通信,整个过程都是由开发者来控制的,而且相比于传统方式更加灵活,功能更加强大。
  Thread.sleep与await(或是Object的wait方法)的本质区别:sleep方法本质上不会释放锁,而await会释放锁,并且在signal后,还需要重新获取锁才能继续执行(该行为与Object的wait方法完全一致)

数组存数据取数据:

package com.msz_4;
import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

public class msz_4_002_MyTest2 {
    public static void main(String[] args) {
        BoundedContainer boundedContainer = new BoundedContainer();
        IntStream.range(0,10).forEach((i)->{
            new Thread(()->{
                try {
                    boundedContainer.put("hello");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        });

        IntStream.range(0,8).forEach((i)->{
            new Thread(()->{
                try {
                    boundedContainer.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        });
    }
}

class BoundedContainer {
    private String[] elements = new String[10];
    private Lock lock = new ReentrantLock();
    private Condition notEmptyCondition = lock.newCondition();
    private Condition notFullCondition = lock.newCondition();
    private int elementCount;//elements数组已有的元素数量
    private int putIndex;
    private int takeIndex;
    public void put(String element) throws InterruptedException {
        this.lock.lock();
        try {
            while (this.elementCount == this.elements.length) {
                notFullCondition.await();
            }
            elements[putIndex] = element;
            if (++putIndex == this.elements.length) {
                putIndex = 0;
            }
            ++elementCount;
            System.out.println("put method:" + Arrays.toString(elements));
            notEmptyCondition.signal();
        } finally {
            this.lock.unlock();
        }
    }

    public String take() throws InterruptedException {
        this.lock.lock();
        try {
            while (0 == this.elementCount) {
                notEmptyCondition.await();
            }
            String element = elements[takeIndex];
            elements[takeIndex] = null;
            if (++takeIndex == this.elements.length) {
                takeIndex = 0;
            }
            --elementCount;
            System.out.println("take method:" + Arrays.toString(elements));
            notFullCondition.signal();
            return element;
        } finally {
            this.lock.unlock();
        }
    }
}

volatile关键字

  private volatile int count;
  volatile关键字主要的三方面作用:①实现long / double类型变量的原子操作;②防止指令重排;③实现变量的可见性。

  volatile double a = 1.0;

  当使用volatile修饰变量时,应用就不会从寄存器中获取该变量的值,而是从内存(高速缓存)中获取。

  volatile与锁类似的地方有两点:①确保变量的内存可见性;②防止指令重排序。

  volatile可以确保对变量写操作的原子性,但不具备排他性;另外的重要一点在于:使用锁可能会导致线程的上下文切换(内核态与用户态之间的切换),但使用volatile并不会出现这种情况
  volatile int a = b + 2;//(x)
  volatile int a = a++;//(x)
  volatile int count = 1;//(√)
  volatile boolean flag = false;//(√)
  如果要实现volatile写操作的原子性,那么在等号右侧的赋值变量中就不能出现被多线程所共享的变量,哪怕这个变量也是一个volatile修饰也不可以。
  volatile Date date = new Date();//整个操作依然不是原子操作

  防止指令重排序与实现变量的可见性都是通过一种手段来实现的:内存屏障(memory barrier)
  
  int a = 1;
  String s = “hello”;
  内存屏障(Release Barrier,释放屏障)
  volatile boolean v = false;//写入操作
  内存屏障(Store Barrier,存储屏障)
  
  Release Barrier:防止下面的volatile与上面的所有操作的指令重排序。
  Store Barrier:重要作用是刷新处理器缓存,结果是可以确保该存储屏障之前一切的操作所生成的结果对于其他处理器来说都可见。
  
  内存屏障(Load Barrier,加载屏障)
  boolean v1 = v;
  内存屏障(Acquire Barrier,获取屏障)
  int a = 1;
  String s = “hello”;
  
  Load Barrier:可以刷新处理器缓存,同步其他处理器对该volatile变量的修改结果。
  Acquire Barrier:可以防止上面的volatile读取操作与下面的所有操作语句的指令重排序。
  
  对于volatile关键字变量的读写操作,本质上都是通过内存屏障来执行的。
  
  内存屏障兼具了两方面的能力:①防止指令重排序;②实现变量内存的可见性。
  
  ①对于读取操作来说,volatile可以确保该操作与其后续的所有读写操作都不会进行指令重排序。
  ②对于修改操作来说,volatile可以确保该操作与其上面的所有读写操作都不会进行指令重排序。
  
  volatile与锁的一些比较
锁同样具备变量内存可见性与防止指令重排序的功能。

monitorenter
内存屏障(Acquire Barrier,获取屏障)

正常的执行逻辑…

内存屏障(Release Barrier,释放屏障)
monitorexit


Java内存模型(Java Memory Model,JMM)以及happen-before

①变量的原子性问题
②变量的可见性问题
③变量修改的时序性问题

happen-before重要规则:
①顺序执行规则(限定在单个线程上的):该线程的每个动作都happen-before它的后面的动作。
②隐式锁(monitor)规则:unlock happen-before lock,之前的线程对于同步代码块的所有执行结果对于后续获取锁的线程来说都是可见的。
③volatile读写规则:对于一个volatile变量的写操作一定会happen-before后续对该变量的读操作。
④多线程的启动规则:Thread对象的start方法happen-before该线程run方法中的任何一个动作,包括在其中启动的任何子线程。
⑤多线程的终止规则:一个线程启动了一个子线程,并且调用了子线程的join方法等待其结束,那么当子线程结束后,父线程的接下来的所有操作都可以看到子线程run方法中的执行结果。
⑥线程的中断规则:可以调用interrupt方法来中断线程,这个调用happen-before对该线程中断的检查(isInterrupted)


关于CyclicBarrier的底层执行流程

①初始化CyclicBarrier中的各种成员变量,包括parties、count以及Runnable(可选);
②当调用await方法时,底层会先检查计数器是否已经归零,如果是的话,那么就首先执行可选的Runnbale,接下来开始下一个generation;
③在下一个分代中,将会重置count值为parties,并且创建新的Generation实例;
④同时会调用Condition的signalAll方法,唤醒所有在屏障前面等待的线程,让其开始继续执行;
⑤如果计数器没有归零,那么当前的调用线程将会通过Condition的await方法,在屏障前进行等待;
⑥以上所有执行流程均在lock锁的控制范围内,不会出现并发情况。


CAS(Compare And Swap)

①synchronized关键字与Lock等锁机制都是悲观锁:无论做何种操作,首先都需要先上锁,接下来再去执行后续操作,从而确保了接下来的所有操作都是由当前这个线程来执行的;
②乐观锁:线程在操作之前不会做任何预先处理,而是直接去执行;当在最后执行变量更新的时候,当前线程需要有一种机制来确保当前被操作的变量是没有被其他线程修改的;CAS是乐观锁的一种极为重要的实现方式;

CAS(Compare And Swap)

比较与交换:这是一个不断循环的过程,一直到变量值被修改成功为止。CAS本身是由硬件指令来提供支持的,换句话说,硬件中是通过一个原子指令来实现比较与交换的;因此,CAS可以确保变量操作的原子性的。

对于CAS来说,其操作数主要涉及到如下三个:
①需要被操作的内存值V
②需要进行比较的值A
③需要进行写入的值B

只有当V==A的时候,CAS才会通过原子操作的手段来将V的值更新为B

关于CAS的限制或是问题:
①循环开销问题:并发量大的情况下会导致线程一直自旋;
②只能保证一个变量的原子操作:可以通过AtomicReference来实现对多个变量的原子操作
③ABA问题:1 -> 3 -> 1


ThreadLocal

  本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程都会操作该副本,从而完全规避了多线程的并发问题。


Java中存在四种类型的引用

①强引用(strong)
②软引用(soft)
③弱引用(weak)
④虚引用(phantom)

public class Test{
	private static final ThreadLocal<String> tl = new ThreadLocal();
}

try {
......
}finally {
	tl.remove();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值