多线程高并发知识

1.线程实现方式
1继承Thread类

定义Thread类的子类,并重写Thread类的run()方法,创建子类对象(即线程对象),调用线程对象的start()方法来启动该线程
2.实现Runnable接口
并重写该接口的run()方法,该run()方法同样是该线程的执行体。创建该Runnable实现类的实例,并将此实例作为Thread的target(即构造函数中的参数)来创建Thread对象(该Thread对象才是真正的线程对象,只是该Thread对象负责执行其target的run()方法)。最后调用线程对象的start()方法来启动该线程
3 使用Callable
4通过线程池创建线程

**2线程池的了解、原理 消息队列的可靠性怎么保证 创建线程的三种方式
线程池作用?
1、提高效率:线程的创建和销毁对于系统资源的消耗是比较大的,线程池创建好一定数量的线程,来任务的时候,从线程池创建好的线程获取线程,省去了创建和销毁线程的过程

2、方便管理:编写线程池管理代码对线程池中的线程进行统一管理,比如缓存队列,任务到达线程数上限时,加入缓存队列排队等候,避免无限制的创建线程导致系统崩溃,
为什么要用线程池
降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控

消息队列的可靠性怎么保证
在这里插入图片描述
2.Fork/Join
Fork/Join Pool采用优良的设计、代码实现和硬件原子操作机制等多种思路保证其执行性能。其中包括(但不限于):计算资源共享、高性能队列、避免伪共享、工作窃取机制
创建线程的方法 平时有接触多线程吗?进程和线程状态
进程
进程的5种状态:创建态、就绪态、运行态、阻塞态、终止态

创建态完成创建进程的一系列工作进入就绪态。

就绪态除处理机外的其他条件都已具备,等待进程被调度则可进入运行态,若时间片已到或处理机被抢占进程将返回就绪态。

运行态中的进程用“系统调用”的方式申请系统某种资源,或等待某个事件发生时,进程进入阻塞态。该过程时进程主动行为。(运行态----->阻塞态具有单向性)

阻塞态中的进程若其申请的资源得到分配,或等待事件已发生,可重新进入就绪态(阻塞态------>就绪态具有单向性)
线程
线程可有6中状态:新创建、可运行、被阻塞、等待、计时等待、被终止

3wait和sleep啥区别
在这里插入图片描述
sleep()方法正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!)。

wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);

2、sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;

3、sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;
4线程池,各种细节,参数,原理,阻塞队列,拒绝策略
五种线程池:
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多
四种拒绝策略:

RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
  • 1
  • 2
  • 3
  • 4
  • 5

三种阻塞队列:

BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
  • 1
  • 2
  • 3
  • 4

5三个线程ABC,怎样保证顺序执行(我说了join,信号量,最后让我用锁实现下)
使用CountDownLatch
使用Atom原子类 AtomicInteger
等待队列Condition唤醒部分线程,使用ReentrantLock进行加锁。

6synchronized 和lock 啥区别,原理,公平锁与非公平如何实现(说到AQS)
在这里插入图片描述
来源:
lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

异常是否释放锁:
synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

是否响应中断
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作
Synchronized修饰普通同步方法:锁对象当前实例对象;
Synchronized修饰静态同步方法:锁对象是当前的类Class对象;
Synchronized修饰同步代码块:锁对象是Synchronized后面括号里配置的对象,这个对象可以是某个对象(xlock),也可以是某个类(Xlock.class);

7synchronized 锁升级
在这里插入图片描述

8volatile作用,实现原理
1防止重排序
先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:

(1)分配内存空间。

(2)初始化对象。

(3)将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

(1)分配内存空间。

(2)将内存空间的地址赋值给对应的引用。

(3)初始化对象
  因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
实现可见性
可见性的意思是,当一个线程修改一个共享变量时,另外一个线程能读取到修改以后的值

可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题
  
9 cas 原理,cas产生的问题(ABA,占用cpu) 乐观锁和悲观锁的区别
什么是CAS
CAS即Compare And Swap的缩写,翻译成中文就是比较并交换,其作用是让CPU比较内存中某个值是否和预期的值相同,如果相同则将这个值更新为新值,不相同则不做更新,也就是CAS是原子性的操作(读和写两者同时具有原子性),其实现方式是通过借助C/C++调用CPU指令完成的,所以效率很高。

ABA的解决方案:利用类似数据库乐观锁的机制,把每次更新操作都对应一个版本号,线程A去更新的时候,不光要判断当前线程的缓存值和主存的值是否一样,还要判断他拿到的版本号是否一致,AtomicStampedReference可以用来解决这个问题AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
CAS原理
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

CAS缺点:
CPU开销较大,多线程反复尝试更新某一个变量的时候容易出现;
不能保证代码块的原子性,只能保证变量的原子性操作;
ABA问题。
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

10 是死锁,怎么防止死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,永远在互相等待的进程称为死锁进程
面对如何避免死锁这个问题,我们只需要这样回答! : 在并发程序中,避免了逻辑中出现复数个线程互相持有对方线程所需要的独占锁的的情况,就可以避免死锁。

11进程/线程通信的方式
进程:管道( pipe ):有名管道 (namedpipe) :信号量(semophore ) :消息队列( messagequeue ) :信号 (sinal ) :共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
线程间的通信方式
锁机制:包括互斥锁、条件变量、读写锁
信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
信号机制(Signal):类似进程间的信号处理

12进程和线程的区别,进程、线程的通信方式有哪些 .死锁产生的必要条
件,产生死锁的解决措施。

们都能提高程序的并发度,提高程序运行效率和响应时间。线程和进程在使用上各有优缺点。 线程执行开销比较小,但不利于资源的管理和保护,而进程相反。同时,线程适合在SMP机器上运行,而进程可以跨机器迁移。

他们之间根本区别在于 多进程中每个进程有自己的地址空间,线程则共享地址空间。所有其他区别都是因为这个区别产生的。比如说:

  1. 速度。线程产生的速度快,通讯快,切换快,因为他们处于同一地址空间。
  2. 线程的资源利用率好。
  3. 线程使用公共变量或者内存的时候需要同步机制,但进程不用。

13死锁产生的必要条件,产生死锁的解决措施。

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

死锁的解除:

一旦检测出死锁,就应立即釆取相应的措施,以解除死锁。死锁解除的主要方法有:

  1. 资源剥夺法。挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。

  2. 撤销进程法。强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。

  3. 进程回退法。让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。

2.消息队列的可靠性怎么保证
在这里插入图片描述

了解高并发吗,多线程里怎么保证线程安全
在这里插入图片描述
Fork/Join
Fork/Join就是利用了分治的思想组建的框架,平日里很多场景都能利用到分治思想。框架的核心ForkJoinPool,因为含有任务队列和窃取的特性所以能更好的利用资源。

 

 

多线程

1. 进程和线程之间有什么不同?

  进程是一个独立的运行环境,它可以被看作是一个程序或者一个应用。而线程是在进程中执行的一个任务。进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能。好比Java运行环境是一个(包含了不同的类和程序的)单一进程。

想理解的更深刻,请点击进程与线程的区别.

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

    1) start()被用来启动新的线程,run()不能。

  2)start()不能被重复调用,run()可以。

  3)start()中的run代码可以不执行完就继续执行下面的代码,即线程转换,如果直接调用run()必须等待其代码全部执行完才能继续执行下面的代码。

  4)start()实现了多线程,run()没有实现多线程。

3.  在多线程中,什么是上下文切换?

  上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。是多任务操作系统和多线程环境的基本特征。

4. Java中的volatile 变量是什么?

   volatile是一个特殊的修饰符,只有成员变量(类的成员变量、类的静态成员变量)才能使用它。

被volatile修饰之后就具备了两层语义:

  1)保证了不同线程对这个变量进行操作时的可见性(即一个线程修改了某个变量的值,这新值对其他线程来说是即刻可见的)。

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

5. Java中堆和栈有什么不同?(相对于线程来说)

  栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。

  堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

6.  什么是线程池? 为什么要使用它?

  创建线程要花费资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

7. 死锁是什么?如何避免死锁?

  死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

1)互斥条件:一个资源每次只能被一个进程使用。

2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

  避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

8. Thread类中的yield方法有什么作用?

  Thread.yield() 方法会使当前线程从运行状态变为就绪状态,把运行机会让给其它相同优先级的线程。它是一个静态的原生(native)方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能会被再次继续执行的。

9. Java中notify 和 notifyAll有什么区别?

  调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒。

10.  Java中interrupted 和 isInterruptedd方法的区别?

  interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。

  Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

11. Java多线程中调用wait() 和 sleep()方法有什么不同?

  sleep()和wait()都是使线程暂停执行一段时间的方法。二者区别为:

1)原理不同。

  sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,它会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动苏醒。而wait()方法是Object类的方法,用于线程间的通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程用调用notify()或notifyAll()时才苏醒过来,开发人员也可以给它指定一个时间使其自动醒来。

2)对锁的处理机制不同。

  由于sleep()方法的主要作用是让线程暂停一段时间,时间一到则自动恢复,不涉及线程间的通信,因此调用sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。而wait()方法则不同,当调用wait()方法后,线程会释放掉它所占用的锁,从而使线程所在对象中的其他synchronized数据可被别的线程使用。

3)使用区域不同。

  wait()方法必须放在同步控制方法或者同步语句块中使用,而sleep方法则可以放在任何地方使用。sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用它的interrupt(),产生InterruptedException异常。

  由于sleep不会释放锁标志,容易导致死锁问题的发生,一般情况下,不推荐使用sleep()方法,而推荐使用wait()方法。

 12. 有三个线程T1,T2,T3,怎么确保它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

13.  如何创建守护线程?

  使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。

复制代码

 

/**
 * @author liao.wenhui
 * @date 2019/7/15 15:13
 */
public class DaemonThread {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });

        //设置守护线程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}

 

复制代码

前提知识:

  守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件(百度百科)。

  Java线程分为两类分别为daemon线程(守护线程)和User线程(用户线程),在JVM启动时候会调用main函数,main函数所在的线程是一个用户线程,这个是我们可以看到的线程,其实JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别那?区别之一是当最后一个非守护线程结束时候,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意是只要有一个用户线程还没结束正常情况下JVM就不会退出。

14. 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

  线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。

  时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(即最好不要让你的程序依赖于线程的优先级)。

15.什么是ThreadLocal?

  ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。每个线程都会拥有他们自己的Thread变量,它们可以使用get()/set()方法去获取他们的默认值或者在线程内部改变他们的值。

16.Java线程池中submit() 和 execute()方法有什么区别?

  两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

17.  Java中Runnable和Callable有什么不同?

  Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回任务执行结果值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

高并发

1. 什么是FutureTask?

  在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值