并发线程

本文详细介绍了Java并发编程中的线程和锁机制,从基础概念到高级用法,如线程的上下文切换、死锁避免、线程池的工作原理、各种锁的实现和优化,以及volatile关键字的使用。通过实例和对比分析,阐述了synchronized与ReentrantLock的区别,以及如何在实际开发中选择合适的同步策略。此外,还探讨了原子类在并发控制中的作用,如AtomicInteger和AtomicReference等。
摘要由CSDN通过智能技术生成

基础部分1

基础

什么是线程和进程

**进程是程序的一次执行过程,是系统运行程序的基本单位,**因此线程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。在java中,当我们启动main函数时其实就是启动了一个jvm的进程,而main函数所在的线程就是这个进程冲的一个线程,也称为主线程。
线程和进程像是,但是线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
总结: 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
程序计数器主要有下面两个作用:

程序计数器为什么是私有的?

字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。

虚拟机栈和本地方法栈为什么是私有的?

虚拟机栈: 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

一句话简单了解堆和方法区

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

什么是上下文切换?

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

什么是线程死锁?如何避免死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
产生死锁必须具备以下四个条件:
互斥条件:该资源任意一个时刻只由一个线程占用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁?
破坏互斥条件 :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件 :一次性申请所有的资源。
破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

说说 sleep() 方法和 wait() 方法区别和共同点?

两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
两者都可以暂停线程的执行。
wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。

为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!

new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

线程池

池化技术相比大家已经屡见不鲜了,线程池,数据库连接池,Htpp连接池等等都是这个思想的应用。池化技术的思想主要是为了减少可以再次获取资源的消耗,提高对资源的利用率。
线程池提供了一种限制和管理资源。每个线程池还维护一些基本统计信息,例如已完成任务的数量。好处就是降低资源消耗、提高响应速度、提高线程的可管理性。

实现runnable接口和callable接口的区别

Runnable自 Java 1.0 以来一直存在,但Callable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Runnable 接口不会返回结果或抛出检查异常,但是Callable 接口可以。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口,这样代码看起来会更加简洁。

execute和submit有什么区别?

1.execute()方法用于提交不需要返回值得任务,所以无法判断任务是否被线程池执行成功与否
2.submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功。

JUC常用的工具类

JUC就是java.util .concurrent工具包的简称
CountDownLatch,CyclicBarrier,semaphore

在这里插入图片描述

semaphore 信号量
semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release(); 释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数

线程实现的四种方式

1、继承Thread类重写run方法
2、实现runnable实现run方法
3、集成callable接口 实现call方法 使用futureTask调用(有返回值、可处理异常)
4、线程池(线程复用,控制最大并发数,管理线程)—常用
a)低资源消耗:通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
b)提高响应速度
c)提高线程的可管理性
线程池的业务流程如下图:
在这里插入图片描述

线程的四种类型

1、newcachedThreadPool 创建一个可根据需要创建新线程的线程池
2、NewFixedThreadPool创建一个可重用固定线程数的线程池
3、NewScheduledThreadPool 创建一个线程池,她可以安排在给定延迟后运行命令或定期执行
4、NewSingleThreadExecutor 返回一个线程池(这个线程池只有一个线程),这个线程池在线程死后重新启动一个线程替代原来的线程继续执行下去

如何停止一个正在运行的线程

1)使用退出标志,正常退出
2)使用stop方法强行终止,
3)使用interupt方法中断线程
4)正常运行,线程自动结束

ThreadPoolExecutor

七大参数分析
1)corepoolSize线程中的常驻核心线程数
2)maxmunpoolsize能够容纳同时执行的最大线程数,必须大于1
3)keepaliveTime多余空闲线程的存活时间。当前池中线程数量超过corepoolSize时或者当空闲时间达到keepaliveTime时,多余线程会被直接销毁直到剩下corePoolSize个线程为止(过剩策略)
4)TimeUnit unit ,keepAliveTime的单位
5)BlockingQueue workQueue 任务队列,被提交但是尚未被执行的任务
6)ThreadFactory 标识生成线程池中的工作现场的线程工厂,直接使用默认
7)RejectedExecutionHandler 拒绝策略;表示当前队列满了并且工作线程大于等于线程池的最大线程数时是如何来拒绝请求执行的runnable的策略

拒绝策略:
1)abortPolicy:直接抛出RejectedExcutionException异常阻止系统正常运行
2)callerRunsPolicy:调用者运行 一种调节机制,不会抛出任务也不会抛出异常,而是将某些任务回退到调用者,从而降低新的任务流量
3)DiscardOldesPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
4)DiscardPolicy 直接丢弃任务,不予任何处理也不排除异常。如果任务丢失,这是最好的一种方案

队列的分类
1)arrayBlockingQueue 一个由数组结构组成的有界阻塞队列,先进先出,那么被阻塞在外面的
2)linkedBlockingQueue基于链表结构的队列、有界队列、先进先出
3)synchronousQueue 不存储元素的阻塞队列。每个插入操作必须要等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态 吞吐量要高于linkedBlockingQueue
4)PriorityBlockingQueue 一种具有优先级的无限阻塞队列
5)DelayQueue:一个使用优先级队列实现的无界阻塞队列

Sleep和wait的区别

1、对于sleep方法是属于thread类中的,

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值