Java并发知识

参考文章1
参考文章2

1、线程和进程

1.1 何为进程?

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

1.2 何为线程?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。

2、进程和线程区别

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

线程切换后能恢复到正确的执行位置。

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

为了保证线程中的局部变量不被别的线程访问到。

虚拟机栈为虚拟机执行 Java 方法 (也就是字节码),而本地方法栈则为虚拟机使用到的 Native 方法服务。

3、悲观锁和乐观锁

参考文章

3.1 悲观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

3.2 乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

3.2.1 乐观锁常见的两种实现方式

3.2.1.1 版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

3.2.1.2 CAS算法

即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数:

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

3.2.2 乐观锁的缺点

3.3.2.1 ABA 问题

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

3.3.2.2 循环时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。

3.3.2.3 只能保证一个共享变量的原子操作

CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。

7. 什么是上下文切换?

PU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

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

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

8.1 线程死锁原因

  1. 系统资源不足
  2. 进程运行顺序不当!
  3. 资源分配不当!

8.2 死锁排查

8.2.1 使用堆栈排查死锁:

jps -l #显示进程号
jstack 进程号 #分析内存信息

在这里插入图片描述

8.2.2 JConsole

在window打开 JConsole,JConsole是一个图形化的监控工具!
在这里插入图片描述

9、volatile 与 synchronized 作用与区别

9.1 volatile

它所修饰的变量不保留拷贝,直接访问主内存中的
在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变 量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。 一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中

9.2 synchronized

当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

  1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  2. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
  3. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
  4. 当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
  5. 以上规则对其它对象锁同样适用.

10、线程池

在这里插入图片描述

10.1 七大参数

public ThreadPoolExecutor(int corePoolSize,  // 核心池子的大小
             int maximumPoolSize,  // 池子的最大大小
             long keepAliveTime,  // 空闲线程的保留时间
             TimeUnit unit,  // 时间单位
             BlockingQueue<Runnable> workQueue, // 队列
             ThreadFactory threadFactory, // 线程工厂,不修改!用来创建线程
             RejectedExecutionHandler handler // 拒绝策略) {
             }

在这里插入图片描述
在这里插入图片描述

10.2 拒绝策略

  1. AbortPolicy (默认的:队列满了,就丢弃任务抛出异常!)
  2. CallerRunsPolicy(哪来的回哪去? 谁叫你来的,你就去哪里处理)
  3. DiscardOldestPolicy (尝试将最早进入队列的任务删除,尝试加入队列,替换)
  4. DiscardPolicy (队列满了任务也会丢弃,不抛出异常)

10.3 线程池用哪个?生产中如何设置合理参数

  1. 会导致 OOM(OutOfMemory) 默认大小 integer最大值
  2. 阿里巴巴开发手册要求的

10.4 最大参数该如何设置?

  1. CPU 密集型:根据CPU设置
//获取 CPU 数量
Runtime.getRuntime().availableProcessors();
  1. IO 密集型:磁盘读写、 一个线程在IO操作的时候、另外一个线程在CPU中跑,造成CPU空闲。最大线程数应该设置为 IO任务数! 大文件读写耗时!单独的线程让他慢慢跑。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值