走入并行世界 读书笔记
来源:互联网 发布: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(); } }}
阅读全文
1 0
- 走入并行世界 读书笔记
- 走入并行世界
- 第一章 走入并行世界
- 走入IBM小型机世界
- 走入ios的世界
- 走入PHP的世界
- 走入jquery的世界
- 《走入IBM小型机世界》读书笔记[冰砖]----第一天
- 走入“地图定位、导航”开发的世界
- 走进并行世界
- AJAX的革命性框架,带大家走入ZK的世界
- PD(problem detection 经验谈--摘自走入小型IBM世界)
- 走入C++程序世界-------第一个C++程序
- 今天“农村打工仔”带你们走入Android的世界
- 初学cocoa开发:带你走入不一样的世界
- 初学cocoa开发:带你走入不一样的世界
- 带你一步步走入Paxos的世界 -- 序列1
- 带你一步步走入Paxos的世界 -- 序列2
- caffe 分类源码解读
- 安卓三方 极光推送
- 解决/bin/sh: lz4c: 未找到命令
- hibernate配置
- 反向传播算法的理解(Nielsen版)
- 走入并行世界 读书笔记
- Hadoop 2.X的安装与配置
- Bootstrap-select 多选下拉框(官网的js地址错误)
- 数据结构<七>: 二叉树的遍历
- 函数的作用域、匿名函数
- JAVA this指针
- Spring Boot 集成 Mybatis
- Golang字符串函数用法
- vtk7.1.0+vs2010 读取bmp 图片序列