JAVA 并发编程学习总结
来源:互联网 发布:淘宝图标图片 编辑:程序博客网 时间:2024/06/05 07:28
- 一 相关概念
- 二 并发级别
- 三 并行的两个定律
- 四 线程和进程
- 线程中断 TODO 待补充
- join方法
- 守护线程
- 五 内存模型和线程安全
- 原子性
- 有序性
- 可见性
- Happens-Before
- 五种实现同步通信的机制
- waitnotify 方法
- awaitsignal方法
- BlockingQueue阻塞队列
- Semaphore 信号量
- 管道通信
- 读写锁问题
- 六 无锁类
- 七 并发包
- ReentrantLock 完全互斥锁
- Timer 定时器
- CountDownLatch 倒数计时器
- CyclicBarrier
- 八 线程池
- 九 锁优化
- 虚拟机内的锁优化
- 偏向锁偏向当前已经持有锁的线程
- 轻量级锁减少线程互斥的几率
- 自旋锁 不断尝试请求获取锁
- 虚拟机内的锁优化
一. 相关概念
1.同步和异步
同步是指在发出一个功能调用时,在没有得到结果之前,该调用就不返回
异步是指当一个异步过程调用发出后,调用者不需要等待结果返回随时可以进行下一个请求,在后台会开启线程继续执行该任务,该任务完成后会通过状态、通知和回调来通知调用者
2.并发和并行
并发: 两个或多个事件在同一个时间段内发生
并行:两个或多个事件在同一时刻发生
3.临界区和临界资源
临界资源: 一次仅允许一个线程使用的共享资源
临界区: 每个线程中访问临资源的那段程序称为临界区,每次只允许一个线程进入临界区
4.阻塞和非阻塞
阻塞: 在调用结果返回之前,当前线程会被挂起。函数只有在得到结果只会才会返回
非阻塞: 在不能立刻得到结果之前 该函数不会阻塞当前线程而是会立即返回
5.死锁、活锁和饥饿
死锁: 两个或两个线程在执行过程中,由于竞争资源造成的阻塞的过程。没有外力推动的情况下无法进行下去。
产生死锁有四个必要条件:
- (1) 互斥条件:一个资源每次只能被一个进程使用。
- (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
举一个很常见的例子,有两个线程ThreadA和ThreadB。ThreadA先获取变量A的锁,再获取变量B的锁;而ThreadB先获取变量B的锁再获取变量A的锁,由于线程的调度是随机的,那么有可能ThreadA先获取了A的锁,而此时ThreadB获取了变量B的锁,ThreadA阻塞等待ThreadB释放B锁,而ThreadB又阻塞等待ThreadA释放A锁,两个线程陷入无限期的等待,也就是死锁
package com.hqq.day25.concurrency;/** * DeadLock * 死锁Demo * Created by heqianqian on 2017/8/12. */public class DeadLock { private static final Object A = new Object(); private static final Object B = new Object(); public static void main(String[] args) { new ThreadA().start(); new ThreadB().start(); } private static class ThreadA extends Thread { @Override public void run() { try { System.out.println("Thread A Try to Lock A"); synchronized (A) { System.out.println("Thread A Locked A"); System.out.println("Thread A Try to Lock B..."); Thread.sleep(1000); synchronized (B) { System.out.println("Thread A Locked B"); } } } catch (InterruptedException e) { e.printStackTrace(); } } } private static class ThreadB extends Thread { @Override public void run() { try { System.out.println("Thread B Try to Lock A"); synchronized (B) { System.out.println("Thread B Locked A"); System.out.println("Thread B Try to Lock B..."); Thread.sleep(1000); synchronized (A) { System.out.println("Thread B Locked B"); } } } catch (InterruptedException e) { e.printStackTrace(); } } }}
运行结果
Thread A Try to Lock AThread A Locked AThread A Try to Lock B...Thread B Try to Lock AThread B Locked AThread B Try to Lock B...
两个线程都陷入了等待……
相对来说死锁还是比较容易判断的,而另一种不容易发现的就是活锁了
活锁:指事物1可以使用资源,但它让其他事物先使用资源;事物2可以使用资源,但它也让其他事物先使用资源,于是两者一直谦让,都无法使用资源。
因此避免活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列中第一个事务获得锁。
饥饿如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求……T2可能永远等待,这就是饥饿。
二. 并发级别
1.阻塞并发 Blocking algoithms
是并发级别最低的同步算法 同一时刻只能一个线程访问临界区资源
2.无阻塞并发 Obstruction-freedom
是指在任何时间点,一个孤立运行线程的每一个操作可以在有限步内结束。只要没有竞争,线程可以持续运行。一旦共享数据被修改,就会终止已完成的部分操作并进行回滚
3.无锁并发 Lock-freedom
Lock-freedom是指整个系统作为一个整体一直运行下去,系统内部单个线程某段时间内可能饥饿,因此无锁并发保证每次都有一个线程胜出,不会进入无限期的等待。可以用CAS实现
4.无等待并发 Wait-freedom
Wait-freedom是指每一个线程都一直运行下去而无需等待外部条件。整个流程操作都在一个有限步的步骤内完成。该级别是最高的并发级别,没有任何阻塞
三. 并行的两个定律
1.Amdahl定律
定义了串行系统并行化后加速比的计算公式和上限
加速比=优化前耗时/优化后耗时
举个例子
加速比=优化前系统耗时/优化后系统耗时=500/400=1.25
上图是加速比和处理器个数的关系
2.Gustafson定律
执行时间=a[串行时间]+b[并行时间]
总执行时间=a+n*b[n为处理器个数]
加速比=(a+n*b)/(a+b)
串行比例 F = a / (a+b)
四. 线程和进程
进程(Process):
- 具有一定独立功能的程序
- 关于某个数据集合上的一次运行活动
- 系统进行资源分配和调度的一个独立单位.
线程(Thread):
- 进程的一个实体
- 是CPU调度和分派的基本单位
- 比进程更小的能独立运行的基本单位
- 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
二者的关系:
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
区别:
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
优缺点
线程和进程在使用上各有优缺点:
线程执行开销小,但不利于资源的管理和保护;而进程正相反。
同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
1.线程中断 //TODO 待补充
使用interrupt()方法中断线程只是打了一个停止的标记 并不是真正停止线程
其他方法:
boolean isInterrupted(): 判断线程是否被中断
boolean interrupted(): 判断是否被中断 并清除当前中断状态
2.yield()和join()方法
yield()方法:
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
yield()是一个静态的本地方法,表示的的那个线程愿意放弃CPU的使用权并且和其他线程一起竞争CPU
因此和sleep()方法的区别就是 调用yield()的线程还是有可能获得CPU的使用权的
join()方法:
/** * Waits at most {@code millis} milliseconds for this thread to * die. A timeout of {@code 0} means to wait forever. * * <p> This implementation uses a loop of {@code this.wait} calls * conditioned on {@code this.isAlive}. As a thread terminates the * {@code this.notifyAll} method is invoked. It is recommended that * applications not use {@code wait}, {@code notify}, or * {@code notifyAll} on {@code Thread} instances. * * @param millis * the time to wait in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
join()方法是用途是等待当前线程运行结束。
查看源码知道,默认情况下即没有设置join()的时间时 只要当前还有运行的线程 则等待该线程运行结束后再运行。
此处只有wait() 而我们并没有看到notify() 原因是每个线程结束后JVM会自动调用notifyAll()
while (isAlive()) { wait(0); }
例子: 如果使Main方法在前面四个线程顺序执行完再执行?
可以使用join()简单实现
package com.hqq.day25.concurrency.common;/** * JoinDemo2 * Created by heqianqian on 2017/8/12. */public class JoinDemo2 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " running...."); } }; Thread thread1 = new Thread(runnable); thread1.setName("Thread1"); Thread thread2 = new Thread(runnable); thread2.setName("Thread2"); Thread thread3 = new Thread(runnable); thread3.setName("Thread3"); thread1.start(); thread2.start(); thread3.start(); thread1.join(); thread2.join(); thread3.join(); System.out.println("Main Ended!"); }}
运行结果
Thread1 running....Thread2 running....Thread3 running....Main Ended!
join()和sleep()的区别
join()内部是使用wait()来实现的 因此也就等同于wait()和sleep()的区别,也就是wait()是会释放锁进入等待池等待被唤醒,而sleep()不会释放锁只是睡眠到一定时间又继续执行
守护线程
守护线程用来在后台完成一些系统服务 当所有非守护线程运行结束 守护线程也会终止
使用setDeamon(boolean)
设置当前线程是否是守护线程
五. 内存模型和线程安全
1. 原子性
- 一个操作是不可中断的 即使在多个线程一起执行时 一个操作一旦开始 也会是不可中断的
2. 有序性
- 指令的执行顺序和编写的源代码的顺序相同
实际在执行程序时为了提高性能 编译器和处理器会对指令做重排序
重排序分三种类型:
- 编译器优化的重排序:
编译器在不改变单线程序语义的前提下 可以重新安排语句的执行顺序 - 指令级并行的重排序:
将多条执行重叠执行 如果不存在数据依赖性 处理器可以改变语句对应机器指令的执行顺序 - 内存系统的重排序:
由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
3.可见性
- 当一个线程修改了一个共享变量的值 其他线程可以立即知道这个修改
4.Happens-Before
满足一下原则:
- a) 程序顺序原则: 一个线程内保证语义的串行性
- b) volatile原则: volatile变量的写优先于读
- c ) 锁原则: 对于一个监视器的解锁,happens-before于随后对这个监视器的加锁
- d) 传递性原则: A优先于B B优先于C 可以得出A优先于C
- 线程的start()优先于它的每一个操作
- 线程的所有操作优先于线程的终结join()
- 对象的构造函数优先于finalize()函数
5.五种实现同步[通信]的机制
- wait()/notify() 方法
- await()/signal()方法
- BlockingQueue阻塞队列方法
- Semaphore信号量
- PipedInputStream和PipedOutputStream管道通信的方法
1.wait()/notify() 方法
package com.hqq.day21.communication.wait_notify.alternate;/** * Alternate * 交替运行线程 * Created by heqianqian on 2017/8/7. */public class Alternate { private volatile boolean isSolid; public synchronized void drawSolid() { try { for (int i = 0; i < 5; i++) { while (!isSolid) { this.wait(); } for (int j = 0; j < 5; j++) { System.out.println("★★★★★"); } isSolid = false; this.notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } public synchronized void solidHollow() { try { for (int j = 0; j < 5; j++) { while (isSolid) { this.wait(); } for (int i = 0; i < 5; i++) { System.out.println("☆☆☆☆☆"); } isSolid = true; this.notifyAll(); } } catch (Exception e) { e.printStackTrace(); } }}
package com.hqq.day21.communication.wait_notify.alternate;/** * App * Created by heqianqian on 2017/8/7. */public class App { public static void main(String[] args) { Alternate alternate = new Alternate(); new Thread(alternate::drawSolid).start(); new Thread(alternate::solidHollow).start(); }}
运行结果
☆☆☆☆☆☆☆☆☆☆★★★★★★★★★★☆☆☆☆☆☆☆☆☆☆★★★★★★★★★★
2.await()/signal()方法
await()/signal()方法和wait()/notify() 方法的区别:
- wait()/notify() 方法只能在synchronized同步代码块中使用,而await()/signal()方法一般是结合Lock使用
- wait()/notify() 是Object类的方法 所有类都有 而await()/signal()方法只有部分类采用 比如Condition
3.BlockingQueue阻塞队列
BlockingQueue内部是使用await()/signal()来实现的 用于阻塞的方法是put()和take()方法
当使用put()添加元素时 如果发现当前队列元素已经是最大时自动阻塞
当使用take()获取元素时 如果发现当前队列为空 会自动阻塞
package com.hqq.day15.blocking_queue;import java.util.concurrent.BlockingQueue;/** * Producer * 功能:从blockingqueue中放入数据 * Created by heqianqian on 2017/7/26. */public class Producer<T> implements Runnable { private BlockingQueue<T> blockingQueue; public Producer(BlockingQueue<T> blockingQueue) { this.blockingQueue = blockingQueue; } @Override @SuppressWarnings("unchecked") public void run() { try { blockingQueue.put((T) Integer.valueOf(1)); Thread.sleep(1000); blockingQueue.put((T) Integer.valueOf(2)); Thread.sleep(1000); blockingQueue.put((T) Integer.valueOf(3)); } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.hqq.day15.blocking_queue;import java.util.concurrent.BlockingQueue;/** * Customer * 功能:从blockingqueue中取数据 * Created by heqianqian on 2017/7/26. */public class Customer<T> implements Runnable { private BlockingQueue<T> blockingQueue; public Customer(BlockingQueue<T> blockingQueue) { this.blockingQueue = blockingQueue; } @Override public void run() { try { System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.hqq.day15.blocking_queue;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;/** * BlockingQueueExample * Created by heqianqian on 2017/7/26. */public class BlockingQueueExample { public static void main(String[] args) { BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(1024); Producer<Integer> producer = new Producer<>(blockingQueue); Customer<Integer> customer = new Customer<>(blockingQueue); new Thread(producer).start(); new Thread(customer).start(); }}
运行结果:
123
4. Semaphore 信号量
概念就不赘述了 这里说一下Semaphore和互斥量Mutex的区别,一般来说我们说互斥量是二元信号量 也就是Semaphore阈值为1的情况 但是二者还是存在一定的区别:
信号量在整个系统可以被任意线程获取并释放 同一个信号量可以被系统中的其他线程释放 而互斥量则要求哪个线程获取的就由哪个线程释放
例子:
package com.hqq.day15.semaphore;import java.util.concurrent.Semaphore;/** * CountLetterRunnable * 使用Semaphore进行线程间的通信 * Created by heqianqian on 2017/7/27. */public class CountLetterRunnable implements Runnable { private Semaphore semaphore; private int times = 0; public CountLetterRunnable(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { while (times < 50) { semaphore.acquire(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " print " + i+" for "+ times+" times"); } semaphore.release(); times++; } } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.hqq.day15.semaphore;import java.util.concurrent.Semaphore;/** * CountNumberRunnable * 使用Semaphore进行线程间的通信 * Created by heqianqian on 2017/7/27. */public class CountNumberRunnable implements Runnable { private Semaphore semaphore; private int times = 0; public CountNumberRunnable(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { while (times < 50) { semaphore.acquire(); for (int i = 'a'; i < 'z'; i++) { System.out.println(Thread.currentThread().getName() + " print " + (char) i + " for " + times + " times"); } semaphore.release(); times++; } } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.hqq.day15.semaphore;import java.util.concurrent.Semaphore;/** * SemaphoreRunnable * Created by heqianqian on 2017/7/27. */public class SemaphoreRunnable implements Runnable { private Semaphore semaphore; public SemaphoreRunnable(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " executed!"); Thread.sleep(2000); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.hqq.day15.semaphore;import java.util.concurrent.CountDownLatch;import java.util.concurrent.Semaphore;/** * SemaphoreExample * Created by heqianqian on 2017/7/27. */public class SemaphoreExample { public static void main(String[] args) { Semaphore semaphore = new Semaphore(1); //SemaphoreRunnable s1 = new SemaphoreRunnable(semaphore); //SemaphoreRunnable s2 = new SemaphoreRunnable(semaphore); // //new Thread(s1).start(); //new Thread(s2).start(); CountNumberRunnable num = new CountNumberRunnable(semaphore); CountLetterRunnable letter = new CountLetterRunnable(semaphore); new Thread(num).start(); new Thread(letter).start(); }}
5.管道通信
管道通信分为 PipedInputStream和PipedOutputStream 和PipedReader和PipedWriter两组 这里就只举PipedReader和PipedWriter的例子
package com.hqq.day21.communication.pipe.character;import java.io.IOException;import java.io.PipedReader;/** * ReaderThread * 使用字符管道读取数据 * Created by heqianqian on 2017/8/7. */public class ReaderThread extends Thread { private PipedReader reader = new PipedReader(); public ReaderThread(PipedReader reader) { this.reader = reader; } public void readData() { char[] chars = new char[1024]; try { reader.read(chars); String data = new String(chars); System.out.println(Thread.currentThread().getName() + " Read[" + data + "]"); reader.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { readData(); }}
package com.hqq.day21.communication.pipe.character;import java.io.IOException;import java.io.PipedWriter;/** * WriterThread * 使用字符管道写数据 * Created by heqianqian on 2017/8/7. */public class WriterThread extends Thread { private PipedWriter pipedWriter; public WriterThread(PipedWriter pipedWriter) { this.pipedWriter = pipedWriter; } public void writeData() { String data = "你好"; try { pipedWriter.write(data); System.out.println(Thread.currentThread().getName() + " Write [" + data + "]"); pipedWriter.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { writeData(); }}
package com.hqq.day21.communication.pipe;import com.hqq.day21.communication.pipe.character.ReaderThread;import com.hqq.day21.communication.pipe.character.WriterThread;import com.hqq.day21.communication.pipe.stream.ReadStreamThread;import com.hqq.day21.communication.pipe.stream.WriteStreamThread;import java.io.*;/** * App * Created by heqianqian on 2017/8/7. */public class App { public static void main(String[] args) throws IOException, InterruptedException { //Byte Stream PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(); pis.connect(pos); ReadStreamThread readStreamThread = new ReadStreamThread(pis); WriteStreamThread writeStreamThread = new WriteStreamThread(pos); writeStreamThread.start(); readStreamThread.start(); //Character Stream PipedReader reader = new PipedReader(); PipedWriter writer = new PipedWriter(); reader.connect(writer); ReaderThread readerThread = new ReaderThread(reader); WriterThread writerThread = new WriterThread(writer); writerThread.start(); readerThread.start(); }}
运行结果
Thread-3 Write [你好]Thread-2 Read[你好]
6.读写锁问题
共享数据时要求:
- 允许多个读操作同时进行
- 读操作和写操作不可同时进行
- 写操作和写操作不可同时进行
读写锁ReentrantReadWriteLock
具有特性:
a) 可重入性:内部的WriteLock可以获取ReadLock 反之不成立
b) 可降级:WriteLock可以降级成ReadLock 反之不成立
c) WriteLock支持Condition 而ReadLock不支持 使用时会抛出UnSupportedOperationException
六. 无锁类
实现机制CAS(CompareAndSet) 使用CPU指令原语cmpXchg实现
- 1.AtomicInteger
- 2.Unsafe
- 3.AtomicReference
模板类 封装任意类型数据 - 4.AtomicStampedReference
内部Pair封装了值和时间戳 用时间戳来标识每次改变 - 5.AtomicIntegerArray
内部封装了整型数组 - 6.AtomicIntegerFieldUpdater
作用:让普通变量也具有原子性
七. 并发包
//TODO 待补充 TAT
1.ReentrantLock 完全互斥锁
使用Condition实现等待通知[Condition 是jdk5出现的技术 可以实现多路通知功能]
[多路通知:在一个Lock对象里可以创建多个Condition(对象监视器)实例 线程对象可以注册在指定的Condition中 从而可以有选择的进行线程通知 在调度线程上更加灵活]
wait/notify调度的线程是由JVM随机通知的 而ReentrantLock+Condition可以有选择性的通知
- Object的wait()方法相当于Condition的await()方法
- Object的wait(long)方法相当于Condition的await(long,TimeUnit)方- 法
- Object的notify()方法相当于Condition的signal()方法
- Object的notifyAll()方法相当于Condition的signalAll()方法
公平锁和非公平锁;
- 公平锁:线程获取锁的顺序是按照加锁的顺序来分配的[FIFO]
- 非公平锁:线程获取锁的顺序是抢占机制,随机获得锁的
其他方法:
- getHoldCount():查询当前线程保持此锁定的个数 调用lock()方法的次数
- getQueueLength():返回正等待获取此锁定的线程估计数
- getWaitQueueLength(Condition):返回等待与此锁定相关给定条件Condition的线程估计数
比如有5个线程 每个线程都调用了Condition的await() 那么getWaitQueueLength()返回的就是5 - boolean hasQueuedThread(Thread):查询指定的线程是否正在等待获取此锁定
- boolean hasQueuedThreads():查询是否有线程正在等待此锁定
- boolean hasWaiters(Condition):查询是否有线程正在等待和此锁定关的condition的条件
- isFair():公平锁 [ReentrantLock默认是非公平锁]
- isHeldByCurrentThread():查询当前线程是否保持此锁定
- isLocked():查询此锁定是否由任意线程保持
- void lockInterruptibly():如果当前线程未被中断.则获取锁定.如果已经被中断则抛出异常
- void tryLock():仅在调用时锁定未被另一个线程保持的情况下才获取该锁定
- void tryLock(Long,TimeUnit):如果锁定在给定的等待时间内没有被另一个线程保持,且当前线程未被中断.则获取该锁定
- void awaitUninterruptibly():线程等待的时候可以不被打断
- void awaitUntil():线程在等待时间到达之前 可以被其他线程提前唤醒
[ReentrantReadWriteLock]:读写互斥锁 读和读之间不互斥 提高代码运行速度
和读操作有关的锁:共享锁 和写操作有关的锁:排他锁
具有特性:
a) 可重入性b) 可中断性c) 可限时性d) 公平性
2.Timer 定时器
可以用于安卓中的轮询动画
作用:主要负责计划任务的功能[在指定的时间开始执行某一个任务]
主要负责设置计划任务 封装任务的类是TimerTask类
Timer:
- schedule(TimerTsk,Date):在指定的日期执行一次某任务
- schedule(TimerTsk,long):延迟long毫秒后执行任务
- schedule(TimerTsk,long,long):long):毫秒之后按照指定间隔无线循环的执行某一任务
- cancel():将任务队列中的全部队列清除
- scheduleAtFixedRate(TimerTask,Date,long):和schedule的区别只在于不延迟的情况
不延迟的情况下:
schedule:如果执行任务的时间没有被延迟,下一次任务的执行时间参考的是上一次任务[开始]时间
scheduleAtFixedRate:如果执行任务的时间没有被延迟,下一次任务的执行时间参考的是上一次任务[结束]时间
追赶执行性
schedule:不具有 不执行scheduleAtFixedRate:具有 追赶执行
TimerTask:
cancel():把自身从任务队列中清除
package com.hqq.day21.timer;import java.text.SimpleDateFormat;import java.util.Timer;import java.util.Date;import java.util.TimerTask;/** * TimerExample * Created by heqianqian on 2017/8/8. */public class TimerExample { private static Timer timer = new Timer(false);//设置为守护进程后 task内的任务也不再执行 public static void main(String[] args) throws InterruptedException { System.out.println("当前时间:"+System.currentTimeMillis()); TimerTask timerTask1 = new TimerTask() { @Override public void run() { System.out.println("运行了,时间为" + System.currentTimeMillis()); } }; TimerTask timerTask2 = new TimerTask() { @Override public void run() { System.out.println("我也运行了,时间为" + System.currentTimeMillis()); } }; TimerTask recycleTask = new TimerTask() { @Override public void run() { System.out.println("间隔周期执行:"+System.currentTimeMillis()); } }; TimerTask cancelTask = new TimerTask() { @Override public void run() { System.out.println("任务执行!"); this.cancel(); } }; //timer.schedule(timerTask1, 2000);//delay大于0 延迟delay执行 如果delay的时间早于当前时间 则立即执行 //timer.schedule(timerTask2,100); //间隔周期执行 //timer.schedule(recycleTask,1000,1000); //测试TimerTask的cancel()方法 把自身从任务队列中清除 timer.schedule(cancelTask,1000,1000); }}
运行结果:
当前时间:1502584381969任务执行!
3.CountDownLatch 倒数计时器
计数器的初始值为线程的数量,每当一个线程完成了自己的任务,计数器的值就会减1,当计数器的值达到0时,它表示所有的线程都已经完成了任务 然后在闭锁上等待的线程就可以恢复执行任务。
例子:
package com.hqq.day15.countdown_latch;import java.util.concurrent.CountDownLatch;/** * Decrementer * Created by heqianqian on 2017/7/26. */public class Decrementer implements Runnable { private CountDownLatch countDownLatch; public Decrementer(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { countDownLatch.countDown(); System.out.println("Count Down"); Thread.sleep(1000); countDownLatch.countDown(); System.out.println("Count Down"); Thread.sleep(1000); countDownLatch.countDown(); System.out.println("Count Down"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }}
package com.hqq.day15.countdown_latch;import java.util.concurrent.CountDownLatch;/** * Waiter * Created by heqianqian on 2017/7/26. */public class Waiter implements Runnable{ private CountDownLatch countDownLatch; public Waiter(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("CountDownLatch Finish!"); }}
package com.hqq.day15.countdown_latch;import java.util.concurrent.CountDownLatch;/** * CountDownLatchDemo * Created by heqianqian on 2017/7/26. */public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(3); Waiter waiter = new Waiter(countDownLatch); Decrementer decrementer = new Decrementer(countDownLatch); new Thread(waiter).start(); new Thread(decrementer).start(); }}
运行结果:
Count DownCount DownCount DownCountDownLatch Finish!
4. CyclicBarrier
初始时规定一个数目,然后计算调用CyclicBarrier.await()进入等待的线程数,当线程数达到了这个数目 所有等待的线程被唤醒并继续
所有的线程必须到齐后才可以一起继续运行
初始时可以带一个Runnable参数,在线程数达到该数目其他线程被唤醒之前至执行。
例子:
package com.hqq.day15.cyclic_barrier;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;/** * CyclicBarrierRunnable * Created by heqianqian on 2017/7/27. */public class CyclicBarrierRunnable implements Runnable { private CyclicBarrier cyclicBarrier1; private CyclicBarrier cyclicBarrier2; public CyclicBarrierRunnable(CyclicBarrier cyclicBarrier1, CyclicBarrier cyclicBarrier2) { this.cyclicBarrier1 = cyclicBarrier1; this.cyclicBarrier2 = cyclicBarrier2; } @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " awaiting at barrier1"); cyclicBarrier1.await(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " awaiting at barrier2"); cyclicBarrier2.await(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " done!"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }}
package com.hqq.day15.cyclic_barrier;import java.util.concurrent.CyclicBarrier;/** * CyclicBarrierExample * Created by heqianqian on 2017/7/27. */public class CyclicBarrierExample { public static void main(String[] args) { Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Barrier1 executed!"); } }; Runnable r2 = new Runnable() { @Override public void run() { System.out.println("Barrier2 executed!"); } }; CyclicBarrier c1 = new CyclicBarrier(2,r1); CyclicBarrier c2 = new CyclicBarrier(2,r2); CyclicBarrierRunnable cbr1 = new CyclicBarrierRunnable(c1,c2); CyclicBarrierRunnable cbr2 = new CyclicBarrierRunnable(c1,c2); new Thread(cbr1).start(); new Thread(cbr2).start(); }}
运行结果:
Thread-0 awaiting at barrier1Thread-1 awaiting at barrier1Barrier1 executed!Thread-0 awaiting at barrier2Thread-1 awaiting at barrier2Barrier2 executed!Thread-1 done!Thread-0 done!
和CountDownLatch的作用类似 区别是CountDownLatch倒数到0之后不可以继续使用 而CyclicBarrier可以继续使用
八. 线程池
//TODO 待补充 QAQ
1. 线程池的种类
- new FixedThreadPool: 固定数量的线程池
- new SingleThreadExecutor:单一线程池
- new CachedThreadPool:缓存线程池 根据需求改变大小
- new ScheduledThreadPool:任务调度线程池
2.Fork/Join 分治思想
fork/join 类似 Map/Reduce算法
区别是fork/join只有的必要的时候才把任务分割成一个个的小任务,而map/reduce总是在一开始就执行第一步分割。因此fork/join适合JVM内线程级别 而map/reduce适合分布式系统
package com.hqq.day15.fork_join_pool;import java.util.ArrayList;import java.util.List;import java.util.concurrent.RecursiveAction;/** * MyRecursiveAction * Created by heqianqian on 2017/7/27. */public class MyRecursiveAction extends RecursiveAction { private int workLoad = 0; public MyRecursiveAction(int workLoad) { this.workLoad = workLoad; } @Override protected void compute() { if (workLoad > 20) { System.out.println("Split WorkLoad : " + this.workLoad); List<MyRecursiveAction> actions = new ArrayList<>(); actions.addAll(createSubTask()); for (MyRecursiveAction action : actions) { action.fork(); } } else { System.out.println("Finish Task By MySelf " + workLoad); } } public List<MyRecursiveAction> createSubTask() { List<MyRecursiveAction> actions = new ArrayList<>(); MyRecursiveAction action1 = new MyRecursiveAction(this.workLoad / 2); MyRecursiveAction action2 = new MyRecursiveAction(this.workLoad / 2); actions.add(action1); actions.add(action2); return actions; }}
package com.hqq.day15.fork_join_pool;import java.util.concurrent.ForkJoinPool;/** * ForkJoinPoolExample * Created by heqianqian on 2017/7/27. */public class ForkJoinPoolExample { public static void main(String[] args) { //1.使用无返回的RecursiveAction ForkJoinPool forkJoinPool = new ForkJoinPool(); MyRecursiveAction action = new MyRecursiveAction(100); forkJoinPool.invoke(action); }}
执行结果:
Split WorkLoad : 100Split WorkLoad : 50Split WorkLoad : 50Split WorkLoad : 25
九. 锁优化
几种优化机制
- a) 减少锁持有的时间
- b) 减少锁粒度
- c) 锁分离
- d) 锁粗化
- e)锁消除
1.减少锁持有的时间
减少其他线程等待的时间 只在需要线程安全的代码上加锁
2.减少锁粒度
将大对象拆分成小对象 对每个小对象加锁 降低锁竞争 最典型的例子就是ConcurrentHashMap的分段锁 只在数据所在的JDK7中是Segment,JDK8中是Node上加锁
3.锁分离
最典型的例子是读写锁ReadWriteLock 实现读操作和写操作分离
4.锁粗化
如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部,这样就只需要加锁一次就够了
例子:
第一个例子
for (int i = 0; i < 10; i++) { synchronized (lock){ ... }}
会优化成
synchronized (lock){ for (int i = 0; i < 10; i++) { ... } }
第二个例子
synchronized (lock1){ //...} //..。无需同步但很快能执行完的代码synchronized (lock2){ //...}
会优化成
synchronized (lock1){ //...}
5.锁消除
编译器级别的优化
在JIT中 发现不可能有共享的对象会消除他们的锁
常见情况是JDK中自带锁机制的对象如Vector和StringBuffer,当编译的时候发现这些对象没有处于线程不安全的状态 会消除他们的锁操作
package com.hqq.day26.lock_elimate;/** * EliminateLockDemo * 测试锁消除机制 * Created by heqianqian on 2017/8/13. */public class EliminateLockDemo { public static void main(String[] args) { long startTime = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { concatString("Let it"," Crash!"); } System.out.println("Cost "+(System.currentTimeMillis()-startTime)+" mills"); } private static void concatString(String str1,String str2){ StringBuffer buffer = new StringBuffer(); buffer.append(str1); buffer.append(str2); System.out.println(buffer.toString()); }}
不是锁消除的情况下运行结果:
...Cost 15 mills
使用-XX:+DoEscapeAnalysis -XX:+EliminateLocks
打开锁消除
...Cost 0 mills
虚拟机内的锁优化
对象头的概念 Mark Word
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。JVM 对象头一般占用两个机器码,在 32-bit JVM 上占用 64bit, 在 64-bit JVM 上占用 128bit 即 16 bytes(暂不考虑开启压缩指针的场景)。另外,如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java 对象的元数据信息确定 Java 对象的大小,但是从数组的元数据中无法确定数组的大小。
1.偏向锁–偏向当前已经持有锁的线程
在无竞争的情况下,之前获得锁的线程再次请求锁时,那么该线程不用再次获得锁就可直接进入同步块 当其他线程请求锁时 偏向结束
JVM默认启用偏向锁
2.轻量级锁—减少线程互斥的几率
利用CPU的原语CAS 在线程进入互斥之前进行补救
轻量级锁失败会转成重量级锁 即使用操作系统层面的互斥 也有可能尝试自旋锁
3.自旋锁 – 不断尝试请求获取锁
当请求的锁被其他线程占有时 当前线程不会挂起 而是会不断尝试请求获取锁
总结:
偏向锁:避免某个线程反复获取/释放同一把锁的资源消耗
而轻量级锁和自旋锁都是为了防止调用操作系统层面的互斥
- JAVA 并发编程学习总结
- Java并发编程类学习总结
- 并发学习->《Java并发编程的艺术》->重点摘录总结
- 并发编程学习总结
- 并发编程学习总结
- 并发编程学习总结
- Java并发编程总结
- java并发编程总结
- Java并发编程总结
- Java并发编程-总结
- Java并发编程总结
- Java并发编程总结
- Java并发编程总结
- Java并发编程-并发编程知识点总结
- Java并发编程-并发编程知识点总结
- java并发编程学习之synchronized学习总结
- Java并发编程学习
- java并发编程学习总结(基础篇)
- 模仿QQ,实现列表简单折叠
- 第一个网站搭建过程额(1)
- 2018网易内推测试工程师选择题
- java语言基础——实现int类型数组元素拷贝
- 8.12
- JAVA 并发编程学习总结
- 2-4端口\富规则firewall
- css重点
- Tunnel Warfare HDU
- 生产者与消费者模型
- codeup 1817 A+B
- 浅拷贝和深拷贝
- HTTP相关知识
- 前端工程化初识