多线程基础与JUC进阶笔记

1.实现线程的几种方式

1.1.继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象.start()
  • 不建议使用:避免OOP单继承局限性
public class Test1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("线程"+i);
        }
    }

    public static void main(String[] args) {

        Test1 test1 =new Test1();
        test1.start();
        for (int i = 0; i <20 ; i++) {
            System.out.println("主线程"+i);
        }
    }
}


1.2.实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
public class Test2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }

    public static void main(String[] args) {
        Test2 test2 = new Test2();
        new Thread(test2,"线程1").start();
        new Thread(test2,"线程2").start();

    }
}

1.3.实现Callable接口

1.实现Callable接口

2.重写call方法,需要返回值类型

3.创建目标对象

4.创建执行任务:ExecutorService executorService = Executors.newFixedThreadPool(3);

5.提交执行:Future result1 = ser.submit(1)

6.获取结果:boolean r1= result1.get()

7.关闭服务:ser.shutdownNow()

public class Test3 implements Callable<Boolean> {


    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
        }
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test3 t1 = new Test3();
        Test3 t2 = new Test3();
        Test3 t3 = new Test3();
        //创建执行任务:
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> submit1 = executorService.submit(t1);
        Future<Boolean> submit2 = executorService.submit(t2);
        Future<Boolean> submit3 = executorService.submit(t3);

        // 获取结果
        boolean r1= submit1.get();
        boolean r2= submit2.get();
        boolean r3= submit3.get();

        //关闭服务:
        executorService.shutdownNow();
    }
}

2.Lambda表达式

public class Test4 {
    public static void main(String[] args) {
        Lambda lambda ;
        //lambda表达式实现接口
        lambda = (a,b) -> {
            System.out.println((a+b));
        };
        lambda.print(1,1);
    }
}
//函数式接口
interface Lambda{
    void print(int a,int b);
}

总结:

  • lambda表达式只有一行代码的情况下才能简化称为一行,如果有多行,那么就用代码块包裹。
  • 使用lambda表达式的前提必须是函数式接口(只有一个方法)
  • 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号

3.静态代理模式

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色

好处:

  • 代理对象可以做很多真实对象做不了的事情
  • 真实对象专注做自己的事情

静态代理模式,婚庆公司举例:

public class Test5 {
    public static void main(String[] args) {
        
        new WeddingCompany(new You()).happyMarry();
    }

}
interface Marry{
    void happyMarry();
}
//角色,你去结婚
class You implements Marry{

    @Override
    public void happyMarry() {
        System.out.println("你在结婚中");
    }
}
//代理角色,婚庆公司,帮助你结婚
class WeddingCompany implements Marry{
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        before();
        target.happyMarry();
        after();
    }

    private void after() {
        System.out.println("结婚后收钱");
    }

    private void before() {
        System.out.println("结婚前准备");
    }

}

线程的实现原理就是基于静态代理模式。

new WeddingCompany(new You()).happyMarry();

new Thread(()-> System.out.println("我爱你"));

Thread也实现了Runnable接口。

Thread就是一个代理对象,代理中间的真实对象Runnable接口。

4.synchronized锁

synchronized方法:锁的是当前类的class。

public class Test6 {
    public static void main(String[] args) {
        BuyTickets b1= new BuyTickets();
        new Thread(b1,"张三").start();
        new Thread(b1,"李四").start();
        new Thread(b1,"赵五").start();
        
    }
}
class BuyTickets implements  Runnable{
    private int ticketsNum = 10;
    private boolean flag = true;//外部停止方式
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
        }
    }
	
    private synchronized void buy(){
        if (ticketsNum<=0){
            flag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"拿到了"+ticketsNum--);
    }
}

synchronized代码块:synchronized(锁的对象){

​ 要加锁的内容

}

private  void buy(){
        synchronized (ticketsNum) {
            if (ticketsNum <= 0) {
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNum--);
        }
    }

锁的对象是进行增删改的对象。

5.死锁

当某个同步块同时拥有“两个以上对象的锁时”,就可能会放生死锁问题

两个对象互相占着对方的资源,产生僵持。

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源,在未使用完前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

6.Lock锁

  • 从JDK5.0开始, java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock充当。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock可重用锁,可以显式加锁,释放锁。
    //new一个锁对象
    ReentrantLock lock = new ReentrantLock();
    private  void buy(){
        //加锁
       try {
           lock.lock();
           if (ticketsNum <= 0) {
               flag = false;
               return;
           }
           System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNum--);
       }finally {
           //释放锁
           lock.unlock();
       }
        
    }

7.synchronized与Lock区别

  1. synchronized 内置的java关键字,lock是一个java类
  2. synchronized无法判断获取锁的状态,lock可以判断是否获得了锁
  3. synchronized会自动释放锁,lock必须手动释放锁,如果不释放就会死锁
  4. synchronized造成的阻塞线程会一直等待下去,而lock不一定会等
  5. synchronized可重入锁,不可以中断,非公平,lock可重入锁,可以判断锁,默认非公平(可以自己设置)
  6. synchronized适合少量的代码同步问题,lock适合大量的同步代码

8.线程池

  • JDK5.0起提供了线程池相关API:ExecutorService和Excutors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExcutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • Future submit(Callable task) :执行任务,有返回值,一般用来执行Callable
  • Excutors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池

9.线程状态

    public enum State {
        /**
         * 新生
         */
        NEW,

        /**
         运行
         */
        RUNNABLE,

        /**
			阻塞
         */
        BLOCKED,

        /*
        	等待
         */
        WAITING,

        /**
          超时等待
         */
        TIMED_WAITING,

        /**
         死亡
         */
        TERMINATED;
    }

10. wait和sleep的区别

都是线程休眠

1.来自不同的类

wait是Object类中的

sleep是Thread类中的

2.关于锁的释放

wait会释放锁

sleep不会释放锁

3.使用范围不同

sleep在任何地方都可以使用

wait只有在同步代码块中可以使用

4.是否需捕获异常

wait不需要捕获异常

sleep需要捕获异常

11.生产者与消费者问题

/*
生产者消费者问题
 */
public class Test7 {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"D").start();
    }
}
class Data{
    private static Integer num = 0;
    public synchronized void increment() throws InterruptedException {
        //注意这里要用while来判断,不能用if,if只判断一次会引起虚假唤醒问题
        while (num!=0){
            //等待
            this.wait();
        }
        //唤醒其他线程
        this.notifyAll();
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
    }

    public synchronized void decrement() throws InterruptedException {
        while (num==0){
            this.wait();
        }
        this.notifyAll();
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
    }

}

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒。

等待应该发生在循环中,不应用if判断。

12.什么是JUC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4X3h0YS-1602919849079)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201015102153149.png)]

juc就是,java.util.cocurrent 包

13.JUC版的生产者与消费者问题

Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。 await,signal。

一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。

class Data{
    private static Integer num = 0;
    //创建锁对象
    ReentrantLock lock = new ReentrantLock();
    //创建Condition对象,用它其中的方法await 和 signal 来代替wait 和notify
    Condition condition = lock.newCondition();
    public  void increment() throws InterruptedException {
        try{
            //上锁
            lock.lock();
            //注意这里要用while来判断,不能用if,if只判断一次会引起虚假唤醒问题
            while (num!=0){
                //等待
                condition.await();
            }
            num++;
            condition.signal();
            System.out.println(Thread.currentThread().getName()+"=>"+num);
        }finally {
            //释放锁
            lock.unlock();
        }


    }

    public  void decrement() throws InterruptedException {
        try {
            lock.lock();
            while (num==0){
                condition.await();
            }
            num--;
            condition.signal();
            System.out.println(Thread.currentThread().getName()+"=>"+num);

        }finally {
            lock.unlock();
        }

    }
    
}

对比wait和notify,condition可以精准通知和唤醒线程

/**
 * 顺序通知线程执行,condition精准唤醒线程
 * ABCD
 */
public class Test2 {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printA();
            }

        },"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printC();
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printD();
            }
        },"D").start();
    }
}
class Data3{
    private ReentrantLock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    Condition condition4 = lock.newCondition();
    private int num = 1;
    public void printA(){
        lock.lock();
        try {
            while (num!=1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"->"+num);
            num = 2;
            //唤醒B线程
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            while (num!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"->"+num);
            num = 3;
            //唤醒C线程
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            while (num!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"->"+num);
            num = 4;
            //唤醒C
            condition4.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printD(){
        lock.lock();
        try {
            while (num!=4){
                condition4.await();
            }
            System.out.println(Thread.currentThread().getName()+"->"+num);
            num = 1;
            //唤醒A
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
A->1
B->2
C->3
D->4
A->1
B->2
C->3
D->4
...

14.8锁问题

关于锁的8个问题

例子

public class Test3 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        }).start();
        new Thread(()->{
            phone.call();
        }).start();

    }
}
class Phone{
    public synchronized void sendSms(){
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

1.标准情况下,两个线程先打印发短信还是打电话?

先打印发短信,两个线程共用phone对象同一个锁,先调用发短信则打电话必须等发短信执行完之后才能执行。

2.sedSms延迟4秒,两个线程先打印发短信还是打电话?

先打印发短信,和上题情况相同。

3.增加了一个普通方法,先执行发短信还是普通方法?

先执行普通方法,由于普通方法没有上锁,所以不用等待发短信执行完毕就可先执行。

4.两个对象,两个同步方法,第一个对象执行发短信和打电话,第二个对象执行打电话,先执行发短信还是打电话?

先执行打电话,两个对象对应了两个锁,打电话没有休眠时间,所以比发短信先执行。

5.将两个同步方法都改为静态同步方法,只有一个对象。先执行发短信还是打电话?

先执行发短信, 静态方法在类初始化的时候就会执行,这时候锁的就不是对象了,而是Class,按顺序执行发短信在前同时拥有锁,打电话就得等待。

6.5条件中改为两个对象,先执行发短信还是打电话?

发短信,还是和上题答案一样,锁的是Class而不是对象,就和对象无关了。

7.发短信静态的同步方法,打电话普通的同步方法,先执行发短信还是打电话?

先执行打电话,这时候就有两个锁了,发短信占用的是Class的锁,而打电话占用的是对象的锁,两者互不影响

8.上述条件,两个对象,先执行发短信还是打电话?

还是先执行打电话,和上题解释相同。

15.集合的安全问题

15.1.List不安全

public class Test4 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

执行报错:java.util.ConcurrentModificationException

并发下的ArrayList不安全,看add源码会发现没有同步锁

解决方案:

1**.使用vector集合**

vector集合是线程安全的,底层的add方法使用synchronized加上了同步锁

但是效率较低

2.使用Collection.synchronizedList(new ArrayList<>())来创建使集合线程安全

3.使用new CopyOnwriteArrayList<>()来创建

在写入时复制,避免覆盖造成数据问题。

底层add方法采用lock锁,更高效

15.2.Set不安全

上面例子换为Set试一试

public class Test5 {
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

同样报java.util.ConcurrentModificationException异常,没有多试几次或者增加循环次数。

解决方法:

1.Collections.synchronizedCollection(set)解决

2.new CopyOnWriteArraySet<>()解决。

接着问:HashSet的底层是什么?

HashSet底层就是HashMap

源码:

  public HashSet() {
        map = new HashMap<>();
    }
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
 private static final Object PRESENT = new Object();//常量

HashSet的值不重复利用的就是HashMap的键不重复。

15.3.map不安全

public class Test6 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();

        for (int i = 0; i <30 ; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

同样报错ConcurrentModificationException,

解决:

1.Collections.synchronizedMap(map)

2.使用ConcurrentHashMap

16.Callable

  • Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。然而,A Runnable不返回结果,也不能抛出被检查的异常。
public class CallableTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable
        FutureTask futureTask = new FutureTask<>(myThread);//适配类
        new Thread(futureTask).start();//结果会被缓存,效率高
        try {
            String o = (String) futureTask.get();//可能会产生阻塞,把他放到最后
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
class MyThread implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "1024";
    }
}

细节:

1.有缓存

2.结果可能会需要等待,会阻塞。

17.常用的辅助类

17.1.CountDownLatch

减法计数器

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        //总数是6
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"出去了");
                countDownLatch.countDown();//数量-1
            },String.valueOf(i)).start();
        }
        //等待总数为0时,再执行下面的任务,即所有人出去后再关门。
        countDownLatch.await();
        System.out.println("所有人都出去了,关门");
        //2出去了
        //1出去了
        //3出去了
        //4出去了
        //5出去了
        //6出去了
        //所有人都出去了,关门
    }
}

17.2.CyclicBarrier

加法计数器

public class CyclicBarrierTest {
    public static void main(String[] args) {
        //加法计数器,当计数到7时,输出
        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();//等待到收集完7颗龙珠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    
    }
    //1拿到了1个龙珠
    //2拿到了2个龙珠
    //3拿到了3个龙珠
    //4拿到了4个龙珠
    //5拿到了5个龙珠
    //6拿到了6个龙珠
    //7拿到了7个龙珠
    //召唤神龙
}

17.3.Semaphore

信号量

多个线程共享资源,同一时间只允许一定数量的线程占用资源。

模拟抢车位

public class SemaphoreTest {
    public static void main(String[] args) {
        //模拟抢车位,共3个车位
        Semaphore semaphore = new Semaphore(3);
        //6量车在抢
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
               try {
                   semaphore.acquire();
                   System.out.println(Thread.currentThread().getName()+"抢到了车位");
                   TimeUnit.SECONDS.sleep(2);
                   semaphore.release();
                   System.out.println(Thread.currentThread().getName()+"离开了车位");
               }catch (Exception e){
                   e.printStackTrace();
               }
            },String.valueOf(i)).start();
        }
    }
    //1抢到了车位
    //3抢到了车位
    //2抢到了车位
    //1离开了车位
    //6抢到了车位
    //2离开了车位
    //3离开了车位
    //5抢到了车位
    //4抢到了车位
    //6离开了车位
    //4离开了车位
    //5离开了车位
}

18.读写锁

ReadWriteLock读写锁

读取锁(共享锁):允许多个线程进行读取操作。

写入锁(排他锁):只允许一个线程进行写入操作。

public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <=3 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.write(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <=3 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.read(temp+"");
            },String.valueOf(i)).start();
        }
    }
}
class MyCache{
    private volatile Map<String,String> map = new HashMap<>();
    //读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //读取
    public void read(String key){
        System.out.println(Thread.currentThread().getName()+"读取"+key);
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取ok");
    }
    //写入
    public void write(String key,String value){
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入ok");
    }
    //2写入2
    //2写入ok
    //1写入1
    //1写入ok
    //3写入3
    //3写入ok
    //1读取1
    //1读取ok
    //2读取2
    //2读取ok
    //3读取3
    //3读取ok
}

19.阻塞队列

BlockingQueue阻塞队列

在这里插入图片描述

在多线程并发处理和线程池情况下会使用阻塞队列

方式抛出异常有返回值,不抛出异常阻塞等待超时等待
添加addofferputoffer(,)
移除removepolltakepoll(,)
查看队首元素elementpeek--
  public static void main(String[] args) {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));
        System.out.println(arrayBlockingQueue.add("d"));//抛出异常

        System.out.println("===============");
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());  //抛出异常

    }
public static void main(String[] args) {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));
        System.out.println(arrayBlockingQueue.offer("d"));//返回false

        System.out.println("===============");
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());  //返回null

    }
 public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");
        arrayBlockingQueue.put("d");//队列没有位置了,一直阻塞


        System.out.println("===============");
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());  //没有这个元素,一直阻塞

    }
   public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        arrayBlockingQueue.offer("c");
        arrayBlockingQueue.offer("d",1,TimeUnit.SECONDS);//等待1s后就退出


        System.out.println("===============");
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll(1,TimeUnit.SECONDS));  //等待1s后就退出

    }

同步队列SynchronousQueue

没有容量,一个元素进来之后,必须等他出来才能进入下一个元素

    public static void main(String[] args) {
        //没有容量,一个元素进来之后,必须等他出来才能进入下一个元素
        SynchronousQueue<String> queue = new SynchronousQueue<>();
        new Thread(()->{

            try {
                System.out.println(Thread.currentThread().getName()+" put 1");
                queue.put("1");
                System.out.println(Thread.currentThread().getName()+" put 2");
                queue.put("2");
                System.out.println(Thread.currentThread().getName()+" put 3");
                queue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+" take "+queue.take());
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+" take "+queue.take());
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+" take "+queue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            },"t2").start();
        
    }
    //t1 put 1
    //t2 take 1
    //t1 put 2
    //t2 take 2
    //t1 put 3
    //t2 take 3

20.线程池

三大方法,7大参数,4种拒绝策略。

线程池的好处

1.降低资源的消耗

2.提高响应的速度

3.方便管理

线程复用,可以控制最大并发数,管理线程

20.1.三大方法

newSingleThreadExecutor()

newFixedThreadPool(3)

newCachedThreadPool()

  public static void main(String[] args) {
        //ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程池
       // ExecutorService executorService = Executors.newFixedThreadPool(3);//指定线程池大小
        ExecutorService executorService = Executors.newCachedThreadPool();//遇强则强,遇弱则弱,最大可达到21亿
        try {
            for (int i = 0; i <10 ; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行");
                });
            }

        } finally {
            executorService.shutdown();
        }
    }

20.2.七大参数

看一下三大方法的底层源码

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

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方法

进去看一看

7大参数就是说的ThreadPoolExecutor中的7大参数

  • int corePoolSize,//核心线程数
  • int maximumPoolSize,//最大线程数
  • long keepAliveTime,//超时等待时间
  • TimeUnit unit,//超时单位
  • BlockingQueue workQueue,//阻塞队列
  • ThreadFactory threadFactory,//线程工厂,一般不用动
  • RejectedExecutionHandler handler//拒绝策略
   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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

举一个银行排队的例子来说明创建线程池的七大参数问题。

在这里插入图片描述

说明:

  1. 正常情况下银行柜台只开1号和2号窗口(corePoolSize核心线程数),最多有5个柜台同事开放(maximumPoolSize最大线程数)

  2. 在1号和2号窗口都有人的时候,剩下的人进入等候区(BlockingQueue阻塞队列),在等候区满的时候开启3,4,5号柜台。

  3. 在3,4,5窗口连续1小时没有人进入的 时候关闭3,4,5号窗口(keepAliveTime超时时间,TimeUnit超时单位)

  4. 在5个柜台全部开放且等候区满的情况下依然有人进入,这时候选择拒绝此人,拒绝此人的方法就是RejectedExecutionHandler拒绝策略

在这里插入图片描述

在阿里巴巴开发手册中明确要求不允许使用Excutors创建线程。而是通过ThreadPoolExecutor来创建,参数自己设置。

手动创建一个线程池案例

    public static void main(String[] args) {
        ExecutorService executorService=  new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            for (int i = 0; i <5 ; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行");
                });
            }

        } finally {
            executorService.shutdown();
        }
    }

当i的最大值为5时会有2个线程在执行

当i的最大值为6时会有3个线程在执行

当i的最大值为7时会有4个线程在执行

当i的最大值为8时会有5个线程在执行

当i的最大值为9时会抛出异常

所以由此可知线程池最大允许线程执行个数为阻塞队列大小+最大线程数

20.2.1.CPU密集型和IO密集型

面试题:最大线程到底该如何定义?

1.cpu密集型:cpu是几核就定义最大线程数是几。

2.IO密集型:判断你程序中十分耗IO的线程,假设有10个,那就可以设置2倍于他的最大线程数。

20.3.四种拒绝策略

在上述例子中,使用了默认的拒绝策略AbortPolicy,还有三种拒绝策略

在这里插入图片描述

AbortPolicy:

  • 阻塞队列满了,不处理,并抛出异常。

CallerRunsPolicy:

  • 阻塞队列满了,哪里来的去哪里,从main线程来的就交个main线程执行

DisCardOldesPolicy:

  • 阻塞队列满了,丢掉任务不管他并且不会抛出异常

DiscardPolicy:

  • 阻塞队列满了,尝试和最早进入线程池的线程竞争,不会抛出异常

21.四大函数式接口

函数式接口:只有一个方法的接口。

Function函数式接口:有一个输入参数,有一个输出参数

在这里插入图片描述

Function<String,String> function = (str)->{return str;};
 System.out.println(function.apply("123")); 

断定型函数式接口:有一个输入参数,返回值只能是布尔值

在这里插入图片描述

Predicate<String> predicate = (str)->{return false;};
System.out.println(predicate.test("123"));

消费型接口:只有输入参数,没有返回值

在这里插入图片描述

Consumer<String> consumer = (str)->{
            System.out.println(str);
        };
        consumer.accept("123");

供给型接口:没有参数,只有返回值

在这里插入图片描述

Supplier<String> supplier = ()->{return "123";};
        System.out.println(supplier.get());

22.Stream流

在这里插入图片描述

什么是stream流?

存储应交给集合和数据库来做

计算交给流来做

案例

题目要求:

1分钟内完成此题,只能用1行代码实现!

现在有5个用户!筛选:

1、ID 必须是偶数

2、年龄必须大于23岁

3、用户名转为大写字母

4、用户名字母倒着排序

5、只输出1个用户!

public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",26);
        User u3 = new User(3,"c",24);
        User u4 = new User(4,"d",25);
        User u5 = new User(5,"e",26);
        List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
        list.stream()
                .filter(user->{return user.getId()%2==0;})//id为偶数
                .filter(user->{return user.getAge()>23;})//年龄大于23岁
                .map(user ->{return user.getName().toUpperCase();})//字母转换为大写
                .sorted((user1,user2)->{return user2.compareTo(user1);})//倒序排序
                .limit(1)//只允许一个输出
                .forEach(System.out::println);//遍历
    }

23.JMM

请你谈谈对Volatile的理解

Volatile是Java虚拟机提供轻量级的同步机制

1.保证可见性

2.不能保证原子性

3.禁止指令重排

什么是JMM

Java内存模型,不存在的东西,是一种概念,约定。

关于JMM一些同步的约定:

1.线程解锁前,必须把共享变量立刻刷回主存

2.线程加锁前,必须读取主存中的最新值到工作内存中!

3.加锁和解锁是同一把锁

8种操作:

在这里插入图片描述

问题:程序不知道主内存里的值已经修改过了

24.Volatile

1.保证可见性

private volatile static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num==0){

            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num = 1;
        System.out.println(num);
    }

num加了volatile保证可见性,如果不加volatile线程就监测不到主内存中的num的值已经修改了,就会陷入死循环。

2.不保证原子性

private volatile static int num;
    public static void add(){
        num++;
    }
    public static void main(String[] args) {
        for (int i = 0; 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(num);
    }

理论上上述代码输出结果应为20000,实际上

在这里插入图片描述

这是由于volatile不能保证原子性。

那么如何保证原子性?

在这里插入图片描述

在java.util.concurrent.atomic下提供了原子类,可以保证原子性。

改造上述代码

    private volatile static AtomicInteger num=new AtomicInteger();
    public static void add(){
     //   num++;//不是一个原子性操作
        num.getAndIncrement();//+1方法
    }
    public static void main(String[] args) {
        for (int i = 0; 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(num);
    }

结果:

在这里插入图片描述

3.禁止指令重排

指令重排:程序并不会按照写的顺序来执行,计算机会重排

源代码–>编译器优化的重排–>指令并行也可能重排–>内存系统也会重排—>执行

处理器在进行指令重排的时候,考虑:数据之间的依赖性!

线程A线程B
x=ay=b
b=1a=2

正常的结果:x=0,y=0;但是可能由于指令重排

线程A线程B
b=1a=2
x=ay=b

结果为:x=2,y=1.

volatile可以避免指令重排:

内存屏障。cpu指令。作用:

1.保证特定的操作的执行顺序

2.可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

在这里插入图片描述

25.CAS

CAS是原子类保证其原子性的原理。

CompareAndSet

源码:

在这里插入图片描述

如果期望的值符合则更新并且返回true,反之返回false。

代码举例:

public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);
        //参数1:期望值,参数2:更新的值
        System.out.println(atomicInteger.compareAndSet(1, 2));
        System.out.println(atomicInteger);
        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.compareAndSet(1, 2));
        System.out.println(atomicInteger);
    }

在这里插入图片描述

Unsafe类

在这里插入图片描述

Java无法操作内存

Java可以通过native调用C++操作内存。

Unsafe是java提供的操作内存的后门。

在这里插入图片描述

**CAS:**比较当前工作中内存的值和主内存中的值,如果这个值是期望的,那么就执行操作,如果不是就一直循环。

缺点:

1.循环会耗时。

2.一次性只能保证一个共享变量的原子性。

3.ABA问题。

ABA问题

在这里插入图片描述

26.原子引用

解决ABA问题,引入原子引用,对应的思想:乐观锁

原子引用在CAS基础上添加了版本号,记录对对象的修改操作。

即使内存中的值相同,版本号不同则会返回false

    public static void main(String[] args) {
        //参数1:初始值,参数2:初始版本号
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("a1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //参数:期望值,更新值,期望版本号,更新版本号
            atomicStampedReference.compareAndSet(1,2,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("a2=>"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(2,1,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("a3=>"+atomicStampedReference.getStamp());
        },"a").start();
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("b1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b1").start();
    }

在这里插入图片描述

27.各种锁

27.1.公平锁,非公平锁

公平锁:不允许插队,先来后到

非公平锁:允许插队。

非公平锁比公平锁效率更高,例如两个人上厕所,一个大便一个小便,虽然大便的先来,但是小便的先上厕所效率更高。

synchronized 是非公平锁

lock默认非公平锁,但可以手动设置

 public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

27.2.可重入锁

拿到了外面的锁,就可以拿到里面的锁,自动获得。

synchronized

public class Demo01 {
    public static void main(String[] args) {
        Phone1 phone1 = new Phone1();
        new Thread(()->{
            phone1.sms();
        }).start();
        new Thread(()->{
            phone1.sms();
        }).start();
    }
}
class Phone1 {
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"sms");
        call();//这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-heEKiZs2-1602919849112)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201017151932985.png)]

lock

class Phone1 {
    private ReentrantLock lock = new ReentrantLock();
    public  void sms(){
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"sms");
        call();//这里也有一把锁
        lock.unlock();
    }
    public  void call(){
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"call");
        lock.unlock();
    }
}

结果一样。

27.3.自旋锁

spinLock

当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环

自定义一个锁测试

public class SpinLockDemo {
    //int 0, Thread null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==》myLock");
        //自旋锁,如果获取不到锁就一直循环等待
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==》myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}
    public static void main(String[] args) throws InterruptedException {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        },"t1").start();
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        },"t2").start();

    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cHUjiYa-1602919849112)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201017152957717.png)]

t2必须等待t1将锁释放后才能执行解锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值