走入并行世界 读书笔记

来源:互联网 发布:js最新正则表达式大全 编辑:程序博客网 时间:2024/04/30 11:50

一、JMM:Java Memory Model(Java内存模型)

  • 1.1原子性
    操作不可中断
  • 1.2可见性
    共享变量的修改,其他线程是否能够立即知道这个修改
  • 1.3有序性

二、java并行程序基础

Theard案例
class MyThread extends Thread {    @Override    public void run() {       do something...    }}MyThread mt = new MyThread("thread name");mt.start();
Runnable案例
class MyRunnable implements Runnable {    @Override    public void run() {        do something...    }}MyRunnable mt = new MyRunnable();Thread td = new Thread(mt, "thread name");td.start();
并发中的List
  • 异常问题:
    ArrayList al = new ArrayList(10);
    如果多个线程add数据,ArrayList扩容有些时候会越界
  • 解决:
    使用Vector,不过性能不好
    使用CopyOnWriteArrayList,读多写少下,性能非常好
    使用ConcurrentLinkedQueue,高效并发队列,线程安全的LinkedList
并发中的Map
  • HashMap,java8解决了
  • 其他的话,用ConcurrentHashMap线程安全的hashMap

三、线程操作

新建
start()
终止
自己定义一个boolean值,不要用stop()方法
睡眠 + 中断(一般用于线程中断的异常操作)
  • Thread.interrupt() # 中断线程,但是如果没有后续逻辑,它其实没有操作什么

    /** * 线程中断测试 */public static void main(String[] args) throws InterruptedException {    Thread t1 = new Thread() {        @Override        public void run() {            // 循环监控            while(true) {                if (Thread.currentThread().isInterrupted()) {                    System.out.println("线程中断");                    break;                }                try {                    Thread.sleep(2000);                    System.out.println("子线程执行中");                } catch (InterruptedException e) {                    System.out.println("当前线程睡眠被中断");                        // 不设置中断,则下一次循环无法捕获,所以还需要设置中断标记                    Thread.currentThread().interrupt();                 }            }        }    };    t1.start(); // 子线程执行中    Thread.sleep(5000); // 主线程睡眠5秒    t1.interrupt(); // 强制中断子线程}console:    子线程执行中    子线程执行中    当前线程睡眠被中断    线程中断
等待和通知(Object类),下面的join方法就是用的这些,所以少用wait和notify,直接用join即可
  • wait、notify、notifyAll
  • 必须包含在对应的synchronized语句中,必须获得目标对象的一个监视器
  • sleep不会释放锁,wait会释放

    public class ThreadTest {    // 锁    final static Object obj = new Object();    public static class T1 extends Thread{        @Override        public void run() {            synchronized (obj){                System.out.println(System.currentTimeMillis() + " T1 start");                try {                    System.out.println(System.currentTimeMillis() + " T1 wait from obj");                    obj.wait(); // 等待                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(System.currentTimeMillis() + "T1 end");            }        }    }    public static class T2 extends Thread{        @Override        public void run() {            synchronized (obj){                System.out.println(System.currentTimeMillis() + " T2 start!notify one thread");                obj.notify(); // 唤起,必须是同一个obj锁内                System.out.println(System.currentTimeMillis() + " T2 end!");                try {                    Thread.sleep(2000); // 睡眠2秒,不放开锁                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    public static void main(String[] args) { // ti不一定先执行,多执行几次        Thread t1 = new T1();        t1.start();        Thread t2 = new T2();        t2.start();    }}console:    151125703 2560 T1 start    151125703 2560 T1 wait from obj    151125703 2560 T2 start!notify one thread    151125703 2560 T2 end!    151125703 4560 T1 end //T2输出了end以后sleep了2秒,所以sleep并不会释放锁
等待线程结束join和谦让yield
  • join,等待线程完成再执行

    public volatile static int i = 0; // 同步参数public static class AddThread extends Thread {    @Override    public void run() {        for (i = 0; i < 1000000; i++) ;    }}public static void main(String[] args) throws InterruptedException {    Thread t = new AddThread();    t.start();    t.join();               // 如果没有join方法,主线程就已经打印i了;现在需要等t线程完成再打印    System.out.println(i);}
  • yield,当前线程让出cpu,重新进行竞争
    做完重要的事情了,然后让出cpu,给其他线程一些机会
    或者优先级低,怕它占用太多cpu资源,适当的时候使用yield方法
  • volatile
    它只能保证一个线程修改了数据后,其他线程能够看到这个改动,2个线程如果同时修改某一个数据时,依然会产生冲突
线程组
  • 能管理一组线程,比如中断、销毁、停止、优先级等,但是不能同时启动
    @Override    public void run() {        String groupAndName = Thread.currentThread().getThreadGroup().getName() +                "-" + Thread.currentThread().getName();        while(true){            System.out.println("I am " + groupAndName);            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args) {        ThreadGroup tg = new ThreadGroup("PrintGroup");        Thread t1 = new Thread(tg, new thisClass(), "name1");        Thread t2 = new Thread(tg, new thisClass(), "name2");        t1.start();        t2.start();        // activeCount 估计线程总数,因为线程是动态的        System.out.println(tg.activeCount());         tg.list(); // 打印组内所有线程信息    }
守护线程
  • t.start()前设置: t1.setDaemon(true);
  • 用户线程结束了,守护线程自动就结束了,所以不要用在类似于IO这样的线程中
  • 见过的比如 垃圾回收、数据库线程数量监控等等,eg:如果想main结束,线程就结束,可以设置为守护线程
优先级
  • 各个平台表现不一,不严格,有效为1-10
  • t.setPriority(Thread.MAX_PRIORITY);
  • t.setPriority(Thread.MIN_PRIORITY);
  • 或者直接输入1-10
线程池
  • newFixedThreadPool 返回固定线程数量的线程池
  • newCachedThreadPool 可变数量
  • 还有2个是定时执行的,暂时不看了
public static class MyTask implements Runnable{        @Override        public void run() {            System.out.println(System.currentTimeMillis() + ":Thread ID:"             + Thread.currentThread().getId());            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args) {        MyTask task = new MyTask();        // 只有5个线程,处理完5个sleep2秒返回线程池,再用这5个        ExecutorService es = Executors.newFixedThreadPool(5);        // 直接扩充成10个线程了//        ExecutorService es = Executors.newCachedThreadPool();        for (int i = 0; i < 10; i++){            es.submit(task);            // es.execute(task); // 如果线程输出缺失,这个替换submit能查看到堆栈信息        }    }
  • 自定义线程
    /**     * int corePoolSize     核心线程数,一直存活     * int maximumPoolSize  最大线程,超出会进入拒绝策略     * long keepAliveTime   空闲时间,超过则退出线程     * TimeUnit unit        空闲时间单位     * WorkQueue<Runnable> workQueue    任务队列(常用3个,见下)     * RejectedExecutionHandler handler     拒绝任务的处理策略(4个,见下)     *           * 以下参数顺序同上     */    ......        ThreadPoolExecutor es = new ThreadPoolExecutor(10, 100, 10, TimeUnit.SECONDS,            new ArrayBlockingQueue<Runnable>(10),            new ThreadPoolExecutor.DiscardPolicy());    ......
- 任务队列    - 它是一个BlockingQueue接口的对象    - SynchronousQueue 直接提交的队列,不真实保存数据,当线程池满了就执行拒绝策略,所以一般将最大线程数量设置很大    - ArrayBlockingQueue,有界队列,核心线程满了,进入队列,顺序保存数据    - LinkedBlockingQueue,无界队列,核心线程满了,进入队列,不会失败,无限,直到耗尽内存    - PriorityBlockingQueue,优先任务队列,无界,在确保性能的同时,能保证质量- 拒绝策略    - AbortPolicy   抛出异常,阻止运行    - CallerRunsPolicy  运行当前被丢弃的任务,性能可能会极具下降    - DiscardOldestPolicy   抛弃最老的任务    - DiscardPolicy     抛弃无法处理的任务,如果允许任务丢失,这个是最好的
  • 自定义线程二(一般不用自己创建,使用默认的即可)
    同自定义线程,不过可以不用它的策略,自己扩展策略或者使用线程工厂自定义线程池
    ......    ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,                new SynchronousQueue<Runnable>(),                new ThreadFactory() {                    @Override                    public Thread newThread(Runnable r) {                        Thread t = new Thread(r);                        // t.setDaemon(true); // 可以设为守护线程                        System.out.println("create " + t);                        return t;                    }                }    );    ......
  • 最优线程池
    • Ncpu = CPU的数量 System.out.println(Runtime.getRuntime().availableProcessors());
    • Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
    • W/C = 等待时间 与 计算时间的比率
    • Nthreads = Ncpu * Ucpu * ( 1 + W/C )
    • 我拿我的计算机进行了测试

四、同步 synchronized

  • 必须是同一对象锁(int行,Integer不行,++操作以后Integer是个新对象)
  • 总结用法:

    • 对象锁
    • 直接作用于实例方法
    • 直接作用于静态方法

      public class ThreadTest implements Runnable {    static ThreadTest tt = new ThreadTest();    static int i = 0;    @Override    public void run() {        synchronized (tt) { // 如果不同步,则i会被同时写入,造成实际 <200000            for (int j = 0; j < 100000; j++) {                i++;            }        }    }    public static void main(String[] args) throws InterruptedException {        Thread t1 = new Thread(tt); // 注意!必须是同一个对象        Thread t2 = new Thread(tt); // 注意!必须是同一个对象        t1.start();t2.start();        t1.join();t2.join();        System.out.println(i);    }}
    • 另一种写法,抽出同步代码块,变成同步方法/同步静态方法

      public synchronized void increase(){ // static也一样    for (int j = 0; j < 100000; j++) {        i++;    }}@Overridepublic void run() {    increase();}
  • 优化建议

    • jdk6以后synchronized和lock性能非常接近了,用哪个都行,就是读写锁感觉很有用
    • 减少持有时间
      尽量减少同步的方法
    • 减少锁粒度
      不用考虑太多,除非调用少才合适
    • 读写分离锁来替换独占锁
    • 锁分离
    • 锁粗化
      串行的很多锁可以合并为块,开关锁也是要消耗资源的,例如for循环内部的锁不如外部一并加锁
    • 读写锁案例
          public class ReadWriteLockDemo {        // 原来的锁        private static Lock lock = new ReentrantLock();        // 创建读、写锁        private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();        private static Lock readLock = readWriteLock.readLock();        private static Lock writeLock = readWriteLock.writeLock();        // 读取的参考变量        private int value;        // 模拟读方法        public Object handleRead(Lock lock) throws InterruptedException {            System.out.println(Thread.currentThread().getName() + "-read-begin");            try {                lock.lock(); // 模拟读操作                Thread.sleep(2000); // 读操作耗时越多越明显;线程(除了被写占用的线程)会一并进入方法,然后各读各的,各返回各的                return value;            } finally {                lock.unlock();                System.out.println(Thread.currentThread().getName() + "-read-end");            }        }        // 模拟写方法        public void handleWrite(Lock lock, int index) throws InterruptedException {            System.out.println(Thread.currentThread().getName() + "-write-begin");            try {                lock.lock();                Thread.sleep(1000);                value = index;            } finally {                lock.unlock();            }            System.out.println(Thread.currentThread().getName() + "-write-end");        }        public static void main(String[] args) {            final ReadWriteLockDemo demo = new ReadWriteLockDemo();            // 模拟读线程            Runnable readRunnale = new Runnable() {                @Override                public void run() {                    try {                        // 用了读锁,所有的读线程会一起进入,相互不阻塞                        demo.handleRead(readLock);                        // 用普通的锁,会一个一个进入    //                    demo.handleRead(lock);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            };            Runnable writeRunnale = new Runnable() {                @Override                public void run() {                    try {                        // 用了写锁,和普通的lock一样,是阻塞的                        demo.handleWrite(writeLock, new Random().nextInt());    //                    demo.handleWrite(lock, new Random().nextInt());                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            };            for (int i = 0; i < 18; i++){                new Thread(readRunnale).start();            }            for (int i = 18; i < 20; i++){                new Thread(writeRunnale).start();            }        }}