深入浅出JUC并发编程

前言:此篇文章是为了写出一些我对多线程的理解,可能没有涉及到太多的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

总结:谢谢大家的支持,如果文章中有什么错误或者那块理解的不是很到位,请各位及时补充一下!不喜勿喷,互相帮助!共同进步!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_46169471/article/details/108608348