JAVA多线程和JUC

1.程序、进程、线程关系?

1.程序(Process)是一个静态的概念,是指令和数据的有序集合。
2.进程是一个动态的概念,程序运行就是一个进程。进程是操作系统分配资源的基本单位。
3.线程(Thread)是CPU调度和执行的单位。
4.进程和线程是包含与被包含的关系,一个进程可以有多个线程。JAVA中一个进程至少拥有两个线程(main/gc线程)。

2.创建线程的多种方式

1.继承Thread类
缺点:java是单继承,使用继承方式后代码耦合

public class CreatThread1 extends Thread{

    @Override
    public void run() {
        for(int i=0;i<200;i++){
            System.out.println("线程"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CreatThread1 thread = new CreatThread1();
        thread.start();
        for(int i=0;i<1000;i++){
            System.out.println("主线程"+i);
        }

    }
}

2.实现Runnable接口

public class CreateThread2 implements  Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("线程"+i);
        }
    }

    public static void main(String[] args) {
        CreateThread2 thread = new CreateThread2();
        new Thread(thread).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程"+i);
        }
    }
}

3.实现Callable接口
这种方式是对于Runnable的升级版,Runnable创建的线程没有返回值,而Callable有返回值,而且多了其他操作。

4.基于四种线程池的创建方式

public class TestThreadPoolExecutor {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//一个线程,不建议使用
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//指定线程个数,不建议使用
//        ExecutorService threadPool = Executors.newCachedThreadPool();//根据内存而定线程个数,不建议使用
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,//一般线程池可以有几个
                Runtime.getRuntime().availableProcessors(),//最大连接个数,一般是当前电脑的核数
                3,//超时时间
                TimeUnit.SECONDS,//超时单位
                new LinkedBlockingDeque<>(3),//线程池已满,在外面最多可以等待进入线程池的个数
                Executors.defaultThreadFactory(),//默认工程,一般设置这个就行,固定
                new ThreadPoolExecutor.DiscardPolicy());//指定线程的拒绝策略
        try {
            for (int i = 0; i < 100; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

3.怎么让线程停止

不太推荐jdk中的stop方式,stop已经被弃用,如果先前受这些监视器保护的任何对象处于不一致的状态,则损坏的对象将变得对其他线程可见,可能导致任意行为。推荐使用自定义标志位方式来使线程停止。

public class ThreadStop implements Runnable {
    //1.设置标志位
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run...Thread"+i++);
        }
    }
    //2.设置一个公开的方法停止线程
    public void stop(){
        this.flag = false;
    }
    public static void main(String[] args) {
        ThreadStop threadStop = new ThreadStop();
        new Thread(threadStop).start();
        //主线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("main.."+i);
            if (i==900){//当主线程中的i=900时,run线程停止
                threadStop.stop();
                System.out.println("run线程停止了");
            }
        }
    }
}

4.notify()和notifyAll()有什么区别?

1、notify()只能唤醒一个wait()线程,然而notifyAll()可以唤醒多个wait()线程;

2、两个都必须在synchronized中使用,过程不释放锁;

3、当每个线程都有特定锁的时候,只有等待这个锁的线程才能被唤醒,也就是线程2的notify()或notifyAll()不能唤醒线程1的wait();

5.sleep()和wait()有什么区别?

​1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。​

​2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。​

​3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。

6.volatile是什么?如果不加lock和synchronized ,怎么样保证原子性?

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。

2)禁止进行指令重排序。

3)volatile 不是原子性操作

4)可以使用原子类,如AtomicInteger

7.Thread类中的start()和run()方法有什么区别?

start()方法: 它会启动一个新线程,并将其添加到线程池中,待其获得CPU资源时会执行run()方法,start()不能被重复调用。

run()方法:它和普通的方法调用一样,不会启动新线程。只有等到该方法执行完毕,其它线程才能获得CPU资源。

8.为什么wait和notify方法要在同步块中调用?

wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。.

9.简述一下你对线程池的理解。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

10.Semaphore信号量

Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。 Semaphore 可以用来构建一些对象池,资源池之类的, 比如数据库连接池实现互斥锁(计数器为 1)我们也可以创建计数为 1 的 Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。

11.ArrayBlockingQueue

用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。 默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。

12.什么是Callable和Future?

Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而 Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。可以认为是带有回调的Runnable。

Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。

13.并发和并行

并发: 多线程操作同一个资源(并发编程—多个线程操作同一个资源类), CPU 只有一核,可以使用CPU快速交替, 模拟出来多条线程

并行:CPU多核,多个线程可以同时执行

14.Synchronized 与Lock

  • Synchronized 是内置的Java关键字,Lock是一个Java类
  • Synchronized 无法判断获取锁的状态,Lock可以判断
  • Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
  • Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
  • Synchronized 是非公平的;Lock可以自己设置公平锁和非公平锁;
  • Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

15.CopyOnWriteArrayList

使用ArrayList,当多个线程同时写入数据时会产生java.util.ConcurrentModificationException异常(并发修改异常)
解决并发数组问题:
1.使用vector代替arrylist,List list = new Vector<>();
2.使用collections工具类,List list = Collections.synchronizedList(new ArrayList<>());
3.使用CopyOnWrite,List list = new CopyOnWriteArrayList<>();

16.CountDownLatch

相当于减法器

public class TestCountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"线程已完成");
                countDownLatch.countDown();//数量-1
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归零,然后再向下执行
        System.out.println("线程全部执行完毕");
    }
}

17.CyclicBarrier

相当于加法器

public class TestCyclicBarrier {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("7个线程都已完成");
        });
        for (int i = 0; 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();

        }
    }
}

18.ReadWriteLock

读写锁允许访问共享数据时的并发性高于互斥锁所允许的并发性。 它利用了这样一个事实:一次只有一个线程( 写入线程)可以修改共享数据,在许多情况下,任何数量的线程都可以同时读取数据(因此读取器线程)。

19.BlockingQueue

BlockingQueue方法有四种形式,具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足:一个抛出异常,第二个返回一个特殊值( null或false ,具体取决于操作),第三个程序将无限期地阻止当前线程,直到操作成功为止,而第四个程序块在放弃之前只有给定的最大时限。
在这里插入图片描述

20.线程池(三大方法、7大参数、4种拒绝策略)

池化技术包括:线程池、JDBC的连接池、内存池、对象池等

池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后再归还,以此来提高效率

线程池的好处:降低资源的消耗;提高响应的速度;方便管理;线程复用、可以控制最大并发数、管理线程;

public class TestThreadPoolExecutor {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//一个线程,不建议使用
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//指定线程个数,不建议使用
//        ExecutorService threadPool = Executors.newCachedThreadPool();//根据内存而定线程个数,不建议使用
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,//一般线程池可以有几个
                Runtime.getRuntime().availableProcessors(),//最大连接个数,一般是当前电脑的核数
                3,//超时时间
                TimeUnit.SECONDS,//超时单位
                new LinkedBlockingDeque<>(3),//线程池已满,在外面最多可以等待进入线程池的个数
                Executors.defaultThreadFactory(),//默认工程,一般设置这个就行,固定
                new ThreadPoolExecutor.DiscardPolicy());//指定线程的拒绝策略
        try {
            for (int i = 0; i < 100; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

21.四大函数式接口

函数式接口、断定型接口、消费型接口、供给型接口

22.流式计算

public class TestStream {
    public static void main(String[] args) {
        User1 u1 = new User1(1, "a", 21);
        User1 u2 = new User1(2, "b", 22);
        User1 u3 = new User1(3, "c", 23);
        User1 u4 = new User1(4, "d", 24);
        User1 u5 = new User1(5, "e", 25);
        List<User1> list = Arrays.asList(u1, u2, u3, u4, u5);
        list.stream().filter(u->{return u.getId()%2==0;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
        //计算0-1000000000的和
        System.out.println(LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum));
    }
}

23.ForkJoin

ForkJoin 工作特点:工作窃取!

原理是双端队列!从上面和下面都可以去进行执行!,当B线程完成后,B线程回去执行A线程未完成的任务,提高效率。

24.异步回调

即Future,设计的初衷: 对将来的某个事件的结果进行建模,类似ajax
1.没有返回值的runAsync异步回调

public class FutureTest1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  runAsync--->void");
        });
        System.out.println("111111");
        System.out.println(voidCompletableFuture.get());   // 获取异步执行结果
    }
}

2.有返回值的异步回调supplyAsync

public class supplyAsyncTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
            int i = 10 / 0;
            return 1024;
        });

        //设置integerCompletableFuture的回调
        System.out.println(integerCompletableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t);// 成功结果
            System.out.println("u=>" + u);// 错误信息:
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233;
        }).get());  //获取回调成功或者失败的返回结果
    }
}

25.自旋锁

26.什么是多线程中的上下文切换?

1、定义 CPU处理器给每个线程分配 CPU 时间⽚(Time Slice),线程在分配获得的时间⽚内执⾏任务。当⼀个线程被暂停或剥夺CPU的使⽤权,另外的线程开始或者继续运⾏的这个过程就叫做上下⽂切换 。

2、上下⽂切换发⽣的时机

  • 线程被分配的时间⽚⽤完
  • 使⽤synchronized或lock等也会导致上下⽂切换

27.Java内存模型是什么?

JMM就是Java内存模型(java memory model) 因为在不同的硬件⽣产商和不同的操作系统下,内存的访问有⼀定的差异,所以会造成相同的JAVA代码 运⾏在不同的系统上会出现各种问题。所以java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到⼀致的并发效果。
内存模型定义了共享内存系统中多线程程序读写操作的行为规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。

28.什么是原⼦操作?在JUC中有哪些原⼦类 ?

原⼦操作是指⼀个不受其他操作影响的操作任务单元。原⼦操作是在多线程环境下避免数据不⼀致必须 的⼿段。

原⼦操作类: AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference等等!

29.什么是CAS操作,缺点是什么?

CAS 操作:Compare & Swap或是 Compare & Set 在程序中我们使⽤CAS+⾃旋的⽅式就可以实现原⼦操作
CAS的缺点:
1、只能⽀持⼀个变量的原⼦操作
2、CAS频繁失败导致CPU开销过⾼
3、ABA问题

30volatile 变量和 atomic 变量有什么不同?

volatile解决的是多线程可⻅性问题
Atomic解决的是多线程安全问题

32.乐观锁和悲观锁的理解及如何实现,有哪些实现⽅式?

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别⼈会修改,所以每次在拿数据的时候都会上锁,这样别⼈想拿这个数据就会阻塞直到它拿到锁。Java⾥⾯的同步原语synchronized 关键字的实现是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别⼈不会修改,所以不会上锁,但是在更新的时候会判断⼀下在此期间别⼈有没有去更新这个数据,可以使⽤版本号等机制。在 Java中的原⼦变量类就是使⽤了乐观锁的⼀种实现⽅式CAS实现的。

35.什么是不可变对象,它对写并发应⽤有什么帮助?

不可变对象即对象⼀旦被创建它的状态就不能改变。 Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。 不可变对象天⽣是线程安全的。
不可变对象特点:
它的状态不能在创建后再被修改; 所有域都是 final 类型;它被正确创建(创建期间没有发⽣ this 引⽤的逸出)。

36.为什么wait, notify 和 notifyAll这些⽅法不在thread类 ⾥⾯?

JAVA提供的锁是对象级的⽽不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调⽤对象中的wait()⽅法就有意义了。如果wait()⽅法定义在Thread类中,线程正在等 待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

37.⽣产者消费者模型的作⽤是什么?

(1)通过平衡⽣产者的⽣产能⼒和消费者的消费能⼒来提升整个系统的运⾏效率,这是⽣产者消费者 模型最重要 的作⽤ (2)解耦,这是⽣产者消费者模型附带的作⽤,解耦意味着⽣产者和消费者之间的联系少,联系越少 制约就越少

38.CopyOnWriteArrayList可以⽤于什么应⽤场景?

CopyOnWriteArrayList 是 java.util.concurrent 包提供的⽅法,它实现了读操作⽆锁,写操作则通过操作底层数组的新副本来实现,是⼀种读写分离的并发策略。

CopyOnWrite并发容器⽤于对于绝⼤部分访问都是读,且只是偶尔写的并发场景。⽐如⽩名单, ⿊名单,商品类⽬的访问和更新场景。 透露的思想 ● 读写分离● 最终⼀致性 ● 使⽤另外开辟空间的思路,来解决并发冲突

39.请概述下AQS

AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器, 比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基于 AQS 的。当然,我们自己也能利用 AQS 非常轻松容易地构造出符合我们自己需求的同步器。
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒 时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

猜你喜欢

转载自blog.csdn.net/qq_44954571/article/details/125613036