并发控制方法

解决问题:为多线程、多任务间的协作和数据共享提供并发控制。常用方法:内部锁、重入锁、读写锁、信号量等。
一、volatile
由于每个线程有自己的私有内存空间(计数器、本地方法栈、虚拟机栈),同时还保存了主内存中的共享变量的值的拷贝。因此如果线程想改变自己工作内存的数据时,对其他线程是不可见的。为此可以使用volatile关键字迫使所有线程读写主内存中的对应变量,从而使得其在多线程间可见。
声明为volatile的变量解决了线程间数据共享问题,可以保证:
1.其他线程修改了变量,可以立即反馈到当前线程
2.确保当前线程修改了变量,即使会写到主内存,对其他线程可见
3.编译期可以保证其变量有序
主内存和工作内存??
volatile特别适合于在不同线程中进行控制状态标记量

volatile boolean inited = false; // 线程1
context = loadContext();
        
inited = true; // 线程2
while(!inited) {
  sleep();
}
doSomethingWithConfig(context);

分析:
上面的代码利用volatile修饰的inited保证上下文的初始化是否完成,线程1初始化,线程2必须保证线程1初始化完成才能继续进行下去。加入volatile可以保证变量inited的改变及时刷到主存中,可以被其他线程及时看到。
二、synchronized
采用同步方式
1.使用:
一种是锁定一个对象的方法,如 public synchronized void method(),当method方法调用时,调用线程首先获得当前对象的锁,若当前对象锁被其他线程持有,则调用线程会等待,方法结束后,释放对象锁,以上方法等价于
public void method(){ synchronized(this){ } }
一种是锁定代码块,可以精确的控制并发代码,

public void method(Object so ){
some code here ;
synchronized(so){
  ...
}
other code here ;
}

一种是加在static方法上,锁的是当前Class对象,因此所有对该方法的调用,都要获取该Class对象的锁。
以上实际上锁的是对象(所有同步代码段用的同一个对象独占锁),只能防止多个线程同时执行同一个对象的同步代代码码段,即使不同的同步代码段也是不能同时访问的。
2.notify和wait
通常情况下,虽然实现了同步效果,但是对于复杂的业务逻辑,常常配合Object对象的wait和notify方法。
wait过程中,释放对象锁,典型用法:

synchronized (obj )
{
    while (condition)
            obj.wait();
    //收到通知后,继续执行
}

首先在wait前获得对象独占锁,循环进行状态判断,跳出后,wait方法执行后当前线程会释放对象锁,供其他线程使用
当等待在obj对象上的线程被notify后,会获得当前当前对象的独占锁,并继续运行。如果多个线程在等待,那么notify则随机选择一个
如下示例实现一个阻塞队列:

public class BlockQueue  {
    private List arrayList=new ArrayList();
    public  synchronized Object pop() throws InterruptedException {
        while(arrayList.size()==0)
            this.wait();
        if(arrayList.size()>0)
        {
            return  arrayList.remove(0);
        }
        else  return  null;

    }
    public  synchronized void add(Object obj)
    {
        arrayList.add(obj);
        this.notify();
    }
}

同时Object还有wait(long timeout)和notifyAll()将唤醒所有等待在当前对象上的所有线程

三、ReentrantLock重入锁
1.特性
(1).高并发下性能好,可中断、可定时
(2).公平锁和非公平锁:公平锁能保证线程间公平竞争锁对象,对锁的获取是有序的,非公平则无序,可能会出现插队现象
(3).必须手动调用unlock,否则会一直锁定程序,而synchronized随着jvm自动释放
2.重要方法
(1)lock:获得锁,如果锁被占用,就一直等待
(2)lockInerruptibly : 获得锁,但优先响应中断
(3)tryLock:尝试获得锁,成功返回true;失败返回false。该方法不等待,立即返回。
(4)tryLock(long timeout,TimeUnit unit),在给定时间内获得锁
(5)unlock:释放锁
注:lock方法会一直等待,直到获得锁,同时在等待锁的过程中,线程不会响应中断;而lockInterruptibly在线程等待锁过程中,可以优先响应中断。
总结:ReentrantLock重入锁提供了丰富的锁控制,如无等待的tryLock,还有优先响应中断的lockInterruptibly的锁。在锁竞争激烈的情况,这些灵活的锁可以提供更优的方案,从而提升系统性能

四、ReadWriteLock读写锁
jdk5提供的读写分离锁,可以有效减少锁竞争,提升系统性能。在读多的情况下,可以做到多个线程并行读操作,在写的时候还是使用锁。
适合于读多写少的场景

五、condition对象
具体参考:https://www.jianshu.com/p/be2dc7c878dc

六、Semaphone信号量
信号量为多线程协作提供了更强大的控制方法,是对锁的扩展。无论是的内部锁synchronized还是重入锁ReentrantLock都限制了只有一个线程访问一个资源,而信号量支持多个线程同时访问一个资源。
方法:
1.构造函数 :需要指定信号量的准入数,即多少个线程同时访问某个资源。

并发控制方法_第1张图片
image.png

信号量对锁进行了扩展,可以限定对某个资源的最大可访问线程数。

七、ThreadLocal 线程局部变量
能保证不同线程中的局部变量独立性,但是不属于多线程的数据共享。
机制:不提供锁,而是使用以空间换时间的方式,为每个线程提供变量的独立副本,以保证线程安全,因此它不属于多线程间数据共享的方案

public class MyThread implements  Runnable {
   public static  final ThreadLocal localvar=new ThreadLocal<>();
   private long time;
   public  MyThread(long time )
   {
       this.time=time;
   }
   @Override
    public  void run()
   {
        Date d=new Date(time);
        for (int i=0;i<50000;i++)
        {
            localvar.set(d);
            if(localvar.get().getTime()!=time)
                System.out.println("id="+time+"locallvar="+localvar.get().getTime());
        }
   }
}

你可能感兴趣的:(并发控制方法)