java并发-重读笔记整理

java并发有两本很好的书《java并发编程的艺术》、《java并发实战》。本文是重读前者把旧笔记再整理一遍而成。

                                        ——引言

(1)volatile与synchronized

以读一段代码的方式看下:

/** 单例模式下,如何保证并发安全:两个方案:synchronized;双重锁。
 * Created by baimq on 2019/3/16.
 */
public class Singleton {
    //volatile保证在缓存行和内存层面的读一致。
    private static volatile singleton instance;
    
    public  static Singleton getInstance(){
        if(instance==null){
            synchronized (singleton.class){//synchronized保证应用层面的代码访问原子性。
                if(instance==null){
                instance=new singleton();//因为创建对象并赋值,并不原子。所以必须保证可见性。
                 }
            }
        }
        return  instance;
    }
    
}

 

volitile:保证在多线程环境下读的可见性和读的一致性。如果多线程共享的变量不实现volitile,则会导致在A-CPU中读取到M值,随后在B-CPU中读取到N值。但因其不能保证写的原子性,所以不能用来实现内存计数器。

通过lock命令执行MESI协议,保证在CPU和内存层面的读写一致性。其在应用层面不能保证读写一致性。所以使用场景受限:

A、对变量的写操作不依赖于当前值。

B、该变量没有包含在具有其他变量的不变式中。

[MESI:缓存一致性协议。CPU缓冲行与内存之间的数据一致性协议。实现LOCK命令,总线锁或者缓存锁。]

synchronized:用来锁代码块或者方法,保证同一时间只有一个线程能访问到代码块。

Synchronized锁的升级:偏向锁,轻量级锁,重量级锁。

偏向锁:在对象头中写入线程号。

轻量级锁:通过CAS操作实现,如果不能获得锁,则自旋。自旋次数过多,则升级为重量级锁。

重量级锁:通过线程阻塞和唤醒等待线程的方式实现。

解读代码:

利用synchronized来保证只有一个线程可以执行创建对象的动作。

但是漏洞是创建对象并且赋值的过程不是原子的,所以可能发生还没创建好对象,就执行到了return instance阶段。

所以要再利用volitile来保证多个线程的读一致,保证不会在多个线程中读到不一致的结果。

(2)线程的生命周期、对象的锁

thread.join:等待线程执行完毕。

thead.start:开始执行。

thead. Interrupt::安全中断(并没有真中断,而是使线程的isInterrupted返回false)。

thead. isInterrupted()[常用语法:Thread.currentThread().isInterrupted(),用作安全终止线程]。

thread.run,仅执行runnable的run方法一次,与开启线程无关。

 

对象.wait();//让持有对象线程等待

对象.notify();//随机唤醒持有对象的线程之一。

对象notifyAll();//唤醒持有对象的多个线程。

(3)Lock

Lock接口,四种锁的方式:

lock()阻塞性的锁;

尝试非阻塞地获取锁;

能被中断的获取锁;

超时获取锁。

重入锁:可以多次锁,释放时也需要多次释放。

读写锁:在写的过程中阻塞所有读和写。适合读多写少的场景。

(4)ThreadLocal

为对象的共享变量创建本线程的副本。SimpleDateFormat是非线程安全的类,包装下就安全了,原理见我其他博客:

public class SafeDateUtil {

    private static  ThreadLocal<SimpleDateFormat> threadLocalSDF = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static  String formatDate(Date date)throws ParseException{
        return threadLocalSDF.get().format(date);
    }

}

(5)CountDownLatch、CyclicBarrier(同步屏障)、semaphore(信号量、限流器)

CountDownLatch是等所有线程执行完;

CyclicBarrier是等待在线程内屏障点上。

CyclicBarrier更强大,可以完全hold住CountDownLatch的功能。

一个简单的例子把:

/**
 * Created by baimq on 2019/3/16.
 */
public class CountDownDemo {

    public static void main(String[] args) {
        CountDownLatch countDownLatch=new CountDownLatch(3);
        new Thread(new WorkerRunnable(countDownLatch,30)).start();
            new Thread(new WorkerRunnable(countDownLatch,1000)).start();
            new Thread(new WorkerRunnable(countDownLatch,1)).start();
            try {
                Thread.sleep(20);
            System.out.println("在计数中,干完活的有:"+countDownLatch.getCount()+"个");
            countDownLatch.await();
            System.out.println("全部干完活了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static class  WorkerRunnable implements Runnable{
        private CountDownLatch countDownLatch;
        private int sleep;
        public WorkerRunnable(CountDownLatch countDownLatch,int sleep){
            this.countDownLatch=countDownLatch;
            this.sleep=sleep;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"在干活");
            try {
                Thread.sleep(sleep);
                System.out.println(Thread.currentThread().getName()+"干完活了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
        }
    }
}

semaphore是限流器保证只有N个线程在同时执行。

(6)Fork/join、并发容器、executor框架

这些很早就在用了,所以比较熟。

fork/join是为了解决任务切割执行。

并发容器如concurrentHashMap等是分区思路,切割为较小的segment,并发写时对segment锁住。

executor是线程池执行框架,支持设置线程容量、设置拒绝策略,设置队列延后执行等。

 

posted @ 2019-03-16 23:24  叶扬  阅读(240)  评论(4编辑  收藏  举报