走入并行世界 读书笔记

一、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++;
          }
      }
      @Override
      public 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();
                  }
              }
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值