Java多线程并发

线程和进程

进程

进程是程序的一次执行过程,是系统运行程序的基本单位。

线程

线程是一个比进程更小的执行单位,一个进程在执行的过程中可以产生多个线程。
同类的多个线程共享进程的方法区的资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈
线程和进程最大的不同在于基本上各进程是独立的,而同一进程中的线程极有可能会互相影响。

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

程序计数器私有是为了多线程情况下,线程切换后能恢复到正确的执行位置。----一个线程对应一个程序计数器

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

虚拟机栈: 每个Java方法在执行的同时会创建一个栈帧用于存储局部变量、操作数栈、常量池引用等信息;本地方 法栈与虚拟机栈相似;所以为了避免多线程的情况下局部变量被其他线程所访问到,采用线程私有。

并发和并行

并发:

两个及两个以上的作业在同一时间段内执行

并行:

两个及两个以上的作业在同一时刻执行

同步和异步

同步:

发出一个调用后,在没有得到结果之前,该调用就不可以返回,一直等待;

异步:

调用发出之后不用等待返回结果,该调用直接返回

多线程可能带来的问题:内存泄漏、死锁、线程不安全

线程的生命周期和状态

New:新建状态
Runnable:运行中状态,包括就绪状态和运行状态;调用start()方法后处于CPU分配资源阶段,当就绪的线程获得CPU资源时进入运行状态。
Waiting:等待状态,执行wait()方法后,进入等待状态的线程需要依靠其他线程的通知才能返回到运行状态。
Timed_Waiting:超时等待状态,相当于在等待状态的基础上增加了超时限制,当超时时间结束后,线程会回到运行中状态
Blocked:阻塞状态,当线程进入synchronized方法/块或者wait被notify后重新进入synchronized方法/块,但锁依然被其他线程占有时进入阻塞状态。(当线程未到获取锁或者调用wait被notify后重新获取锁失败时,进入阻塞状态)
Terminated:终止状态,线程执行完成或异常终止时进入终止状态,销毁线程

上下文切换

当发生线程切换时,需要保存当前线程的上下文,留待线程下次占用CPU的时候恢复现场,并加载下一个将要占用CPU的线程上下文。

线程死锁

多个线程同时被阻塞,它们中的一个或全部都在等待某个资源被释放。
如图:线程A持有资源2,线程B持有资源1,他们都想申请对方的资源,所以两个线程就会互相等待而进入死锁状态。
在这里插入图片描述

产生死锁的四个必要条件

  1. 互斥条件:该资源任意一个时刻只能由一个线程占有
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被剥夺,只能自己使用完毕后才释放资源
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

预防和避免死锁

预防死锁

破坏产生的必要条件即可:

  1. 一次性申请所有资源
  2. 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源
  3. 按序申请资源,释放资源则反序释放。破坏循环等待条件
避免死锁

在资源分配时,借助于算法(比如银行家算法)对资源分配进行算法评估,使其进入安全状态

sleep()方法和wait()方法对比

共同点:都可以暂停线程的执行

区别:
1. sleep()方法没有释放锁,而wait()方法释放了锁。
2.wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法;而sleep()方法执行完成后会自动苏醒。
3.wait()是Object类的本地方法,sleep()是Thread类的静态本地方法;
因为wait操作的是对象(每个对象都有对象锁)而非当前线程,而sleep()是让当前线程暂停,不涉及对象。

可以直接调用Thread类的run方法吗?

不可以
多线程的工作流程:new一个Thread,线程进入新建状态,调用start() 方法,启动线程进入就绪状态,当分配到时间片之后开始执行run()方法。但如果直接执行run方法,会把run()方法当成一个main线程下的普通方法执行,这并不是多线程工作。

volatile关键字

volatile可以保证变量的可见性但不能保证变量操作的原子性

当new一个对象的时候,这一条代码分为三步:

  1. 为对象分配内存空间
  2. 初始化对象
  3. 将对象指向分配的内存地址

由于JVM具有指令重排的特性,执行顺序可能编程1->3->2。指令重排在单线程环境下不会出现问题,但多线程环境下 会导致获得一个没有初始化的实例。

synchronized 关键字

synchronized 中文翻译是同步的意思,主要解决的是多个线程之间访问资源的同步性,保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。(保证了数据的可见性和原子性)

使用方式:

  1. 修饰实例方法(锁当前对象实例):synchronized void method() {}
  2. 修饰静态方法(锁当前类):synchronized static void method() {}
  3. 修饰代码块(锁当前对象/类):synchronized(object/类.class){}

synchronized底层原理

synchronized 同步语句块的实现使用的是monitorentermonitorexit指令,其中monitorenter指令指向同步代码块开始位置,monitorexit指令指向同步代码快结束位置。

在执行monitorenter时会获取对象的锁,如果锁的计数器为0 ,则表示可以被获取,获取后锁计数器+1,在执行monitorexit指令释放后锁计数器-1,当为0时表示其他线程可以获取锁。(可重入锁—即一个线程获取某个对象的锁,还未释放,当其再次获得这个对象的锁时还是可以获得的,每次计数器+1,为零时才释放锁-----与ReentrantLock一样)
(当执行monitorenter指令时线程试图获取锁也就是获取对象监视器monitor的持有权)

synchronized 修饰的方法并没有monitorenter指令和monitorexit指令,取而代之的是ACC_SYNCHRONIZED 标识,这个标识指明该方法是一个同步方法。
(两者的本质都是对 对象监视器monitor的获取)

单例模式:

双重校验锁实现对象单例:
在这里插入图片描述
第一次判空是因为单例模式只创建一次实例,当多次调用getInstance方法时,直接返回之前创建的实例,就可以不再执行同步方法里的代码,提高性能
第二次判空,是为了防止多线程创建多个实例。

静态内部类:
在这里插入图片描述

ThreadLocal

ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,防止自己的变量被其他线程篡改。
在这里插入图片描述

Thread线程可以拥有多个ThreadLocal维护的自己线程独享的共享变量

线程池

线程池提供了一种限制和管理资源的方式。
优点:降低资源消耗;提高响应速度;提高线程的可管理性。

Runnable接口和Callable接口的区别

Runnable接口不会返回结果或抛出异常,但Callable接口可以。
工具类Executors可以实现将Runnable对象转换成Callable对象。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object result))

execute()和submit()方法的区别?

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

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

如何创建线程池

方式一:通过构造方法
方式二:通过Executor框架的工具类Executors来实现
  1. FixedThreadPool:该方法返回一个固定线程数量的线程池;
  2. SingleThreadExecutor:该方法返回一个只有一个线程的线程池;
  3. CachedThreadPool:该方法返回一个可根据实际情况调整线程数量的线程池。

以上三个方法内部实际上是调用了ThreadPoolExecutor的构造方法。

七个重要参数分析

在这里插入图片描述

corePoolSize:核心线程数
核心线程数定义了最小可以同时运行的线程数量,不会被回收
maximumPoolSize:最大线程数
线程池允许创建的最大线程数
workQueue:工作队列
当提交的任务数超过核心线程数后,再提交的任务就存放到工作队列
keepAliveTime:空闲线程存活时间
当线程池中的线程数量大于核心线程池数,直到等待时间超过keepAliveTime才会被回收销毁。
unit:时间单位
keepAliveTime的时间单位
threadFactory:线程工厂
executor创建新线程的时候用到
handler:饱和(拒绝)策略
定义:当线程池线程数已满,并且工作队列达到限制时所定义的一些策略:
ThreadPoolExecutor.AbortPolicy:(默认)抛出RejectedExecutionException来拒绝新任务的处理
**ThreadPoolExecutor.CallerRunsPolicy:**由调用线程处理该任务,会导致数据处理顺序不一致
**ThreadPoolExecutor.DiscardPolicy:**不处理新任务,直接丢弃
**ThreadPoolExecutor.DiscardOldestPolicy:**丢弃最早未处理的任务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值