一、volatile关键字与内存可见性
public class Testvolatile {
public static void main(String[] args){ //这个线程是用来读取flag的值的
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();
while (true){
if (threadDemo.flag){
System.out.println("主线程读取到的flag = " + threadDemo.flag);
} break;
}
}
}
class ThreadDemo implements Runnable { //这个线程是用来修改flag的值的
public boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("ThreadDemo线程修改后的flag = " + flag);
}
}
ThreadDemo线程修改后的flag = true
volatile 关键字:当多个线程操作共享数据时,可以保证内存中的数据可见,相较于synchronized是一种较为轻量级的同步策略。注意:1.volatile 不具备“互斥性”,2.volatile 不能保证变量的“原子性”
二、Lock 同步锁
Lock需要通过lock()方法上锁,通过unlock()方法释放锁。为了保证锁能释放,所有unlock方法一般放在finally中去执行。
class Ticker {
private int number = 300;
private Lock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + "\t" + number + "还剩" + --number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
} }}
public class Demo {
public static void main(String[] args) {
final Ticker ticker = new Ticker();
new Thread(() -> { for (int i = 0; i < 300; i++) ticker.sale();},"DD").start();
new Thread(()->{
for(int i=0;i<300;i++) ticker.sale();
},"FF").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
ticker.sale();
}
}
}, "AA").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
ticker.sale();
}
}
}, "BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
ticker.sale();
}
}
}, "CC").start(); }}
在Java 5.0 之前,协调共享对象的访问时可以使用的机制只有synchronized 和volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。ReentrantLock 实现了Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。
三、闭锁
java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。ContDownLatch是一个同步辅助类,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行,这就叫闭锁
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
testCloseDoor();
}
public static void testCloseDoor() throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(16);
for(int i=0;i<16;i++){
new Thread(()->
{
System.out.println(Thread.currentThread().getName()+"\t 离开");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();这16个线程执行完之前先等待
System.out.println(Thread.currentThread().getName()+"\t**********关门走人");
}
}
四、创建线程的方式 — 实现Callable接口
public class TestCallable {
public static void main(String[] args){
CallableDemo callableDemo = new CallableDemo();
//执行callable方式,需要FutureTask实现类的支持,用来接收运算结果
FutureTask<Integer> result = new FutureTask<>(callableDemo);
new Thread(result).start();
//接收线程运算结果
try {
Integer sum = result.get();//当上面的线程执行完后,才会打印结果。跟闭锁一样。所有futureTask也可以用于闭锁
System.out.println(sum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class CallableDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0;i<=100;i++){
sum += i;
}
return sum;
}
}
现在Callable接口和实现Runable接口的区别就是,Callable带泛型,其call方法有返回值。使用的时候,需要用FutureTask来接收返回值。而且它也要等到线程执行完调用get方法才会执行,也可以用于闭锁操作。
五、ReadWriterLock读写锁
我们在读数据的时候,可以多个线程同时读,不会出现问题,但是写数据的时候,如果多个线程同时写数据,那么到底是写入哪个线程的数据呢?所以,如果有两个线程,写写/读写需要互斥,读读不需要互斥。这个时候可以用读写锁。
package main.java;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Created by qiyalm on 2019/5/4.
*/
class Myqueue {
private Object object;
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void writeObject(Object o) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + o);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void readObject(Object o) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + o);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock(); } }}
public class ReadWriteLock {
public static void main(String[] args) throws InterruptedException {
Myqueue myqueue = new Myqueue();
Thread thread1 = new Thread("tt");
thread1.run();
thread1.start();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
myqueue.writeObject("ClassName1018");
}
}, "writeThead");
thread.start();
Thread.sleep(100);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
myqueue.readObject("ClassName1019");
}, "readThead" + String.valueOf(i)).start(); } }}
六、CAS算法:
CAS算法是计算机硬件对并发操作共享数据的支持,CAS包含3个操作数:
内存值V
预估值A
更新值B
当且仅当V==B时,才会把B的值赋给V,即V = B,否则不做任何操作。就上面的i++问题,CAS算法是这样处理的:首先V是主存中的值0,然后预估值A也是0,因为此时还没有任何操作,这时V=B,所以进行自增,同时把主存中的值变为1。如果第二个线程读取到主存中的还是0也没关系,因为此时预估值已经变成1,V不等于B,所以不进行任何操作。
乐观锁的缺点
ABA 问题是乐观锁一个常见的问题
1 ABA 问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2 循环时间长开销大
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3 只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。
CAS与synchronized的使用情景
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
七、多线程和线程池
线程的生命周期,线程的生命周期图解:
线程通信
0、sleep(100)
1、Thread类里的静态方法
2、睡眠时间毫秒
3、不释放所在线程锁
1、wait()
1、java.lang.Object类里的非静态方法
2、wait()调用后,释放调用wait对象的线程锁,
如果对象不占用锁则报IllegalMonitorStateException
3、无参 则会一直等直到 notify()或notifyAll()
4、wait(100) 单位毫秒 超时自动被唤醒
2、notify()
1、java.lang.Object类里的非静态方法
2、notify()调用后,通知同一个线程锁下wait(),继续往下执行
3、notifyAll()调用后,通知所有线程锁下wait(),继续往下执行
3、join()
1、Thread类里的非静态方法
2、a.join() 等待a线程执行完毕再接着执行a.join()所在的线程
3、main{ a.start();b.start();a.join();}
main线程先开启了A,B线程,B线程执行不受a.join影响的
只是说主线程等待A执行完再执行,B线程是独立的不受影响的
4、Interrupt()
1、Thread的非静态方法
2、thread.interrupt();//设置中断标志位为true
3、sleep() wait() join() 不断检测线程的中断位
如果中断为true报InterruptedException 如果false不处理
4、抛出InterruptedException 清除中断标志位
即设置中断标志位为false
5、yield()
1、Thread的静态方法
2、线程 从运行状态--到就绪状态
3、线程在 this.start()准备执行——就绪状态run()运行——就是运行状态
6、对象调用wait()(疑惑-在对象是一个线程的前提下,
线程开启再调用调用wait,不会起到等待的效果)
if(对象==类实例 && 类实例.start()){
wait()等待的是类实例run方法执行完
}else {//!类实例.start() || 对象!=类实例
wait()等待所在线程
} //TODO 代码运行总结的不知道对不对
7、sleep()和wait()区别
1、wait()等待释放锁
2、sleep()等待不释放锁
线程池
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
使用线程池的好处:
1.减少在创建和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
JDK自带线程池:
一.FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
二.CachedThreadPool的特点就是在线程池空闲时,即线程池中没有可运行任务时,它会释放工作线程,从而释放工作线程所占用的资源。但是,但当出现新任务时,又要创建一新的工作线程,又要一定的系统开销。并且,在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
ThreadPoolExecutor的重要参数
1、corePoolSize:核心线程数
* 核心线程会一直存活,及时没有任务需要执行
* 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
2、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行
3、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
4、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0
5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
public class ApiApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ApiApplication.class);
app.run(args);
}
@Bean
public ThreadPoolTaskExecutor createThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(10);
threadPoolTaskExecutor.setMaxPoolSize(20);
return threadPoolTaskExecutor;
}
}
package org.springframework.scheduling.concurrent;
@SuppressWarnings("serial")
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
private final Object poolSizeMonitor = new Object();
private int corePoolSize = 1;
private int maxPoolSize = Integer.MAX_VALUE;
private int keepAliveSeconds = 60;
private int queueCapacity = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeOut = false;
private TaskDecorator taskDecorator;
private ThreadPoolExecutor threadPoolExecutor;