【Java面试】并发

简介: 【Java面试】并发

线程有那些状态?

操作系统层面将线程状态分为5种,分别是:新建,就绪,运行,阻塞,死亡

但是在Java的Thread类中,线程的状态枚举如下:

也就是Java将线程分为了六种状态:创建,可运行,阻塞,等待,限时等待和终结

新建状态指的是我们使用new方法新建出来一个线程对象的时候,此时Java还没有将其与OS关联起来,那么这个线程就不会被分配CPU去执行代码,只有调用了start方法之后才会与真正的线程关联起来,此时就从新建状态变为了可运行(就绪)状态,等待分配CPU去执行任务,如果CPU分配给了当前线程,当前线程就会执行对应的任务,任务执行完毕之后,当前线程死亡(终结),注意是死亡,此时这个任务对应的线程以及资源都会被释放。

而并不是所有的任务都会被执行,因此会有部分任务进行阻塞,例如多线程访问同一个锁资源的时候,如果当前线程没有得到锁,那么当前线程就需要进行阻塞,此时CPU被分配给得到锁的线程去执行任务,任务执行完毕之后,锁释放,线程再次去争夺锁,得到锁的话就可以从阻塞状态变为可运行状态,此时只需要等待CPU分配资源就可以运行了。

而当前持锁线程可能执行任务后发现有些条件不满足,那么这个线程不能总是占有锁,因此可以选择调用wait方法去释放锁,然后让自己进入等待状态去等待条件的满足,此时其他线程就可以去竞争锁从而执行他们的任务,等到条件满足,就可以由另一个线程调用notify方法去提醒这个线程再次竞争锁去完成任务。

最后一种是等待的一种情况,叫做现时等待,也就是这次等待可以设定时间。

方法是使用wait方法并且设定等待的时间,再次之前如果时间到了或者另一个线程调用了notify方法,那么这个线程就会被唤醒。

还有一种是sleep方法,这种方法和wait不一样,sleep是不会释放当前线程的锁的,其他线程都得陪着这个线程等,这个线程等待完毕之后在继续执行接下来的任务,调用了sleep方法相对于是告诉CPU你现在先不用执行我的任务了,你可以先去干其他的,相当于让出CPU资源。

一个程序来显示状态切换过程

正常执行流程

首先来两个断点,一个断点打在主线程上,另一个断点打在分支上。

并且设定触发断点的条件为Thread,这样子我们进入debug状态之后就可以以线程作为操控条件了。

主线程断点设置在了最后一句,所以此时前面的语句都已经执行完毕了,但是由于还有一个断点打在了分支线程里面,所以如果我不让他执行,他就得等待

现在切换到分支线程执行语句

此时分支线程的任务已经执行完毕了,那么只有主线程还有任务了。

让主线程执行断点处的语句

可以发现在没有阻塞的情况下,线程状态为NEW->RUNNABLE->TERMINATED

这几个状态都是Thread类中定义的枚举类型。

阻塞执行流程

要让线程阻塞,就让他获取锁失败就行了

首先先创建线程,然后开启线程,但是不让线程执行任务

开启任务之后,先让分支线程执行一下,然后让他触碰到获取锁的位置但是先不执行,然后让主线程去执行获取锁的方法,然后再切换到分支线程去获取锁,此时分支线程获取锁失败,那么再让主线程打印分支线程的状态,此时的状态就是阻塞了。

之后主线程释放锁,分支线程继续执行任务,分支线程变为RUNNABLE状态,然后执行完成任务后死亡。

等待执行流程

大致流程也是差不多的,先让分支线程获取到锁,然后此时分支线程调用wait方法把锁让出去,那么主线程就能得到锁,然后查看此时分支线程的状态,主线程把分支线程唤醒之后,分支线程的状态从WAITTING变为了BLOCKING,因为此时分支线程要继续执行任务的话就需要获得锁。

得到锁之后继续重新执行任务,此时主线程发现分支线程的状态就是RUNNABLE了,然后分支线程执行完毕任务,死亡。

说说线程池的核心参数

线程池的执行流程也就是主线程(业务线程)提交任务到线程池之后,任务的处理流程。

下面来看execute方法

这个方法首先判断要执行的任务是否是空,如果是,返回一个空指针异常。否则,判断当前的工作线程数,如果工作线程数小于核心线程数,那么直接创建一个工作线程执行任务,如果不是,那么判断当前工作队列是否不满,如果是,那么将这个任务放入到工作队列中,如果不是,判断当前是否还有可用的非核心线程(判断maximumPoolSize-corePoolSize>0)即工作线程数是否大于最大线程数,如果不是,那么创建一个非核心线程来执行当前任务,如果是,那么就执行拒绝策略。流程图如下:

wait和sleep的区别

  • 共同点:
    wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
  • 方法归属不同
    sleep(long)是 Thread的静态方法
    而wait(),wait(long)都是Object的成员方法,每个对象都有,因此每一个对象都可以作为锁来调用这个方法
  • 醒来时机不同
    执行 sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
    wait(long)和 wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去
    它们都可以被打断唤醒
  • 锁特性不同
    wait方法的调用必须先获取wait对象的锁,而sleep 则无此限制
    wait方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃,但你们还可以用),而sleep如果在 synchronized代码块中执行,并不会释放对象锁(我放弃,你们也用不了)

Lock和synchronized的区别

  • 语法层面
    synchronized是关键字,源码在jvm 中,用c++语言实现
    Lock是接口,源码由jdk 提供,用java语言实现
    使用synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁
  • 功能层面
    二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
    Lock提供了许多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
    Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock
  • 性能层面
    在没有竞争时,synchronized做了很多优化,如偏向锁、轻量级锁,性能不赖
    在竞争激烈时,Lock的实现通常会提供更好的性能

Lock中Condition的使用

说说Java中的悲观锁与乐观锁

1.悲观锁的代表是synchronized和Lock锁

  • 其核心思想是【线程只有占有了锁,才能去操作共享变量,每次只有一个线程占锁成功,获取锁失败的线程,都得停下来等待】
  • 线程从运行到阻塞、再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
  • 实际上,线程在获取synchronized和Lock锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会

2.乐观锁的代表是Atomiclnteger,使用cas来保证原子性

  • 其核心思想是【无需加锁,每次只有一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功】
  • 由于线程一直运行,不需要阻塞,因此不涉及线程上下文切换
  • 它需要多核cpu支持,且线程数不应超过cpu核数

乐观锁

乐观锁的典型操作就是CAS(compare and set)

这里按照乐观锁的要求,不加锁,每次失败的时候都重试,直到成功

当然,如果想要进行原子操作,需要保证共享变量的可见性,所以一般需要要求变量是volatile类型的。

public class SyncVsCas {
    static final Unsafe U = Unsafe.getUnsafe();
    //得到偏移位置
    static final long BALANCE = U.objectFieldOffset(Account.class,"balance");
    static class Account{
        volatile int balance=10;
    }
    public static void main(String[] args) {
        Account account = new Account();
        while (true) {
            int o = account.balance;
            int n = o+5;
            //第一个参数为:要修改的变量是那个对象的 第二个是偏移量
            //第三个参数为旧值  第四个参数为新值
            //这个方法会把o值和我们共享变量的值进行比对 如果一样那么就修改
            if(U.compareAndSetInt(account, BALANCE, o, n)){
                //原子性的
                break;
            }
        }
        System.out.println(account.balance);
    }
}

悲观锁

悲观锁就是很典型的使用synchronized

public static void sync(Account account){
        Thread t1 = new Thread(()->{
            synchronized (account){
                int o = account.balance;
                int n = o-5;
                account.balance=n;
            }
        },"t1");
        Thread t2 = new Thread(()->{
            synchronized (account){
                int o = account.balance;
                int n = o+5;
                account.balance=n;
            }
        },"t2");
        t1.start();
        t2.start();
        System.out.println(account.balance);
    }

Hashtable和ConcurrentHashMap的区别?

【Java面试】谈一谈你对ThreadLocal的理解


相关文章
|
19天前
|
缓存 Java 关系型数据库
【Java面试题汇总】ElasticSearch篇(2023版)
倒排索引、MySQL和ES一致性、ES近实时、ES集群的节点、分片、搭建、脑裂、调优。
【Java面试题汇总】ElasticSearch篇(2023版)
|
19天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
174 37
|
19天前
|
设计模式 安全 算法
【Java面试题汇总】设计模式篇(2023版)
谈谈你对设计模式的理解、七大原则、单例模式、工厂模式、代理模式、模板模式、观察者模式、JDK中用到的设计模式、Spring中用到的设计模式
【Java面试题汇总】设计模式篇(2023版)
|
19天前
|
存储 关系型数据库 MySQL
【Java面试题汇总】MySQL数据库篇(2023版)
聚簇索引和非聚簇索引、索引的底层数据结构、B树和B+树、MySQL为什么不用红黑树而用B+树、数据库引擎有哪些、InnoDB的MVCC、乐观锁和悲观锁、ACID、事务隔离级别、MySQL主从同步、MySQL调优
【Java面试题汇总】MySQL数据库篇(2023版)
|
19天前
|
存储 缓存 NoSQL
【Java面试题汇总】Redis篇(2023版)
Redis的数据类型、zset底层实现、持久化策略、分布式锁、缓存穿透、击穿、雪崩的区别、双写一致性、主从同步机制、单线程架构、高可用、缓存淘汰策略、Redis事务是否满足ACID、如何排查Redis中的慢查询
【Java面试题汇总】Redis篇(2023版)
|
19天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
19天前
|
缓存 Java 数据库
【Java面试题汇总】Spring篇(2023版)
IoC、DI、aop、事务、为什么不建议@Transactional、事务传播级别、@Autowired和@Resource注解的区别、BeanFactory和FactoryBean的区别、Bean的作用域,以及默认的作用域、Bean的生命周期、循环依赖、三级缓存、
【Java面试题汇总】Spring篇(2023版)
|
19天前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
19天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
10天前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
下一篇
无影云桌面