零零碎碎的东西老是记不长久,仅仅学习别人的文章也只是他人咀嚼后留下的残渣。无心中发现了这个每日一道面试题,想了想若是只是简单地去思考,那么不只会收效甚微,甚至难一点的题目本身可能都懒得去想,坚持不下来。因此不如把每一次的思考、理解以及别人的看法记录下来。不只加深本身的理解,更要激励本身坚持下去。java
在介绍多线程中的同步以前,咱们先来了解下并发编程。git
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
复制代码
线程1中语句1和语句2没有数据依懒性,inited仅是一个标记变量,因此这两个语句可能发生指令重排序。当语句2在语句1以前执行时,这是刚好线程2启动,标记变量init为true则线程2认为初始化已经完成,而此时语句1并无执行,就会形成问题。github
因此说,并发编程中保证原子性、可见性、有序性,是同步的基本要素面试
volatile是java的一个关键字,一旦一个共享变量(类的成员变量、静态变量)被volatile关键字修饰,就具有有两层含义算法
这就保证了可见性与有序性,可是volatile并不保证可见性。看下面一段代码编程
public class Main {
private volatile static int test = 0;
private volatile static int count = 10;
public static void main(String[] args) {
for(int i=0;i<10;i++){
Main mm = new Main();
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<10000;j++){
mm.increase();
}
count--;
}
}).start();
}
while (count > 0){}//全部线程执行完毕
System.out.println("最后的数据为" + test);
}
private void increase(){test++;}
}
复制代码
运行后你会发现,每一次的结果都小于100000。这是由于test++
这个操做,它不是原子性的,与test自己这个变量无关。数组
test++
通过三个原子操做,读取test变量值、test变量进行加一操做、将操做后的变量值写入工做内存。当线程1执行到前两步时,线程2开始读取test变量值,当线程1三个步骤执行完毕时,虽然此时test的值会立马更新到线程2,可是线程2已经在此以前进行了读取变量值的操做,因此实际上两个线程只让test加了一次。缓存
因此说,volatile只进行一些简单的同步操做,好比上面提到的标记变量安全
volatile boolean inited = false;
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
复制代码
并发编程中的单例模式性能优化
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;
}
}
复制代码
这个虽然有synchronized关键字来保证单线程访问,可是这里面实际上是instance=new Singleton()
指令重排序的问题,这一步有三个原子性操做
synchronized一样是java中的一个关键字。它经过锁机制实现同步,要执行代码,则必需要得到锁,只有得到锁对象的线程才能执行锁住的代码,其余线程没有得到锁只能阻塞。锁有对象锁和类锁。同步有两种表现形式:同步代码块和同步方法
对象锁
class Test{
public void testMethod(){
synchronized(this){
...
}
}
}
复制代码
类锁
class Test{
public void testMethod(){
synchronized(Test.class){
...
}
}
}
复制代码
对象锁。这里的o表明任意一个object对象或者数组,谁拥有这个对象谁就能够执行该程序块代码
class Test{
public void testMethod(){
synchronized(o){
...
}
}
}
复制代码
类锁
class Test{
public synchronized static void testMethod(){
...
}
}
复制代码
对象锁
class Test{
public synchronized void testMethod(){
...
}
}
复制代码
ReentrantLock是一个类,它的同步方法与synchronized大体相同。
基本用法
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
.....................
lock.lock(); //若是被其它资源锁定,会在此等待锁释放,达到暂停的效果
try {
//操做
} finally {
lock.unlock(); //释放锁
}
复制代码
ReentrantLock经过lock方法与unlock方法显式的获取锁与释放锁,与synchronized隐式的获取锁不一样。当线程执行到lock.lock()方法时,会尝试获取锁,获取到锁则执行下去,获取不到则会阻塞。unlock()方法则会释放当前线程所持有的锁,若是没有锁能够释放可能会发生异常。
显式的获取锁虽然比隐式的自动获取锁麻烦了很多,但多了许多可控制的状况。咱们能够中断获取锁、延迟获取锁等一些操做。
公平锁
当许多线程在队列中等待锁时,cpu会随机挑选一个线程得到锁。这样就会出现饥饿现象,即优先级低的线程不断被优先级高的线程抢占锁资源,以致于很长时间得到不到锁,这就是不公平锁。RenntrantLock可使用公平锁,即cpu调度按照线程前后等待的顺序得到锁,避免饥饿现象。可是执行效率会比较低,由于须要维护一个有序队列。synchronized是不公平锁。
ReentrantLock lock = new ReentrantLock(true);
复制代码
经过在建立对象时传入boolean对象表示使用什么锁,默认为false不公平锁。
能够看出,ReentrantLock实现了许多更高级的功能,不过却多了点复杂性。在性能上来讲,竞争不激烈时,二者的性能是差很少的,不过当竞争激烈时,即有大量线程等待获取锁,ReentrantLock的性能要更好一些,具体的使用看状况进行。
jdk1.6之前synchronized的性能是不好的,jdk1.6之后对synchronized的性能优化了很多,和ReentrantLock性能差不了多少。官方也表示更支持synchronized,之后还有优化的余地,因此在都能符合需求的状况下,推荐使用synchronized。
乐观锁与悲观锁:
cpu调度线程,经过将时间片分配给不一样的线程进行调度。时间片的切换也就是线程的切换,须要清除寄存器、缓存数据,切换后加载线程须要的数据,须要耗费必定的时间。线程阻塞后,经过notify、notifyAll唤醒。假如线程1在尝试获取锁,获取失败,挂起。这时锁被释放,线程1被唤醒,尝试获取锁,结果又被其余线程抢占锁,线程1继续挂起,获取锁的线程只占用锁很短的时间,释放锁,线程1又被唤醒。。。就这样,线程1反复的挂起、唤醒,线程1认为其余线程获取锁就必定会对锁内的资源进行更新等操做,因此不断等待,这就是悲观锁。synchronized这种独占锁就是悲观锁。
乐观锁并不加锁,首先会认为在本身修改资源以前其余线程不会对资源进行更新等操做,它会尝试用锁内资源进行本身的操做,若是修改后的数据发生冲突,就会放弃以前的操做。就这样一直循环,知道操做成功。
CAS就是一种乐观锁的概念,内有三个操做数---内存原值(C)、预期旧值(A)、新值(B),当且只当内存原值与预期旧值的结果同样时,才更新新值。否则就是不断地循环尝试。Java中java.util.concurrent.atomic包相关类就是 CAS的实现.
类名 | 说明 |
---|---|
AtomicBoolean | 能够用原子方式更新的 boolean 值。 |
AtomicInteger | 能够用原子方式更新的 int 值。 |
AtomicIntegerArray | 能够用原子方式更新其元素的 int 数组。 |
AtomicIntegerFieldUpdater | 基于反射的实用工具,能够对指定类的指定 volatile int 字段进行原子更新。 |
AtomicLong | 能够用原子方式更新的 long 值。 |
AtomicLongArray | 能够用原子方式更新其元素的 long 数组。 |
AtomicLongFieldUpdater | 基于反射的实用工具,能够对指定类的指定 volatile long 字段进行原子更新。 |
AtomicMarkableReference | AtomicMarkableReference 维护带有标记位的对象引用,能够原子方式对其进行更新。 |
AtomicReference | 能够用原子方式更新的对象引用。 |
AtomicReferenceArray | 能够用原子方式更新其元素的对象引用数组。 |
AtomicReferenceFieldUpdater | 基于反射的实用工具,能够对指定类的指定 volatile 字段进行原子更新。 |
AtomicStampedReference AtomicStampedReference | 维护带有整数“标志”的对象引用,能够用原子方式对其进行更新。 |
这种不须要锁的非阻塞算法,在性能上是要优于阻塞算法。通常使用以下,实现自增i++
的同步操做
public class Test {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}
复制代码
CAS的问题