跳至主要內容

Java基础复习-线程与并发

Unisky大约 6 分钟学习面试Java

Java基础复习-线程与并发

这方面接触的不多,希望不会出错误

1.线程、进程和协程的区别

进程是操作系统在内存中分配的一个应用程序,进程之间互相独立,有独立的内存空间。

一条进程可以包括多条线程,线程是执行任务的主要单位,同进程内的所有线程共享该进程的数据,可以并发执行,需要同步机制避免线程对数据操作的冲突。

线程不共享的资源包括:栈、寄存器、线程局部存储、线程信息等

一条线程可以拥有多条协程,协程调度由程序(用户)来执行,而进程线程都由内核调度。Java没有实现协程(Kilim框架模拟实现了协程),Python中通过yield/send或async/await实现协程。

2.线程的6个状态

在java中,一个线程共有六种状态,状态之间可以切换

  • 1.new 线程刚被创建,没有开始运行的状态
  • 2.runnable (与running状态合并)调用start()方法后进入就绪/运行状态,注意start()方法会用一个新的线程去调用run()方法
  • 3.blocked 线程阻塞状态,释放CPU(如试图获取锁时被其他线程占用)
  • 4.waiting 等待状态,无限等待,通过wait(),join()等方法实现,需要被其他线程唤醒或者中断
  • 5.time_waiting 超时等待,为等待状态增加限时,sleep()或者带参数的wait()方法均可以实现
  • 6.terminated 线程执行完毕

3.创建线程的4个方法

  • 1.继承Thread类并重写run()方法创建线程
  • 2.实现Runnable接口并实现run()方法创建线程
  • 3.实现Callable接口并实现call()方法创建线程
  • 4.通过框架如Executor创建

4.线程间的通信

通过Object类提供的wait()、notify()、notifyAll()可以实现进程间的通信,wait()方法会释放锁,notify()不会,这两个方法需要配合synchronized关键词的修饰。

5.Runnable和Callable的区别是什么

  • 1.接口方法不同,一个是run()方法,另一个是call()方法
  • 2.call()方法存在返回值,run()不存在返回值
  • 3.Callable允许抛出异常,Runnable无法继续向上抛异常

6.volatile关键词是什么

可以用于修饰变量,用于保证该变量对于所有线程可见(线程对变量的修改可以立刻被其他线程发现),同时禁止了jvm内部的指令重排(防止指令执行顺序发生混乱)

volatile不具备原子性,仍然可能会导致多个线程对数据操作的冲突(例如线程刚读取完数据,在写入之前发生阻塞)

7.synchronized

synchronized是一个同步关键词,可以修饰类、变量、普通方法、静态方法和代码块。具有原子性、可见性和有序性,实质上是一种悲观锁和非公平锁,会导致阻塞。

1.修饰普通方法: 对于当前对象的实例,一个线程需要获得锁才能够进入这部分同步代码,完成这部分代码后线程会自动释放锁

2.修饰代码块:与修饰方法类似,主要是作用范围不同,当一个线程访问这部分代码块时会拥有锁,其他进程试图访问该对象的这部分代码时会被阻塞,不影响访问其他非同步代码块

 public  void run() {
       synchronized (this){
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.修饰类:一个线程访问这个类时,这个类的所有对象都会被锁定,即这个类的所有方法会共享同一把锁

4.修饰静态方法:与修饰类的情况相似,对于这个静态方法,这个类的所有实例化对象占用同一把锁

5.注意同一个对象的所有synchronized方法是共享锁的

8.乐观锁与悲观锁

都是并发情况下锁的设计思想

乐观锁

  • 默认其余人不会同时修改数据,不对数据加锁。
  • 在执行更新时判断数据是否被修改,若已经被修改则拒绝操作。
  • 自旋CAS算法实现,包括三个操作数,待读写内存位置V,比较预期值A,准备写入的新值B,若V=A,则将A更新为B,否则自旋保持重试直到成功
  • 版本号机制实现,在数据中增加一个version版本数据,数据被修改时版本+1,更新数据时需要判断当前版本号与读取版本号是否一致
  • 乐观锁本身不加锁,仅仅只是进行判断。适用场景限制较高,适用于竞争不激烈的情况

悲观锁

默认别人会同时修改数据,操作数据时对数据加锁,直到完成后才释放锁。 适用于竞争激烈的场合,加锁和释放锁需要消耗额外资源,并且锁的存在影响并发性能

9.进程间的通信方式有哪些

  • 1.管道:数据单向流动,亲缘进程之间的通信
  • 2.命名管道:与管道类似,管道具有名称,可在文件系统中访问,对亲缘关系无要求
  • 3.信号:异步通信方式,处理异步事件
  • 4.共享内存:允许进程共享同一块物理内存,速度快,需要考虑并发和同步
  • 5.信号量:控制进程对资源的访问,常用于同步和互斥,保护共享内存数据
  • 6.消息队列MQ:进程之间传递结构化的数据
  • 7.套接字Socket:允许不同主机间访问,实现分布式系统和网络通信

10.创建多线程的方式

  • 直接创建多个单线程,见前面创建线程方式
  • 通过线程池创建,可以提高响应速度,降低资源损耗,便于线程管理
ExecutorService executorService = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor)executorService;
        //设置线程池的属性
        threadPoolExecutor.setCorePoolSize(12);
        threadPoolExecutor.setMaximumPoolSize(10);
        /**
         * executorService.execute(Runnable commd);
         * executorService.submit(Callable commd):可用FutureTask获取结果;
         */
        executorService.submit(new ThreadTest());
        executorService.submit(new ThreadTest());

线程池包括如下参数:

线程池参数解释
corePoolSize核心线程数,维护最小线程数量
maximumPoolSize最大线程数,池内允许最大线程数量
keepAliveTime空闲线程的存活时长
unit指定keepAliveTime时间的单位
workQueue指定线程工作队列类型
threadFactory指定线程工厂
handler指定超出线程和工作队列时的处理策略