并发2

 

1.1      竞态条件

并发编程中,由于不恰当执行时序出现不正确的结果的情况

 延迟初始化

 读取-修改-写入操作

 

如何避免静态条件

   在某个线程修改变量时,通过某种方式防止其他线程使用此变量,确保其他线程只能在修改操作完成前或者完成后读取和修改状态,而不是在修改状态过程中。

 

1.1.1            使用线程安全对象代替非线程安全域

 AtomicLong….

 

好处:简单方便  弊端:涉及多个变量间的操作,变量状态间有关系时依然有问题

原理:轮询,记录变量是否被修改,被修改,重新读取执行操作,保证修改期间没其他线程操作修改过。

 

 

1.1.2        加锁机制

 

内置锁Synchronized

内置锁(内置):可以对方法加,对代码块加。进入同步代码块自动获得,退出同步代码块自动释放,同一时刻只有一个线程可以访问锁保护的变量。

1.是互斥锁,线程A获得锁,线程B无法获取,进入阻塞

2.是可以重入的,子类改写父类synchronized方法,调用super.XXX()。

锁使用不当很容易引起死锁等活跃性问题,同时也导致严重的性能问题。(减小锁的粒度,对于耗时长的计算尽量不要使用锁)

 

锁不仅仅是互斥性(防止某个线程正在使用对象状态其他线程修改该状态),更是可见性,线程在同步代码块中会从主内存中读取最新值到线程stack中。(线程修改了对象状态后,其他线程能看到,get方法也要加)

重排序问题: http://blog.csdn.net/u012312373/article/details/44983523

 

显示锁ReentrantLock(独占锁)

显示锁定、解锁(支持公平锁,等待时间,可中断,非块结构[锁的释放和锁获取在一个块]的锁粒度更细)

Lock lock = new ReentrantLock();

….

Lock.lock()

try{

..

}

Finally

{

Lock.unlock;

}

 

也是互斥锁,除了使用上需要手动功能同内置锁相同。

 

读写锁(共享锁)

ReentrantLock /ReentrantReadWriteLick  显示锁/读写的显示锁 

支持同时读,但是同时刻只能多个写

 

ReadWriteLock lock = new ReentrantReadWriteLock();

Lock r = lock.readLock();

Lock w = lock.writeLock();

 

r.lock();

try{

doRead

}

Finally

{              

r.unLock();

}

取多少次锁,就必须释放多少次锁,对内置锁和显示锁都适用。

 

等待通知比较

内置锁/条件队列

!!!共享锁和内置锁注意wait使用在while里边,不能使用if,之前wait

的线程可能随时被唤醒,if不会重新判断条件,导致出错~~参考KFC代码

Public synchoronized void put(V v)

{

While(isFull())

  wait();

doPut(v);

notifyALL();

}

 

Take方法类似,put和take是同一个锁,notifyALL会唤醒等待在此锁上的所有线程,但是不一定是take线程能获取到锁并执行。

显示锁/Condition

在锁上添加了条件,一个锁可以有多个条件,在一个条件上可以有多个线程等待,await会在此条件上等待,并且释放锁。SingalAll会唤醒等待在此条件上的所有线程。

Eg:教练在体育馆教导是上午【条件】打篮球,下午【条件】打羽毛球【共享一个场地,可看成一把锁】。

Lock lock = new ReentrantLock();

Condition notFull = lock.newCondition();

Condition notEmpty = lock.newCondition();

Public void put(V v)

{

  Lock.lock();

 Try{

   While(count==items.length)                 

notFull.await;        --等待容器不为满    

 …做put动作

notEmpty.signal()      --put后通知等待不为空的线程唤醒

}

Finally{

Lock.unlock();

}

}

 

Public void take(V v)

{

  Lock.lock();

 Try

{

   While(count==0)                 

noEmpty.await;        --等待容器不为空    

 …做take动作

notFull.signal()      --take后通知等待不为满的线程唤醒

}

Finally{

Lock.unlock();

}

}

 

 

 

1.1.3        Volatile变量

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。

Volatile不保证原子性,保证的是可见性和一定的有序性。

 

原子性测试:

 

可见性:

//线程1

boolean stop = false;

while(!stop){

    doSomething();

}

//线程2

stop = true;

 

这段代码有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。

那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。

但是用volatile修饰之后就变得不一样了:

第一:使用volatile关键字会强制将修改的值立即写入主存;

第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。

那么线程1读取到的就是最新的正确的值。

一定的有序性:

//x、y为非volatile变量

//flag为volatile变量

x = 2;        //语句1

y = 0;        //语句2

flag = true;  //语句3

x = 4;         //语句4

y = -1;       //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

 

并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

 

原理和机制

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

 

使用场景

通常来说,使用volatile必须具备以下2个条件:

1)对变量的写操作不依赖于当前值

2)该变量没有包含在具有其他变量的不变式中

上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

使用示例:

1.状态标记量

volatile boolean flag = false

while(!flag){

    doSomething();

}

public void setFlag() {

    flag = true;

}

 

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;

    }

}

Volatile及其内存模型好文链接: http://www.importnew.com/18126.html

1.2      线程封闭

1.2.1        栈封闭

局部变量(方法中的变量)的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈,避免使用同步,也就没有线程安全的问题。

方法发布的变量需要确保安全,返回对象的深拷贝或者不可变对象。

1.2.2        ThreadLocal类

ThreadLocal类为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的绑定机制,是每一个线程都可以独立地改变自己的副本,而不会与其他副本冲突。

对于多线程资源问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供了一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

理解ThreadLocal和类创建是类似的,Threadlocal是新建多个空间存类里边的ThreadLocal对象,为了避免多线程使用相同的数据,也可以在新建多个对象,新建线程的时候关联不同对象。适用于不需要要求可见性的场景.

 

2      同步工具类

线程访问代码段的限制,通过对代码段访问的限制实现线程间的协作和等待。

2.1      闭锁 CountDownLatch

一个等待N个线程结束 

Countdown()

Await()

比如:老板等待多个工人工作做完,然后检查。

多个线程持有相同的CountDownLatch对象,等待的线程(老板)使用await,等待线程(工人)countDwon().

2.2      循环屏障 CyclicBarrier

await()

N个线程间互相等待

比如赛跑的时候,等待每个人准备ok,一起出发,每位player停止在await位置,调用3此大家同时开始跑。单cpu看谁有cpu使用权。

     

CyclicBarrier barri = new CyclicBarrier(3);

可初始化另一个线程,用于ok后启动。

      //CyclicBarrier barri = new CyclicBarrier(3,new Refereee());

      Player cuncun = new Player("cuncun",barri);

      Player leilei = new Player("leilei",barri);

Player haihai = new Player("haihai",barri);

 

比较屏障和闭锁:从apI上能看出,屏障是到某个时刻多个线程同时开始。无await

2.3      信号量 Semaphore

   acquire()

release()

   用于控制并发线程数量

比如:一个文件的并发访问数量,停车场并发停车容量

 

同步工具文章分享: https://www.cnblogs.com/shijiaqi1066/p/3412338.html

 

3      线程间通信

3.1      轮询,使用volatile。

在一个线程中使用轮询判断变量条件,另一个线程完成后修改对应变量值.

不推荐:轮询会提高CPU使用率,一直运行,非通知机制。

3.2      回调。可以拿数据

两个线程各自有对象引用,可以随时通过引用调用对方方法(停止对方线程)。

回调的核心就是回调方将本身即this传递给调用方,这样调用方就可以在调用完毕之后告诉回调方它想要知道的信息。

     Eg:龟兔赛跑

3.3      Synchronized ,wait,notify ,lock ,condition等方法。

3.4      线程同步工具类。

3.5      Callable阻塞获取线程返回数据

4      多线程练习

4.1      三个线程顺序打印ABC

  Wait notify机制实现通信,同生产者消费者类似。

此处wait和notify指定不同的对象,可以显示唤醒在此锁上的线程。同condition类似。

重点理解唤醒谁,等待被谁唤醒,锁都是一把。

4.2      生产者消费者,生产者负责往篮子放苹果,消费者负责吃苹果,控制篮子最多五个苹果。

4.3      龟兔赛跑[线程间回调通信]

4.4      生产者消费者KFC

https://www.cnblogs.com/pureEve/p/6524366.html

4.5      线程同步工具类

posted on 2018-10-22 20:48  小付瓜  阅读(133)  评论(0)    收藏  举报