并发学习
四个阶段
#1 熟练掌握API,能够完成并发变成
#2 熟读API源码,掌握其原理
#3 理解java虚拟机的内存模型
#4 操作系统对并发的支持
并发的优点:
发挥出多处理器的强大能力
建模的简单性
异步事件的简化处理
响应更加灵敏的用户界面
并发的缺点:
安全性的问题
活跃性问题(饥饿,死锁等问题)
性能问题
进程与线程
进程是资源分配的基本单位
进程中包含多个线程,线程共享进程的资源
线程是处理器调度的基本单位
线程的状态
初始化
就绪
运行
终止
等待
超时等待
阻塞
线程创建的方式
继承Thread 类
实现Runnable类,这回作为线程任务存在
匿名内部类(只使用一次的场景下)
带返回值的线程Callable FutureTask
使用定时器 Timer Quartz
线程池的实现Executor(一个池子装了很多的线程,在使用线程的时候,从池子里获取,用完再还给池子,降低线程创建和销毁的时间)
Lambda表达式实现
Springh实现
线程带来的风险
线程安全性的问题
活跃性问题
性能问题
活跃性问题
死锁
饥饿(线程的优先级)
活锁
性能问题
多线程不一定快
多线程不一定运行在多核服务器上
CPU会分配给各个线程时间(时间片),这个时间非常短,当这个任务在时间片中执行完之后,会执行下一个任务
那在任务切换的过程中(上下文切换),要保存当前任务的执行状态,以便下一次继续执行时可以保留状态
因此上下文切换时是非常消耗资源的
饥饿与公平
高优先级吞噬所有低优先级的CPU时间片
线程被永久堵塞在一个等待进入同步块的状态
等待的线程永远不被唤醒
如何尽量避免饥饿问题
合理设置线程优先级
使用用锁来代替synchronized
线程安全性问题
多线程环境
多个线程共享一个资源
对线程进行非原子性操作
synchronized
让其线程同步,当一个线程进来之后,会锁上,其他的线程进不来,也就是同一时刻,只有一个线程在执行
synchronized
可以修饰普通方法(内置锁为当前类的实例)
public synchronized int getNext(){
return value++;
}
可以修饰静态方法(内置锁为当前的Class字节码对象,也就是 X.class)
public static synchronized int getPrevious(){
return value–;
}
修饰代码块
synchronized (this){
if(value > 0){
return value;
}else{
return -1;
}
}
synchronized 原理与使用
内置锁:在java中,每一个对象都可以用作同步的锁,这些锁就被称之为内置锁
锁是互斥的(互斥锁)
synchronized 放在普通方法上,内置锁就是当前的实例
线程在进入同步代码块时必须现获得内置锁,才可以执行方法,此时,其他的线程无法获得锁,只有等当前线程释放该内置锁,其余线程才可以进入,使得整个方法变成原子性操作
任何对象都可以作为锁,那么锁信息又存在对象的什么地方
存在对象头中
对象头中的信息
java虚拟机会开通2个字节的存储空间来存储对象头
Mark Word(存对象的hash值,锁信息等)
Class Metadata Address
Array Length(数组特有)
jdk1.6 之前,synchrinized 是重量级锁,但在1.6之后引入了如下的锁
偏向锁:偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
每次获取锁和释放锁会浪费资源
很多情况下,进竞争锁不是由多个线程,而是由一个线程在使用,如果每次都获取释放锁,就会很耗费资源
在偏向锁状态下,Mark Word 中的内容为 锁标志位(是否是偏向锁),线程id,Epoch,对象的分代年龄信息
偏向锁会在其他线程尝试竞争该偏向锁时,持有该偏向锁的线程才会尝试释放
因此只有一个线程在访问同步代码块的时候,使用偏向锁会提高性能
轻量级锁
可以让多个线程都可以进入到同步代码块
jvm会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的Mark Word 复制到锁记录中,这之后开始竞争锁
当竞争成功之后, 会把Mark Word锁标志位改成轻量级锁,然后执行同步块代码,另外的线程则等待。
而别的线程也需要复制Mark Word到虚拟机栈中,但发现该Mark Word已经被改变了,无法修改,则会不停的获取修改,直到获取到锁(自旋锁)
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,
还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
单例模式
饿汉式 - 不存在线程安全问题
public class Singleton {
// 私有化构造方法
private Singleton(){}
// 饿汉式
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
懒汉式 - 存在线程安全问题
public class Singleton2{
private Singleton2(){}
private static Singleton2 instance;
public static synchronized Singleton2 getInstance(){
// 轻量级锁,自旋,是会浪费CPU资源的
// 所以在这边加了 synchronized 其实无法提高性能
if(instance == null){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton2();
}
return instance;
}
}
public class Singleton2{
private Singleton2(){}
private static volatile Singleton2 instance;
public static synchronized Singleton2 getInstance2(){
// 双重检查加锁制
if(instance == null){
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2(); // 指令重排序,为了提高执行的性能,
// 会对指令进行优化,可能会把后面的代码放到前面执行,
// 因此这代码其实还是线程不安全的
// 首先申请一片内存空间 1
// 在这块空间里面实例化对象 2
// instance 的引用指向这一块空间地址 3
// 可能执行的顺序是 1 2 3, 也可能是 1 3 2
// 因此 instance 不一定会是null
// 因此,需要使用volatile,可以减少虚拟机优化,可以减少指令重排序
// 避免出现指令重排序
}
}
}
return instance;
}
}
重入锁(锁重入)
synchrinized 就是一个重入锁
两个方法都用同一个对象去锁,第一个线程获取了锁,这个线程再去获取同一把锁时,可以直接获取
自旋锁
不停的旋转CPU的时间片(空转CPU),一直等待其余线程结束
死锁
当一个线程永久的持有一把锁,但其他线程尝试获取这把锁,这就造成了死锁
public class Demo3 {
private Object obj1 = new Object();
private Object obj2 = new Object();
public void a(){
synchronized (obj1){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("a");
}
}
}
public void b(){
synchronized (obj2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("b");
}
}
}
public static void main(String[] args) {
Demo3 d = new Demo3();
new Thread(new Runnable() {
@Override
public void run() {
d.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
d.b();
}
}).start();
}
}
volatile
volatile 称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的
可见:一个线程修改了,在另外一个线程中能够读到修改后的值
Synchronized 除了线程之间互斥之外,还有一个非常大的作用,就是保证可见性
volatile 使用例子
public class Demo2 {
public volatile boolean run = false;
public static void main(String[] args) {
Demo2 d = new Demo2();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10; i++){
System.out.println("执行了第 " + i + "次");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
d.run = true;
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(!d.run){
// 不执行
}
System.out.println("线程2执行了。。。");
}
}).start();
}
}
volatile 底层原理
深入到汇编语言,加了Lock指令
Lock指令
在多处理器的系统上,将当前处理器缓存行的内容写回到系统内存(而不是保存在CPU的缓存中)
这个写回到内存的操作会使在其他CPU里缓存了该内存地址的数据失效
因此在一个方法中如果大量使用volatile, 那么CPU的缓存则使用不上,性能会大大降低。
而volatile 还会阻止虚拟机的指令重排序,不做执行优化,这也会影响一定的性能
有了volatile 为什么还要有synchnized?
有了volatile,只能保证变量的可见性,但并不能保证变量的原子性
有了synchnized,为什么还要有volatile?
volatile 更加的轻量化
JDK 原子类的原理以及使用
原子更新基本类型
private AtomicInteger value = new AtomicInteger(0);
public int getNext(){
return value.getAndIncrement();
}
原子更新数组
private int[] s = {2,1,4,6};
AtomicIntegerArray a = new AtomicIntegerArray(s);
public int getNext2(){
a.getAndIncrement(1);
return a.getAndAdd(2, 10);
}
原子更新抽象类型
AtomicReference<User> user = new AtomicReference<>();
原子更新字段
AtomicIntegerFieldUpdater<User> old = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
实现原理
我们来分析下incrementAndGet的逻辑:
1.先获取当前的value值
2.对value加一
3.第三步是关键步骤,调用compareAndSet方法来来进行原子更新操作,这个方法的语义是:
先检查当前value是否等于current,如果相等,则意味着value没被其他线程修改过,更新并返回true。如果不相等,compareAndSet则会返回false,然后循环继续尝试更新。
这是基于CPU的CAS指令来完成的。CAS算法是由硬件直接支持来保证原子性的,有三个操作数:内存位置V、旧的预期值A和新值B,当且仅当V符合预期值A时,CAS用新值B原子化地更新V的值,否则,它什么都不做。
CAS存在ABA的问题,但其实不太会影响并发的准确性
解决线程安全问题的方法
Synchronized
Volatile
AtomicInteger 等原子类
Lock
Lock接口的认识与使用
Lock 的使用
public class Sequence {
private int value;
Lock lock = new ReentrantLock();
public int getNext(){
lock.lock();
int a = value++;
lock.unlock();
return a;
}
}
使用锁的好处
Lock需要显示地获取和释放锁,可以让我们的代码更加的灵活。而synchrinized不需要显示的获取和释放锁。
使用Lock可以方便的实现公平性
可以非阻塞的获取锁
能被中断的获取锁
可以超时获取锁
手动写一个可重入锁
public class MyLock implements Lock {
private boolean isLocked = false;
private int lockCount = 0;
Thread lockBy = null;
@Override
public synchronized void lock() {
Thread currentThread = Thread.currentThread();
while (isLocked && currentThread != lockBy){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isLocked = true;
lockBy = currentThread;
lockCount++;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public synchronized void unlock() {
if(lockBy == Thread.currentThread()){
lockCount--;
if(lockCount == 0){
isLocked = false;
notify();
}
}
}
@Override
public Condition newCondition() {
return null;
}
}
公平锁
公平锁是针对锁的获取而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求顺序的绝对时间顺序
读写锁
排它锁 与 共享锁
排它锁:在同一时刻,只允许一个线程访问
共享锁:在同一时刻,允许多个线程访问,类似于读写锁,在同个时刻,允许多个读线程进行访问
读与读 不互斥
读与写 互斥
写与写 互斥
读写锁需要保存的状态
写锁冲入的次数
读锁的个数
每个读锁重入的次数
锁降级
锁降级是指写锁降级为读锁
private volatile boolean isUpdate;
public void readWrite(){
// 要读isUpdate,此处要加读锁,保证isUpdate可以拿到最新的值
r.lock();
if(isUpdate){
r.unlock(); // 因为与读锁互斥,所以此处释放
w.lock();
map.put("xxx", "xxx");
r.lock(); // 锁的降级
w.unlock();
}
Object obj = map.get("xxx");
System.out.println(obj);
r.unlock();
}
具体操作,在写锁没有释放的时候,获取到读锁,再释放写锁
锁升级
锁升级是值读锁升级为写锁
具体操作,在读锁没有释放的时候,获取到写锁,再释放读锁
线程安全性问题简单总结
出现线程安全性问题的条件
1 在多线程的环境下
2 必须有共享资源
3 对共享资源进行非原子性操作
解决线程安全性问题的途径
1 synchronized (性能上相对比较慢,jdk6之后对其进行了优化,性能相对得到了提升)
(偏向锁,轻量级锁,重量级索)
2 volatile (线程可见,但不能保证原子性操作)
3 JDK 提供的原子类
4 试用Lock(共享锁,排它锁...)
认识的 “*锁”
偏向锁
轻量级锁
重量级锁
重入锁
自旋锁
共享锁
独占锁
排它锁
读写锁
非公平锁
公平锁
死锁
活锁
乐观锁
悲观锁
线程之间的通信
volatile
wait()
notify()
notifyAll()
wait 和 notify 需要在同步代码块中执行
wait 和 notify 调用的是当前锁的实例的 wait 和notify方法
在使用wait的时候,会释放锁,但线程依旧是处于等待状态
notify方法会随机叫醒一个wait状态的线程
notifyAll 方法会叫醒所有处于wait状态的线程,争夺时间片的线程只有一个
调用notify¬ifyAll方法会执行锁,但是要等到该notify/notifyAll方法所在的synchronized释放锁之后才可以获取到锁
生产者消费者模式
public class Tmall {
private int count;
public final int MAX_COUNT = 10;
public synchronized void push(){
while(count >= MAX_COUNT){
try{
System.out.println(Thread.currentThread().getName() + “库存数量达到上限,生产者停止生产”);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + “生产者生产,当前库存为: " + count);
notifyAll();
}
public synchronized void take(){
while(count <= 0){
try {
System.out.println(Thread.currentThread().getName() + " 库存数量为零, 消费者等待…”);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count–;
System.out.println(Thread.currentThread().getName() + " 消费者消费, 当前库存为: " + count);
notifyAll();
}
}
public class PushTarget implements Runnable{
private Tmall tmall;
public PushTarget(Tmall tmall){
this.tmall = tmall;
}
@Override
public void run() {
while (true){
tmall.push();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TakeTarget implements Runnable {
private Tmall tmall;
public TakeTarget(Tmall tmall){
this.tmall = tmall;
}
@Override
public void run() {
while (true){
tmall.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Tmall tmall = new Tmall();
PushTarget p = new PushTarget(tmall);
TakeTarget t = new TakeTarget(tmall);
new Thread(p).start();
new Thread(p).start();
new Thread(p).start();
new Thread(p).start();
new Thread(p).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
Condition
await()
signal()
可指定唤醒的线程,而object的wait 和 notify方法,无法指定需要唤醒的线程
实现一个有界的队列
public class MyQueue {
private Object[] obj;
private int addIndex;
private int removeIndex;
private int queueSize;
Lock lock = new ReentrantLock();
Condition addCondition = lock.newCondition();
Condition removeCondition = lock.newCondition();
public MyQueue(int count){
obj = new Object[count];
}
public void add(E e) {
lock.lock();
while (queueSize == obj.length){
try {
System.out.println(Thread.currentThread().getName() + "队列满了");
addCondition.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
obj[addIndex] = e;
if(++addIndex == obj.length){
addIndex = 0;
}
queueSize++;
removeCondition.signal();
lock.unlock();
}
public void remove(Object o) {
lock.lock();
while(queueSize == 0){
try {
System.out.println(Thread.currentThread().getName() + "队列为空,不进行移除");
removeCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
obj[removeIndex] = null;
if(++removeIndex == obj.length){
removeIndex = 0;
}
queueSize--;
addCondition.signal();
lock.unlock();
}
}
Condition
同步队列
等待队列
线程之间的通信 join
join() 当一个线程在执行过程中需要调用另一个线程,这个时候可以使用join()
ThreadLocal 原理与使用
public class Demo {
private ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public int getNext(){
Integer value = count.get();
value++;
count.set(value);
return value;
}
public static void main(String[] args) {
Demo d = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName() + " " + d.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName() + " " + d.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName() + " " + d.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName() + " " + d.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
线程之间的通信:CountDownLatch
对一个文本中所有的数字并行求和
public class Demo2 {
private int[] nums;
public Demo2(int line){
nums = new int[line];
}
public void calc(String line, int index, CountDownLatch latch){
String[] nus = line.split(","); // 切分出每个值
int total = 0;
for(String num : nus){
total = total + Integer.valueOf(num);
}
nums[index] = total; // 把计算结果方法数组中指定的位置
System.out.println(Thread.currentThread().getName() + " 开始执行计算任务..." + line + " 结果为 " + total);
latch.countDown();
}
public void sum(){
System.out.println("汇总线程开始执行。。。");
int total = 0;
for(int i = 0; i < nums.length; i++){
total += nums[i];
}
System.out.println("最终的结果是: " + total);
}
public static void main(String[] args) {
List<String> contents = new ArrayList<>();
contents.add("1,2,3,4,5,6,7,8,9,10");
contents.add("11,12,13,14,15,16,17,18,19,20");
contents.add("21,22,23,24,25,26,27,28,29,30");
int lineCount = contents.size();
Demo2 d = new Demo2(lineCount);
CountDownLatch latch = new CountDownLatch(lineCount);
for(int i = 0; i < lineCount; i++){
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
d.calc(contents.get(j), j, latch);
}
}).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
d.sum();
}
}
线程间的通信 - CyclicBarrier
public class Demo {
Random random = new Random();
public void meeting(CyclicBarrier barrier){
try {
Thread.sleep(random.nextInt(4000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 到达会议室, 等待开会...");
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "发言");
}
public static void main(String[] args) {
Demo demo = new Demo();
CyclicBarrier barrier = new CyclicBarrier(10, new Runnable() {
@Override
public void run() {
System.out.println("好,我们开始开户...");
}
});
for(int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
demo.meeting(barrier);
}
}).start();
}
}
}
线程间的通信 - Semaphore
public class Demo {
public void method(Semaphore semaphore){
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is running....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();
}
public static void main(String[] args) {
Demo d = new Demo();
Semaphore semaphore = new Semaphore(10);
while(true){
new Thread(new Runnable() {
@Override
public void run() {
d.method(semaphore);
}
}).start();
}
}
}
线程间的通信 - Exchanger
public class Demo {
public void a(Exchanger<String> exchanger){
System.out.println("a 方法执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String res = "12345";
try {
System.out.println("等待对比结果...");
String value = exchanger.exchange(res);
System.out.println("开始进行比对...");
System.out.println("对比结果为: " + value.equals(res));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void b(Exchanger<String> exchanger){
System.out.println("b 方法开始执行...");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String res = "12345";
try {
String value = exchanger.exchange(res);
System.out.println("开始进行比对...");
System.out.println("比对结果为: " + value.equals(res));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo d = new Demo();
Exchanger<String> exch = new Exchanger<String>();
new Thread(new Runnable() {
@Override
public void run() {
d.a(exch);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
d.b(exch);
}
}).start();
}
}
提前完成任务之Future使用
public class Dmeo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> call = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("正在计算结果....");
Thread.sleep(3000);
return 1;
}
} ;
FutureTask<Integer> task = new FutureTask<Integer>(call);
Thread thread = new Thread(task);
thread.start();
// do something
System.out.println("干点别的...");
Integer result = task.get();
System.out.println("拿到的结果是....");
}
}
自己实现一个future
以买蛋糕为例,我去买蛋糕,蛋糕生产,我去上班,下班之后来取蛋糕
public class ProductFactory {
public Future createProduct(String name){
System.out.println("下单成功,你可以去上班了...");
Future f = new Future(); // 创建一个订单
// 生产产品
new Thread(new Runnable() {
@Override
public void run() {
Product p = new Product(new Random().nextInt(), name);
f.setProduct(p);
}
}).start();
return f;
}
}
public class Product {
private int id;
private String name;
public Product(int id, String name) {
try {
System.out.println("开始生产: " + name);
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.id = id;
this.name = name;
System.out.println(name + " 生产完毕");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Future {
private Product product;
private boolean done;
public synchronized void setProduct(Product product){
if(done){
return;
}
this.product = product;
this.done = true;
notifyAll();
}
public synchronized Product get(){
while(!done){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return product;
}
}
public class Demo {
public static void main(String[] args) {
ProductFactory pf = new ProductFactory();
Future f = pf.createProduct("蛋糕");
System.out.println("我去上班了,下了班我来取蛋糕...");
// 拿着订单获取产品
System.out.println("我拿着蛋糕回家..." + f.get());
}
}
Future API 以及应用场景
Callable call = new Callable() {
@Override
public Integer call() throws Exception {
System.out.println(“正在计算结果…”);
Thread.sleep(3000);
return 1;
}
} ;
FutureTask task = new FutureTask(call);
Thread thread = new Thread(task);
thread.start();
// do something
System.out.println("干点别的...");
Integer result = task.get();
System.out.println("拿到的结果是....");
Callable和Runnable的区别
Runnable 是被线程调用的,在run方法是异步执行的
Callable的call方法,不是异步执行的,是由Future的run方法调用的
Fork/Join 框架
多线程的目的不仅仅是提高程序运行的性能,还可以充分利用CPU资源
用Fork、Join 思想求 1 - 100 的和,第一个线程 1-50, 第二个线程51-100, 再继续分割, 一个线程 1 - 25, 另一个 26- 50,一次类推,一直在分割成50个线程为止
public class Demo extends RecursiveTask {
private int begin;
private int end;
public Demo(int begin, int end){
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
// 拆分任务
if(end - begin <= 2){
// 计算
for(int i = begin; i <= end; i++){
sum += i;
}
}else{
// 拆分
Demo d1 = new Demo(begin, (begin + end) / 2);
Demo d2 = new Demo((begin + end)/2 + 1, end);
// 执行任务
d1.fork();
d2.fork();
Integer a = d1.join();
Integer b = d2.join();
sum = a + b;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
Future<Integer> future = pool.submit(new Demo(1, 1000));
System.out.println(",,,,,,,,,,,,,");
System.out.println("计算的值为: " + future.get());
}
}
并发容器和同步容器
CopyOnWriteArrayList
ConcurrentLinkedQueue(非阻塞)
阻塞队列
BlockingQueue
两个阻塞方法
put()
take()
抛出异常
add()
remove()
有返回值
offer()
poll()
线程池
线程池的优势
第一:降低资源消耗,通过重复利用已创建的线程降低创建和销毁造成的消耗
第二:提高响应速度,当任务达到时,任务可以不需要等到线程创建就立即执行
第三:提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌
LongAdder
StampedLock
ReadWriteLock写锁是互斥的
读写是互斥的
写写是互斥的
StampLock
读锁并不会阻塞写锁
如何实现:
拿到锁之后会返回一个票据,根据票据的值是否被改变,判定是否在读的过程中发生了写,或者在写的过程中发生了读
public class Demo {
private StampedLock lock = new StampedLock();
private int balance;
// 悲观锁 - 开始
public void read(){
long stamp = lock.readLock();
int c = balance;
System.out.println(c);
lock.unlock(stamp);
}
public void write(int value){
long stamp = lock.writeLock();
balance += value;
lock.unlock(stamp);
}
// 悲观锁 - 结束
public void conditionReadWrite(int value){
// 首先判断balance的值是否符合更新的条件
long stamp = lock.readLock();
while(balance > 0){
long writeStamp = lock.tryConvertToWriteLock(stamp);
if(writeStamp != 0){ // 成功转换成写锁
stamp = writeStamp;
balance += value;
break;
}else{
// 没有转换成写锁,这里需要首先释放读锁,然后再拿到写锁
lock.unlockRead(stamp);
// 获取写锁
stamp = lock.writeLock();
}
}
lock.unlock(stamp);
}
// 乐观锁 - 开始
public void optimisticRead(){
long stamp = lock.tryOptimisticRead();
int c = balance;
// 这里可能会出现了写操作,因此要进行判断
if(!lock.validate(stamp)){
// 要重新读取
long readStamp = lock.readLock();
c = balance;
stamp = readStamp;
}
lock.unlockRead(stamp);
}
// 乐观锁 - 结束
public static void main(String[] args) {
}
}
重排序问题
什么是重排序
编译器和处理器为了提高程序的运行性能,会对代码指令重新排序
数据依赖性(as-if-serial)
单线程下,保证指令运行的结果正常
读后写
写后写
写后读
指令重排序分类
编译器重排序
处理器重排序
为什么要进行指令重排序
提高程序的运行性能
指令重排序带来的影响
竞争与同步
happen-before
Happeds-before 是用来指定两个操作之间的执行顺序,提供跨线程的内存可见性
在java内存模型中,如果一个操作执行的结果需要另一个操作可见,那么这两个操作之间必然存在happens-before关系
happens-before 规则如下:
程序顺序规则
监控器锁规则
volatile变量规则
传递性
Start规则
Join规则
程序线程规则
单个线程中的每个操作,总是前一个操作happens-before于该线程中的任意后续操作
监视器规则
对一个锁的解锁,总是happens-before于随后对这个锁的解锁
volatile变量规则
对一个volatile域的写,happens-before于任意一个后续对这个volatile域的读
传递性
A happens-before B
B happend-before C
则
A happens-before C
Start规则
A 线程中调用了 B 线程,那么 A 线程中的代码happens-before B线程的代码
Join规则
在一个地方join,那么join的线程 happens-before 之前的线程
锁的内存语义
锁的释放和获取所建立的 happens-before关系
/**
* 程序次序规则
* 1 happens-before 2 , 2 happens-before 3, 4 happens-before 5, 5 happens-before 6
* 监视器规则
* 3 happens-before 4
* 传递性
* 1 happens-before 2, 2 happens-before 3, 3 happens-before 4, 4 happens-before 5, 5 happens-before 6
*/
public class Demo {
private int value;
public synchronized void a(){ // 1 获取锁
value++; // 2
} // 3 释放锁
public synchronized void b(){ // 4 获取锁
int a = value; // 5
// 处理其他操作
} // 6 释放锁
}
锁的释放和获取的内存语义
锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息
volatile的内存语义
Volatile读写所建立的happens-before关系
/**
* 程序次序规则
* 1 happens-before 2 , 2 happens-before 3, 3 happens-before 4, 4 happens-before 5
* volatile 规则
* 2 happens-before 3
* 传递性
* 1 happens-before 2, 2 happens-before 3, 3 happens-before 4, 4 happens-before 5
*/
public class Demo {
private volatile boolean flag;
private volatile int a;
public void writer(){
a = 1; // 1
flag = true; //2
}
public void reader(){
if(flag){// 3
int b = a + 1;//4
System.out.println(b);//5
}
}
}
volatile 读写的内存语义
写一个volatile变量时,java的内存模型会把该线程对应的本地内存中的共享变量值刷新到主内存中
读一个volatile变量时,java内存模型会把当前线程对应的本地内存中的共享变量置为无效,然后从主内存中读取该共享变量
final 域的内存语义
写final域的重排序规则
写final域的重排序规则禁止把final域的写 重排序到构造方法之外
java的内存模型禁止编译器把final域的写 重排序到构造方法之外
编译器会在final域的写之后,在构造方法执行完毕之前,插入一个内存屏障storestore,保证处理器把final域的写操作在构造方法中执行
内存屏障
LoadLoad
StoreStore
LoadStore
StoreLoad
读final域的重排序规则
在一个线程中,初次读对象引用和初次读该对象所包含的final域,java内存模型禁止处理器重排序这两个操作
final域为静态类型
final域为抽象类型
在构造方法内对一个final引用的对象的成员域的写入,与随后在构造方法外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序