前言:此篇文章是为了写出一些我对多线程的理解,可能没有涉及到太多的AQS等底层实现原理,但是如果花时间来阅读的话,也会有很大的收获的!
多线程
进程和线程
进程:一个程序
一个进程可以包含多个线程,至少包含一个
线程:是一个单独的资源类
Java默认有几种线程
默认是两个,一个是GC垃圾回收和main方法线程
线程的几种状态
·新建(NEW)
线程对象一旦创建就会进入到新生状态
·就绪(Runnable)
当调用start()方法,线程立即进入就绪状态,但不意味着立刻调度执行
·运行(Running)
进入运行状态,线程会自动调用run方法,进行一个运行状态.
·阻塞(Blocked)
当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行
·死亡(DEAO)
线程中断或者结束,一旦进入死亡状态,就不能再次启动
wait/sleep区别
1.不同的类 wait是object类 sleep是Thread类
2.锁的释放
wait会释放锁,sleep不会释放
3.使用的范围
wait必须在同步代码块中使用(得有一个人等)
sleep可以在任何地方
什么是守护线程
线程分为用户线程和守护线程,守护线程为用户线程提供公共服务,在没有用户线程可服务就会自动离开.
守护线程的优先级
优先级比较低,用于为系统中的对象和线程提供服务
如何设置守护线程
通过SetDaemon(true)设置为”守护线程”
生命周期
于线程同生共死,当守护的线程死亡,守护线程也就死亡
虚拟机必须确保用户线程执行完毕:列如 main主线程
虚拟机不用等待守护线程执行完毕 :列如 GC线程
线程同步
什么是synchronized?
多个线程同时访问同一个数据,带来方便的同时,也带来了访问冲突问题,我们要保证线程同步互斥,也就是指并发执行的多个线程,变成在同一时间内只允许一个线程访问共享资源,在访问时加入同步锁synchronized,当一个线程获得锁,独占资源,其他线程必须等待,使用后释放锁.
存在问题
1.一个线程持有锁会导致其他所有需要此锁的线程挂起。
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
3.如果一个优先级高得线程等待一个优先级低得线程释放锁,会导致优先级倒置,引发性能问题
synchronized同步方法
public synchronized void caoyuzheng(){
//同步方法
}
Synchronized块
synchronized(obj){}//同步代码块
我们这里插入一个Synchronized实现卖票的一个例子
package com.cao.demo1;
/**
* @Author 癔症
* @Date 2020/8/18 10:52
* @Version 1.0
*/
/**
*基本的卖票例子
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作!
*/
public class SynchronizedDemo {
public static synchronized void main(String[] args) {
Ticket ticket = new Ticket();
//函数式接口 @FunctionalInterface jdk1.8 lambad表达式 (参数)->{代码} 减少了大量的代码,最大的有点是解决了程序之间的耦合性
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i=0;i< 40;i++){
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
class Ticket{
//属性,方法
private int num=50;
//卖票的方式
public synchronized void sale(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(num--)+"票"+"剩余票数"+num);
}
}
}
Synchronized在1.6版本之前一直都是处于一个重量级锁,但是它在1.6版本之后做了很大的更改,也就是所谓的锁升级!
锁升级
无锁状态,到它的偏向锁,再到它的一个轻量级锁,最后到重量级锁
偏向锁
一般在偏向锁的情况下,它就偏向于获得第一个锁的线程,它会将线程拉到这个锁对象的对象头当中,当其他线程来的时候,它可能就会立刻结束这个偏向状态,进而跑到一个轻量级锁,
轻量级锁
在低并发情况下来消除锁的源于,它主要是在虚拟栈中开辟一个空间叫Lock Record,将锁对象的Make word 写入,再尝试将另一个Lock Record的指针,使用CAS去修改锁对象头的那个区域,完成一个加锁过程,它也是普遍应用于一个低并发的情况,再往上如果锁竞争非常激烈。那就会立刻升级为一个重量级锁.
重量级锁
用的是一个互斥锁的过程,通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高.重量级锁的话,同步方法和同步代码块不一样的!
同步代码块(重量级锁)
在编译之后,会在你的代码前后加上两个指令,一个是mointerenter,一个是mointerexit,一个线程来的时候,它发现它的锁标志位是无锁,是01状态,它会尝试给一个互斥锁对象,锁对象的时候会跟另一个对象关联,就是监视器monitor,它会在monitor的一个锁定器加1.并且将这个monitor的指针写入到一个对象头中表示,并且修改它的锁对象标志位为1 0,就是它重量级锁的一个标志位,以此完成换锁的过程,并且它在这个过程是可重入的,因为它不会每次出去之后,再进来需要加锁和释放锁,它每次进来后获取这个锁,让锁记录加1即可,它加锁完之后,当其他线程来的时候,它会检查到这个锁对象头中,monitor监视器锁上计数器不为0,它会在monitor监视状态下等待去竞争这个锁,如果之前的操作结束,它就退出开始释放这锁,并且逐步的将加上的锁定释放几次,将计数器清零来完成对锁的一个释放.让其他线程继续去竞争这个锁,这是它重量级锁同步代码块的一个原理.
同步方法(重量级锁)
同步方法的话,它就不是这种指令了,而是ACC_SYNCHRONIZED标志位,相当于一个flag,当JVM去检测到这样一个flag,它自动去走了一个同步方法调用的策略,这个原理是比较简单的.
锁升级一般不会降级
简单来说就是锁会由它锁竞争的强度而升级.
锁降级基本上就是进入gc的时候了,所以基本不考虑锁降级.
Lock锁
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
ReentrantLock默认使用非公平锁
默认是使用的非公平锁,为什么,因为是为了保证公平的,为什么保证公平,假如我前面有个线程执行的时间会很花费很长时间,但是后面的线程花费及其少的时间,它就会插队,保证公平,我们可以在里面填入参数true改编成公平锁
Lock锁同步代码的实现
package com.cao.demo1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author 癔症
* @Date 2020/8/18 13:26
* @Version 1.0
*/
public class LockDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for(int i=0;i<40;i++) { ticket.sale();
}
},"A").start();
new Thread(()->{
for(int i=0;i<40;i++) { ticket.sale();
}
},"B").start();
new Thread(()->{
for(int i=0;i<40;i++) { ticket.sale();
}
},"C").start();
}
//Lock锁三部曲
//1.new ReentrantLock();
//2.加锁, 使用.lock();
//3.解锁, finally-->.unlock();
class Ticket2{
//属性,方法
private int num=50;
Lock lock=new ReentrantLock();
public void sale(){
//加锁
lock.lock();
try {
//业务代码
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(num--)+"票"+"剩余票数"+num);
lock.tryLock();//查看锁的状态
}
}catch (Exception e){
}finally {
//解锁
lock.unlock();
}
}
}
}
公平锁/非公平锁
公平锁:十分共平,保证线程之间可以条条有序
举例来说如果我前面一条线程执行需要30秒,而另一条线程执行需要3秒,但是3秒的还是会要等待30秒的执行完.
非公平锁:十分不公平,线程之间可以进行一个插队操作!
举例来说如果我前面一条线程执行需要30秒,而另一条线程执行需要3秒,但是3秒的线程会提前执行,30秒的后执行.
synchronized和lock的区别
1.Synchronized 无法判断获取锁的状态,lock可以获取锁的状态
2.Synchronized 会自动释放锁,lock必须手动释放锁!如果不释放,会出现死锁.
3.Synchronized 线程需要释放锁才能进行执行下一个线程,假如出现阻塞会一直等待,lock锁不一定会等待下去,因为它可以获取锁的状态
4.Synchronized 可重入锁,不可以中断的,非公平;lock锁可重入锁,可以判断锁,非公平(可自动进行设置)
ReentrantLock和Synchronized 的区别
1.synchronized是JVM的一个关键字,ReentrantLock其实就是一个类,你需要去手动去编码.
2.synchronized在使用的时候比较简单,直接同步代码块或者直接同步方法,不需要关心锁的释放,但是ReentrantLock需要手动的去lock然后配合try finally代码块一定要去把它的锁给释放
3.ReentrantLock相比synchronized有几个高级特性,它提供了一个,如果一个线程长期等待不到一个锁的时候,为了防止死锁,可以去手动调用lockInterruptibly方法,尝试去释放这个锁,释放自己的资源不去等待
4.ReentrantLock提供了一个,可以构造公平锁的一个方式,因为它的构造函数有一个但是不推荐使用,因为它会让ReentrantLock等级下降,它提供了一个condition,可以指定去唤醒绑定到condition身上的线程,来实现选择性通知的一个机制.
关于选择性,如果不需要ReentrantLock的特性的话,还是使用synchronized,因为相比来说synchronized的话,它是JVM层面的关键字,当优化JDK的时候它会非常方便的去了解,当前的锁被那些线程所持有,这个状态的话不是ReentrantLock能相比的,还是synchronized比较好些
死锁
某一个同步块同时拥有两个以上对象的锁时,就可能发生死锁问题.
产生死锁的四个必要条件
\1. 互斥条件:一个资源每次只能被一个进程使用。
\2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放.
\3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺.
\4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系.
死锁避免
破任意一个或多个条件就可以避免死锁,同步中尽量不要嵌套同步
死锁排查
1.我们可以使用jps -l来查看所有线程号
2.然后使用jstack查看所有线程号
如果报出异常,说明此线程出现了死锁,这种方法主要是通过日志和堆栈信息来确定的.
当然也可以使用工具来排查,这里就省略不说了.
并发保证集合安全
CopyOnWriteArrayList
CopyOnWriteArrayList解决并发ArrayList不安全问题
并发下ArrayList是不安全的
解决方案:
1、使用Vector
2、Collections.synchronizedList转换安全 转换的synchronized
3、使用 CopyOnWriteArrayList
CopyOnWriteArrayList写入时复制,计算机程序设计领域的一种优化策略
多个线程调用的时候,list 读取的时候,固定的 写入(覆盖).
在写入的时候避免覆盖,造成数据问题
读写分离
CopyOnWriteArrayList 跟Vector 的区别
CopyOnWriteArrayList 比Vector 效率要高,因为CopyOnWriteArrayList 在Add的时候底层没有使用同步锁来实现,Vector在Add的时候底层使用了使同步锁来实现,使用同步锁实现效率会比较低.
CopyOnWriteArraySet
并发下Set是不安全的
解决方案
1、Collections.synchronizedList转换安全 转换的synchronized
2、使用CopyOnWriteArraySet
补充知识:HashSet底层实现原理
//HashSet底层是使用HashMap实现的
public HashSet() {
map = new HashMap<>();
}
//添加时就单纯一个put key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT就是一个常量固定值
private static final Object PRESENT = new Object();
并发下hashMap是不安全的
1.使用ConcurrentHashMap代替来保证一个线程安全
2.放进Collection.Synchronized中来保证线程安全.
CountDownLatch
它适合一个线程等待一批线程达到一个同步点,之前进去就行
它的计数器是不能重用的
减法计数器,
package aadd;
import java.util.concurrent.CountDownLatch;
/**
* @Author 癔症
* @Date 2020/8/26 20:47
* @Version 1.0
*/
//计数器
public class CoutDownlatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6 ,必须要执行任务的时候使用
CountDownLatch downlatch = new CountDownLatch(6);
for(int i=1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go out");
downlatch.countDown();//数量-1
},String.valueOf(i)).start();
}
downlatch.await();//等待计数器归零,然后向下执行
System.out.println("关闭");
}
}
原理:
countDown(); 数量-1
await(); 等待计数器归零
CyclicBarrier
它是一批线程同时到达一个临界点,之后再往下走
它的计数器是可以留下来的
加法计数器
package aadd;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @Author 癔症
* @Date 2020/8/26 21:00
* @Version 1.0
*/
public class CyclicBarrierDemo{
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙");
});
for(int i=1;i<=7;i++){
final int temp =i;
new Thread(()->{
System.out.println(
Thread.currentThread().getName()+"收集"+temp+"个龙珠"
);
try {
cyclicBarrier.await();//等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
它就是我们经常称谓的信号量,它可以维持一组许可证
package aadd;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔症
* @Date 2020/8/26 21:12
* @Version 1.0
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
原理:
acquire();//获取,假设如果已经满了,等待,等待被释放为止!
release(); //释放,会将当前的信号量释放+1.然后唤醒等待的线程!
作用:多个共享资源互斥的作用!并发限流,控制最大的线程数!
读写锁
独享锁(写锁):一次只能被一个线程占有
共享锁(读锁):多个线程可以同时占有
ReadwriteLock
写入的时候,只希望同一个线程去写
writeLock.lock方法
读取的时候所有人都可以读!
readLock.lock方法
这个时候其实我们已经不知不觉看到了很多锁了.其实锁并没有太难!!!
接下来我们再来聊聊队列,为线程池打一个铺垫!!!
阻塞队列
BlockingQueue
写入:如果队列满了,就必须阻塞等待
取:如果是队列是空的,必须阻塞等待生产.
什么情况下会使用阻塞队列:多线程并发处理,线程池!
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer |
移除 | remove | poll | take | poll |
判断队列首 | element | peek | - | - |
//第一种抛出异常
package bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author 癔症
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
Test1();
}
public static void Test1(){
//队列的大小
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
System.out.println( queue.add("a"));
System.out.println( queue.add("b"));
System.out.println( queue.add("c"));
//java.lang.IllegalStateException: Queue full
//System.out.println( queue.add("d"));
//java.util.NoSuchElementException
//System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
}
}
//第二种不抛出异常
package bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author 癔症
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
Test2();
}
public static void Test2(){
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
//超出长度。不抛出异常
//System.out.println(queue.offer("d"));
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//移除超出长度,返回null
//System.out.println(queue.poll());
}
}
//第三种阻塞等待
package bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author 癔症
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Test3();
}
public static void Test3() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
queue.put("a");
queue.put("b");
queue.put("c");
//队列没有位置了,一直阻塞
// queue.put("d");
System.out.println( queue.take());
System.out.println( queue.take());
System.out.println( queue.take());
//一直等待 死掉了
System.out.println( queue.take());
}
}
//超时等待
package bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔症
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Test4();
}
public static void Test4() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
queue.offer("a");
queue.offer("b");
queue.offer("c");
//超时退出
// queue.offer("d", 2,TimeUnit.SECONDS);
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//超过两秒就退出
// queue.poll( 2,TimeUnit.SECONDS);
}
}
同步队列
synchronousQueue
它没有容量,进去一个元素,必须等待取出来之后,才能再放元素.
package bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔症
* @Date 2020/8/27 16:25
* @Version 1.0
*/
//同步队列
//put一个元素,必须先toke取出,否则put不进去
public class synchronousQueueTest {
public static void main(String[] args) {
//同步队列
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"p1");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"p2");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.put("3");
System.out.println(Thread.currentThread().getName()+"p3");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
synchronousQueue.take();
System.out.println(Thread.currentThread().getName()+"------put1");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.take();
System.out.println(Thread.currentThread().getName()+"------put2");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.take();
System.out.println(Thread.currentThread().getName()+"------put3");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
线程池
线程池:三大方法、7大参数、4种拒绝策略
好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
三大方法
package pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author 癔症
* @Date 2020/8/27 17:04
* @Version 1.0
*/
//Executors 工具类 、3大方法
public class Demo1 {
public static void main(String[] args) {
//ExecutorService threadpool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadpool2 = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
ExecutorService threadpool3 = Executors.newCachedThreadPool();//创建一个缓存线程池,遇强则强,遇弱则弱
/* for (int i = 0; i <10 ; i++) {
//使用了线程池之后要使用线程池来创建线程
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"单线程线程池");
});
}
try {
}catch (Exception e){
}finally {
//线程池使用完,线程结束,关闭线程池
threadpool.shutdownNow();
}*/
for (int i = 0; i <10 ; i++) {
//使用了线程池之后要使用线程池来创建线程
threadpool3.execute(()->{
System.out.println(Thread.currentThread().getName()+"固定大小线程池");
});
}
try {
}catch (Exception e){
}finally {
//线程池使用完,线程结束,关闭线程池
threadpool3.shutdownNow();
}
}
}
七大参数
这里我们通过源码来进行分析
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
它们都是通过ThreadPoolExecutor来实现的
public ThreadPoolExecutor(int corePoolSize,//核心线程大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //创建线程,一般不动
RejectedExecutionHandler handler ) { //拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
四种拒绝策略
AbortPolicy就是抛出异常
CallerRunsPolicy就是 那个线程来的,去哪里 就比方main线程
DiscardOldestPolicy就是 队列满了,会尝试和最早的竞争,也不会抛出异常
DiscardPolicy就是 不会抛出异常,队列满了就停止了
我们去自定义一个线程来使用
package pool;
import java.util.concurrent.*;
/**
* @Author 癔症
* @Date 2020/8/27 17:04
* @Version 1.0
*/
//Executors 工具类 、3大方法
public class Demo1 {
public static void main(String[] args) {
//自定义线程池! ThreadPoolExecutor 最常用的线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); //如果队列满了,还有人进来,不处理,并抛出异常
/* for (int i = 0; i <10 ; i++) {
//使用了线程池之后要使用线程池来创建线程
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"单线程线程池");
});
}
try {
}catch (Exception e){
}finally {
//线程池使用完,线程结束,关闭线程池
threadpool.shutdownNow();
}*/
for (int i = 0; i<6 ; i++) {
//使用了线程池之后要使用线程池来创建线程
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"自定义线程");
});
}
try {
//最大承载:阻塞队列加最大线程数 Deque+max 超出此范围 就会走拒绝策略
}catch (Exception e){
}finally {
//线程池使用完,线程结束,关闭线程池
poolExecutor.shutdownNow();
}
}
}
池的最大的大小如何去设置!
查询CPU最大核数
Cpu密集型,几核,就是几,可以保持CPU的效率最高
IO密集型 >判断你程序种十分耗IO的线程
Runtime.getRuntime().availableProcessors();
volatile
volatile是java虚拟机提供轻量级的同步机制
1、保证可见性
package ACID;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔症
* @Date 2020/8/30 11:03
* @Version 1.0
*/
// 工作内存感知不到主内存发生了变化,所以程序才不会结束
public class Vdemo02 {
private static int num=0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
不加volatile会出现死循环,因为线程感知不到外边值的一个变化,加了volatile会保证一个可见性
package ACID;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔症
* @Date 2020/8/30 11:03
* @Version 1.0
*/
// 工作内存感知不到主内存的变化,所以程序才不会结束
public class Vdemo02 {
//加上volatile,工作内存可以感知到主内存的变化,保证一致性
private volatile static int num=0;
public static void main(String[] args) {
new Thread(()->{//对主内存的变化是不知道的
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
这里的可见性采用的是一个嗅觉机制和MESI机制.
总线风暴
不断的总线嗅探机制会出现问题
由于volatile的mesi缓存一致性协议需要不断的从主内存嗅探和cas不断循环无效交互导致总线带宽达到峰值
解决方案: 部分volatile和cas使用synchronize
2、不保证原子性
原子性:不可分割,要么同时成功,要么同时失败
线程A在执行任务的时候,不能被打扰的,也不能被分割。
package ACID;
/**
* @Author 癔症
* @Date 2020/8/30 19:35
* @Version 1.0
*
*/
//不保证原子性
public class VDemo03 {
public static int num;
public static void add(){
num++;
};
public static void main(String[] args) {
//num应该是20000,但是它可能输出的不是两万,为什么?
for (int i = 1; i <=20;i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){// main gc
Thread.yield();//暂停线程
}
System.out.println(Thread.currentThread().getName()+"------------"+num);
}
}
原子类
Atomic就是原子类
加上synchronized就可以保证一个原子性,因为synchronized默认是支持原子性的
但是加上volatile不会保证原子性
使用原子类 解决原子性问题,我们根本你不需要使用volatile和synchronized
package ACID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author 癔症
* @Date 2020/8/30 19:35
* @Version 1.0
*
*/
//不保证原子性
public class VDemo03 {
//不保证原子性
//我们可以使用原子类
public static AtomicInteger atomicInteger =new AtomicInteger();
public static void add(){
//不是一个原子性操作
//num++;
atomicInteger.getAndIncrement();//使用原子类执行+1的操作
};
public static void main(String[] args) {
//num应该是20000,但是它可能输出的不是两万,为什么?
for (int i = 1; i <=20;i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){// main gc
Thread.yield();//暂停线程
}
System.out.println(Thread.currentThread().getName()+"------------"+atomicInteger);
}
}
原子类的底层直接和操作系统挂钩,在内存中修改值!它底层是一个Unsafe类,Unsafe底层大量调用了native方法,说明调用的是其他语言的方法.(很深层).
3、禁止指令重排
代码在编译的时候,指令会进行一个重新排序,这个就是指令重排
加了volatile关键字会产生内存屏障 作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
读写的时候,加上volatile会在上下产生一个内存屏障,去禁止上下指令顺序交换
总结:volatile保证可见性,不保证原子性,由于内存屏障,可以保证避免指令重排的现象产生
CAS
CAS(比较并交换),在并发不是特别大的情况下,锁竞争不激烈,你要去修改这个东西,你要先查,查完之后,再修改,修改完,准备写进去之前,它会再查一次,比较之前的结果有没有区别,如果有区别说明这个修改是不安全的.如果要是没有区别,说明这个修改是安全的,这个时候它就可以安全的去修改,而不是直接加锁的那种形式,在低并发的情况性能会好一点!
缺点:
1、由于底层是自旋锁,循环会耗时
2、一次只能保证一个共享变量的原子性
3、高并发情况下大量使用它会出现ABA问题
ABA问题
package cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author 癔症
* @Date 2020/8/31 19:04
* @Version 1.0
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//如果我们期望的值达到了,那么就更新,否则,就不更新,CAS是 CPU的并发原语
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021,2020));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020,6666));
System.out.println(atomicInteger.get());
}
}
之前读和再过读中间可能被第三人修改过,但是又给改了回来.
原子引用解决ABA
AtomicStampedReference增加版本号,如果有人来修改,则增加版本号.
package cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @Author 癔症
* @Date 2020/8/31 19:04
* @Version 1.0
*/
public class CASDemo {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
public static void main(String[] args) {
new Thread(()->{
int stamp=atomicStampedReference.getStamp();//获得这个版本号
System.out.println("a----->"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//compareAndSet 比较并交换
System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("aa----->"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("aaa----->"+atomicStampedReference.getStamp());
},"A").start();
//底层是使用了乐观锁原理
new Thread(()->{
int stamp= atomicStampedReference.getStamp();
System.out.println("b----->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("bb----->"+atomicStampedReference.getStamp());
},"B").start();
}
}
思想跟乐观锁几乎一样
可重入锁
可重入锁(递归锁):拿到了外面的锁之后,就可以拿到里面的锁,自动获取
自旋锁
CAS底层就是通过自旋锁来实现的.
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
总结:谢谢大家的支持,如果文章中有什么错误或者那块理解的不是很到位,请各位及时补充一下!不喜勿喷,互相帮助!共同进步!