基于面试和学习顺序的Java并发、多线程

Java并发

进程:程序的一次执行,系统进行资源分配的最小单位。进程切换涉及新分配以及回收资源空间,耗费处理器时间较多

线程:CPU执行计算调度的基本单位,一个进程包含一个或多个线程,它们共享进程资源

进程状态

创建:向OS申请空白PCB,填入进程id、控制信息等

就绪:等待CPU调度即可执行

执行

阻塞:在某些事件发生前不能继续执行如I/O操作

死亡

(挂起:内存不足,就绪的或阻塞的进程放入磁盘,等待载入内存、载入内存和事件发生)

线程状态

[å¾ç]

引用 https://i-blog.csdnimg.cn/blog_migrate/96163b4bef1fbff0ac39755c0ca6e982.png

JVM对线程状态的实现:

  • Thread t=new Thread()创建线程
  • t.start()就绪状态
  • 执行run()方法体为执行状态
  • (不确定)等待monitor lock的Blocked状态,等待notify()的waiting状态,Thread.sleep()(不释放锁)导致的waiting-timed状态对应阻塞
  • run()方法体执行完对应线程销毁

多线程创建方式

1. 继承Thread类,重写run方法

public class MyThread extends Thread{
       @Override
       public void run(){
           //实现
       }
   }
   psvm(String[] args){
       MyThread t1=new MyThread();
       //
       t1.start();
   }

2. 实现Runnable接口,重写run方法(lambda表达式使得更方便new Thread(()->{System.out.print("Success!"}).start())

 public class MyThread implements Runnable{
       @Override
       public void run(){
           //实现
       }
   }
   psvm(String[] args){
       MyThread t1=new MyThread();
       Thread t1=new Thread(t1);
       //
       t1.start();
   }

线程中断涉及到interrupt()、interrupted()、isInterrupted()

1.interrupt()是给线程设置一个中断标志位,如果线程正处于休眠时调用interrupt()会抛出异常,同时会把中断标志位清除。interrupt()只是把线程的中断标志改成true,并不会对线程的状态进行改变。
2.interrupted()是静态方法,可以使用Thread直接调用,它用来判断线程是否含有中断标志位,同时它会把中断标志位清除。
3.isInterrupted()方法也可以用来判断线程是否含有中断标志位,它不会清除中断标记位

当收到了中断请求后,如何结束该线程呢?

return或更优雅的方式则是:抛出InterruptedException异常
 

线程池

接口:Executor基础接口-->ExecutorService接口(提供了线程池生命周期管理的方法)
—>AbstractExecutorService抽象类提供默认实现-->ThreadPoolExcutor类

重要参数:

  • 核心线程数corepoolsize:如果运行的线程少于corepoolsize则创建新线程处理请求,即使有其它空闲线程
  • 最大线程数maximumpoolsize:大于corepoolsize,小于maximumpoolsize,加入任务队列,仅当队列满时才创建新线程;超过max根据任务拒绝策略(报错、扔。。)
  • 如果core和max相同则创建固定大小的线程池
  • 阻塞队列大小
  • 线程空闲时间:如果线程数大于core,空闲线程时间>this则销毁线程

常见实现:newfixedthreadpool固定大小
                  newcachedthreadpool根据新任务创建新线程
                  newsinglethreadexcutor单个线程

BlockingQueue:ArrayBlockingQueue(有界队列实现类,底层采用数组来实现。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。)、
SynchronousQueue(当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走;同理,当一个读线程做读操作的时候,同样需要一个相匹配的写线程的写操作。这里的 Synchronous 指的就是读线程和写线程需要同步,一个读线程匹配一个写线程。不像ArrayBlockingQueue、LinkedBlockingDeque之类的阻塞队列依赖AQS实现并发操作,SynchronousQueue直接使用CAS实现线程的安全访问)、
DelayQueue()

ThreadLocal

创建hreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。内部是ThreadLocalMap,键为线程对象,值为变量副本。

jmm

每个线程拥有其工作内存,主内存中存在线程共享的数组数据、实例变量、静态变量等,线程读取主内存数据并在工作内存创建一个数据副本,对数据副本进行操作并在适当的时候写回内存。

volatile关键字

实现变量的可见性、禁止指令重排序。happens-before规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读。
  

synchronized

synchronized可用于代码块和方法(静态方法锁住类对象,实例方法锁住实例对象)

图来自 https://www.jianshu.com/p/d53bf830fa09

原理:Synchronized实现线程同步,关键是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。获取的过程互斥,同一时刻只有一个线程能够获取到monitor。同时每个对象有个计数器,进入代码块的线程执行monitorenter命令计数器加1,完毕执行monitorexit指令计数器减1,为0时真正释放锁,即为可重入。

释放锁的时候会将值刷新到主内存中,其他线程获取锁时会强制从主内存中获取最新的值,完成可见性。

优化

对象头记录线程id,没有则cas替换,替换失败撤销偏向锁。参考上文图片获取链接讲解。

 

Lock锁接口


ReentrantLock
需手动释放锁(标准用法是try之前lock,finally中unlock)、ReentrantLock可以实现公平锁(加了一个判断,是否是同步队列中的第一个)
state变量----》CAS----》失败后进入等待队列----》释放锁后唤醒

ReentrantReadWriteLock
读写锁,读取数据时可以多个线程同时进入到临界区
写数据时无路是读数据还是写数据都是互斥的

 

AQS:Abstract Queue Synchronizer 抽象队列同步器

是什么?比如ReadWriteLockReentrantLock等常用的锁,都是通过内部类来实现该抽象,从而实现锁功能

核心变量:

1、state(volatile的):尝试用CAS方式去更新state=1

2、等待队列:线程2加锁失败,进入等待队列,挂起

独占式:需要子类实现的方法为tryAcquire、tryRelease;涉及方法有:
acquire:AQS实现的方法,用于阻塞式获取资源。
tryAcquire:子类需要实现的方法,用于获取资源的具体实现。
release:AQS实现方法,释放资源。
tryRelease:子类实现,释放资源的具体实现。

共享式:需要子类实现的方法为tryAcquireShared、tryReleaseShared;涉及方法有:
acquireShared:AQS实现的方法,用于阻塞式获取共享资源。
tryAcquireShared:子类需要实现的方法,用于获取共享资源的具体实现。
releaseShared:AQS实现方法,释放共享资源。
tryReleaseShared:子类实现,释放共享资源的具体实现。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值