Java并发笔记
Posted .x->y=z
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发笔记相关的知识,希望对你有一定的参考价值。
为什么需要并行? – 业务要求 – 性能 并行计算还出于业务模型的需要 – 并不是为了提高系统性能,而是确实在业务上需要多个执行单元。 – 比如HTTP服务器,为每一个Socket连接新建一个处理线程 – 让不同线程承担不同的业务工作 – 简化任务调度 Linus Torvalds :并行计算只有在 *图像处理* 和 *服务端编程* 2个领域可以使用,并且它在这2个领域确实有着大量广泛的使用。但是在其它任何地方,并行计算毫无建树! 计算密集型 在多核时代,一般没有必要特别区分并发和并行 同步(synchronous)和异步(asynchronous) ? 并发(Concurrency)和并行(Parallelism) ? 临界区 ? 阻塞(Blocking)和非阻塞(Non-Blocking) – 阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起。这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其它所有阻塞在这个临界区上的线程都不能工作。 – 非阻塞允许多个线程同时进入临界区 ? 锁(Deadlock)、饥饿(Starvation)和活锁(Livelock) ? 并行的级别 并发级别 – 阻塞 – 非阻塞 – 无障碍 – 无锁 – 无等待 有关并行的2个重要定律 ? Amdahl定律(阿姆达尔定律) ? Gustafson定律(古斯塔夫森) 进程切换是一个重量级的操作,需要消耗大量的计算资源 在Java中的线程会直接映射到操作系统的线程上去 Thread t1 = new Thread(); t1.start(); // 开启线程进入就绪状态 Thread t2 = new Thread(); t2.run(); // 不能开启线程,在本线程内执行run方法 继承Thread重写run方法,MyThread extends Thread new Thread(new Runnable() {}).start(); Thread.stop()不建议使用@Deprecated,太过暴力,类似于Linux强制杀死进程:kill -9 thread_id public static native void sleep(long millis) throws InterruptedException public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("Interruted!"); break; } try { Thread.sleep(2000); } catch (InterruptedException e) { System.out.println("Interruted When Sleep"); // 设置中断状态,抛出异常后会清除中断标记位 ******** Thread.currentThread().interrupt(); } Thread.yield(); } } suspend()、resume() 等待线程结束(join)和谦让(yeild) join的本质: while(isAlive()) { wait(0); } 线程执行完毕后,系统会调用notifyAll(); ===> 可以不需要在线程实例上使用wait()和notifyAll() 守护线程 ? 在后台默默地完成一些系统性的服务,比如垃圾回收线程、 JIT线程就可以理解为守护线程 ? 当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出 Thread t = new DaemonT(); t.setDaemon(true); t.start(); 高优先级的线程更容易再竞争中获胜 基本的线程同步操作 ? synchronized – 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。 public void run() { for(int j = 0; j < 10000000; j++) { synchronized(instance) { i++; } } } – 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。 public synchronized void increase(){ i++; } – 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。 public static synchronized void increase(){ i++ } ? Object.wait() Obejct.notify() public static class T1 extends Thread { public void run() { synchronized (object) { System.out.println(System.currentTimeMillis()+":T1 start!"); try { System.out.println(System.currentTimeMillis() + ":T1 wait for object "); object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis()+":T1 end!"); } } } public static class T2 extends Thread { public void run() { synchronized (object) { System.out.println(System.currentTimeMillis() +":T2 start! notify one thread"); object.notify(); System.out.println(System.currentTimeMillis()+":T2 end!"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } 1425224592258:T1 start! 1425224592258:T1 wait for object 1425224592258:T2 start! notify one thread 1425224592258:T2 end! 1425224594258:T1 end notify() VS notifyAll() notify()随机唤醒一个线程,notifyAll()唤醒全部等待此监视器的线程,让他们去竞争这个监视器的使用权 》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》 Java内存模型和线程安全 ? 原子性 原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。 ? 有序性 在并发时,程序的执行可能就会出现乱序。 一条指令的执行是可以分为很多步骤的(*数据旁路技术*&*指令重排*可以使流水线更加流畅,指令重排不能出现语义问题) – 取指 IF – 译码和取寄存器操作数 ID – 执行或者有效地址计算 EX – 存储器访问 MEM – 写回 WB ? 可见性 可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。 – 编译器优化 – 硬件优化(如写吸收,批操作) Java虚拟机层面的可见性(volatile) ? Happen-Before ? 程序顺序原则:一个线程内保证语义的串行性 ? volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性 ? 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前 ? 传递性:A先于B,B先于C,那么A必然先于C ? 线程的start()方法先于它的每一个动作 ? 线程的所有操作先于线程的终结(Thread.join()) ? 线程的中断(interrupt())先于被中断线程的代码 ? 对象的构造函数执行结束先于finalize()方法 ? 线程安全的概念 指某个函数、函数库在多线程环境中被调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成。 ReentrantLock(重入锁 )是synchronized关键字的增强,目前两者性能不相上下。 ReentrantLock 可重入 可多次加锁 可中断 发生死锁时,可启用一个守护线程去中断某一线程从而达到解锁目的 可限时 避免死锁和长期等待锁 公平锁 公平锁虽然不会产生饥饿但是由于公平锁需要解决排队问题(先到先得),所以性能较非公平锁差,没有特殊要求没必要使用公平锁。 JDK并发包--并发容器及典型源码分析 ?? 集合包装 ? HashMap --> 适用小并发量,串行解决方案,非高并发解决方案 public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<>(m); } ? List public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); } ? Set public static <T> Set<T> synchronizedSet(Set<T> s) { return new SynchronizedSet<>(s); } ?? ConcurrentHashMap ConcurrentHashMap(HashMap底层使用数组实现,因此为实现大规模高并发,可将整个数组分成N个段<Segment>,一个就可供N个线程同时写入数据,理论提高效率N倍) put()方法各个Segment有各自的锁,get()方法无锁,但是在size()方法中需要拿到所有Segment的锁后才能统计数据,但size()方法并非一个高频率调用的函数。 之所是高性能,是因为不会随随便便就加锁,而是经过自旋等待在有必要的时候才加锁。 ?? BlockingQueue 阻塞队列,是一个接口,线程安全,不是一个高性能的容器,但是BlockingQueue是一个非常好的**在多个线程中共享数据的容器** 若为空队列,此时有线程尝试读取数据,则此读的线程会等待,直到有另外线程往队列中写入数据,则读的线程就会被唤醒并且读取数据; 若队列已经写满了,则写入的线程就会等待,直到有线程读取数据有空闲空间后才能写入队列; 因此Blocking会引起线程阻塞。 BlockingQueue作为生产者消费者容器很方便。 put()、take() 实现:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。 ?? ConcurrentLinkedQueue 类似ConcurrentHashMap的高性能Queue,内部使用了大量的无锁操作。offer()、poll() ? BlockingQueue VS ConcurrentLinkedQueue 在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式:一种是使用**阻塞算法**,另一种是使用**非阻塞算法**。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现。
以上是关于Java并发笔记的主要内容,如果未能解决你的问题,请参考以下文章
[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段