java 并发

什么是线程?什么是进程?为什么要有线程?有什么关系与区别?

线程:
线程是进程中的最小执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,包括内存空间、文件描述符等。线程之间可以并发执行,使得程序可以同时处理多个任务,提高了系统的响应能力和并发性。

进程:
进程是操作系统中的一个执行实例,是程序在计算机上的一次执行活动。每个进程都有自己独立的内存空间和系统资源,进程之间是相互独立的。进程的切换代价相对较高,因为需要保存和恢复进程的所有状态信息。

为什么要有线程?
在单核CPU时代,进程是独立运行的,每个进程有自己的地址空间,进程之间切换开销大。为了充分利用CPU的计算资源,提高系统并发能力,引入了线程的概念。线程在同一个进程内共享进程的资源,线程间切换开销较小,可以实现更高效的多任务处理。

关系与区别:

  • 关系:线程是进程的一部分,一个进程可以包含多个线程。线程共享进程的资源,包括内存空间、文件描述符等。
  • 区别:进程是一个独立的执行实例,有自己独立的内存空间和系统资源。线程是进程的最小执行单元,多个线程共享进程的资源。进程之间相互独立,线程之间可以并发执行,共享进程的资源。

总结:线程是进程内部的执行流,是处理器执行任务的最小单位。一个进程可以包含多个线程,线程之间可以并发执行,共享进程的资源,从而提高系统的并发性和性能。多线程编程可以实现更高效的多任务处理,但也需要注意线程同步和共享资源的安全性问题。

什么是守护线程?

守护线程(Daemon Thread)是一种特殊类型的线程,其特点是在程序运行时在后台提供一种通用服务的功能。与普通线程(用户线程)相对应,守护线程的生命周期依赖于程序中是否还存在正在运行的用户线程。当所有的用户线程结束运行时,守护线程会被自动终止,而不会等待其运行完成。

在Java中,通过调用Thread类的setDaemon(true)方法将线程设置为守护线程。守护线程通常用于提供程序的支持和后台服务,如垃圾回收器(GC线程)就是一个典型的守护线程。垃圾回收器在程序运行过程中自动回收不再使用的对象,但当所有用户线程执行完毕时,程序就终止了,此时也不需要再继续执行垃圾回收的工作,因此垃圾回收线程会自动终止。

守护线程的一个重要应用场景是Web服务器,它可以创建一个守护线程来处理网络连接,如果所有的用户请求线程都结束了,服务器就没有处理请求的必要了,守护线程会自动退出。

需要注意的是,守护线程并不适合处理需要完整执行的任务,因为它的生命周期是不可控的,当所有用户线程结束时,它会被强制终止,可能导致任务未完成。因此,守护线程适合处理后台服务和支持性工作,而不适合处理关键业务逻辑。

如何创建、启动 Java 线程?

在Java中,创建和启动线程通常有两种方式:继承Thread类和实现Runnable接口。

1. 继承Thread类:
创建一个继承自Thread类的子类,并重写run()方法来定义线程的执行逻辑。然后通过调用start()方法来启动线程。示例代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行逻辑
        System.out.println("Thread is running!");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}

2. 实现Runnable接口:
创建一个实现了Runnable接口的类,并实现run()方法来定义线程的执行逻辑。然后将实现了Runnable接口的类作为参数传递给Thread类的构造方法,并调用start()方法启动线程。示例代码如下:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行逻辑
        System.out.println("Thread is running!");
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start(); // 启动线程
    }
}

无论是继承Thread类还是实现Runnable接口,调用start()方法后,线程会进入就绪状态,并由Java虚拟机自动调度执行。需要注意的是,线程的执行顺序是由系统的线程调度器决定的,因此线程的执行顺序可能是随机的。

除了继承Thread类和实现Runnable接口外,还可以使用Callable接口和Future类创建线程。Callable接口允许线程返回一个结果,而Future类可以用于获取Callable线程的返回值。这是一种更加灵活的方式来创建和管理线程。

线程池参数

在Java中,线程池的参数主要由ThreadPoolExecutor类的构造方法来定义,它的构造方法有以下几个参数:

  1. corePoolSize:
    表示线程池的核心线程数,也就是线程池中始终保持活动状态的线程数量。即使这些线程处于空闲状态,也不会被回收。当有新的任务提交时,如果核心线程数还没有达到上限,会优先创建新的核心线程来执行任务。

  2. maximumPoolSize:
    表示线程池的最大线程数,包括核心线程和非核心线程。当有新的任务提交时,如果核心线程数已满,并且线程池中的线程数量还没有达到最大线程数,会创建新的非核心线程来执行任务。当线程池中的线程数达到最大线程数后,如果任务继续提交,任务会被放入任务队列中等待执行。

  3. keepAliveTime:
    表示非核心线程的空闲时间。当非核心线程空闲时间超过该值时,会被回收释放,以减少线程池的线程数量。这个参数只有在allowCoreThreadTimeOut设置为true时才生效。

  4. unit:
    表示keepAliveTime的时间单位,可以是TimeUnit.SECONDSTimeUnit.MINUTES等等。

  5. workQueue:
    表示任务队列,用于存放等待执行的任务。当线程池的线程数达到核心线程数后,后续提交的任务会被放入任务队列中等待执行。线程池提供了多种任务队列的实现,常用的有ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue等。

  6. threadFactory:
    表示线程工厂,用于创建新的线程。可以通过自定义线程工厂来给线程设置特定的名称、优先级等。

  7. handler:
    表示线程池的拒绝策略,当线程池的任务队列和线程数都已满,无法继续接收新的任务时,会根据指定的拒绝策略来处理这些任务。常用的拒绝策略有ThreadPoolExecutor.AbortPolicy(抛出异常)、ThreadPoolExecutor.CallerRunsPolicy(由提交任务的线程来执行任务)、ThreadPoolExecutor.DiscardPolicy(直接丢弃任务)等。

综上所述,线程池的参数主要包括核心线程数、最大线程数、非核心线程的空闲时间、任务队列、线程工厂和拒绝策略等。合理地设置这些参数可以根据系统的需求来控制线程池的并发度、资源消耗和任务调度,以达到最优的性能和资源利用率。

详细解释Callable接口和Future类

Callable接口和Future类是Java多线程中用于处理有返回值的任务的一组接口和类。它们通常与线程池结合使用,使得多线程编程更加灵活和高效。

Callable接口:
Callable接口是一个泛型接口,定义了一个带有返回值的任务,它只有一个方法call(),没有像Runnable接口那样的run()方法。call()方法可以返回一个结果,并且可以抛出异常。通常情况下,我们可以通过实现Callable接口来创建具有返回值的任务,并将任务提交给线程池执行。Callable接口的定义如下:

public interface Callable<V> {
    V call() throws Exception;
}

Future类:
Future类是一个接口,它代表了异步计算的结果。当一个线程提交了一个Callable任务到线程池后,线程池会返回一个Future对象,通过该对象可以获得任务的执行结果。Future接口定义了一些方法,用于查询任务是否完成、获取任务的执行结果或者取消任务的执行。常用的Future实现类是FutureTaskFuture接口的定义如下:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

通过Future对象,我们可以在主线程中继续执行其他任务,然后在需要获取任务结果的地方调用get()方法来等待任务执行完成并获取结果。如果任务还没有执行完成,get()方法将会阻塞主线程,直到任务执行完成并返回结果。如果不想等待任务执行完成,可以使用isDone()方法来判断任务是否完成。

总结:Callable接口用于定义带有返回值的任务,Future类用于获取任务的执行结果或者取消任务的执行。通过这两个接口,我们可以更好地管理多线程任务,实现高效的并发编程。

偏向锁 / 轻量级锁 / 重量级锁

偏向锁、轻量级锁、重量级锁都是Java中用于优化锁的实现,针对不同的锁竞争场景,采用不同的策略来提高锁的性能。

1. 偏向锁(Biased Locking):
偏向锁是Java中针对单线程访问同步块的优化措施。它的设计思想是假设在大多数情况下,锁只会被一个线程访问,因此当一个线程访问同步块时,会将对象头中的标记设置为偏向锁,并记录下持有偏向锁的线程ID。这样,下次这个线程再次访问同步块时,就无需再进行加锁操作,而是直接进入临界区。如果有其他线程尝试竞争偏向锁,偏向锁就会升级为轻量级锁。

2. 轻量级锁(Lightweight Locking):
轻量级锁是针对多个线程交替执行同步块的优化措施。当一个线程尝试获取锁时,会先在栈帧中分配一块用于存储锁记录的空间,并将对象头中的标记设置为轻量级锁。然后线程尝试使用CAS(比较并交换)操作来尝试获取锁,如果获取成功,则进入临界区执行。如果CAS操作失败,说明有其他线程也在竞争锁,这时轻量级锁会膨胀为重量级锁。

3. 重量级锁(Heavyweight Locking):
重量级锁是Java中默认的锁实现,用于处理复杂的锁竞争场景。当一个线程尝试获取锁时,会进入重量级锁的阻塞状态,操作系统会将该线程挂起,直到锁被释放,然后唤醒该线程。重量级锁采用操作系统的原生锁机制,会涉及用户态和内核态的切换,开销较大,适用于竞争激烈的情况。

在锁的优化中,JVM会根据线程竞争的情况自动选择偏向锁、轻量级锁或重量级锁来提高锁的性能。偏向锁适用于只有一个线程访问同步块的情况,轻量级锁适用于多个线程交替执行同步块的情况,而重量级锁适用于竞争激烈的情况。这些优化措施都是为了减少锁的开销,提高程序的并发性能。

synchronized 和 java.util.concurrent.lock.Lock 之间的区别

synchronizedjava.util.concurrent.lock.Lock都是Java中用于实现线程同步的机制,但它们之间有一些重要的区别:

  1. 锁的类型:

    • synchronized是Java中的关键字,属于内置锁(Intrinsic Lock),可以直接在方法或代码块中使用。每个Java对象都有一个内置锁,通过synchronized关键字来获取。
    • java.util.concurrent.lock.Lock是Java并发包中提供的接口,属于显式锁(Explicit Lock)。它提供了更灵活的锁定和释放机制,并且可以有多个条件变量。
  2. 获取锁的方式:

    • synchronized的获取锁是隐式的,在进入synchronized代码块或方法时自动获取锁,在退出代码块或方法时自动释放锁。
    • java.util.concurrent.lock.Lock的获取锁是显式的,需要手动调用lock()方法来获取锁,调用unlock()方法来释放锁。这样可以更精确地控制锁的范围和持有时间。
  3. 锁的可中断性:

    • synchronized获取锁的过程是不可中断的,即使其他线程尝试中断持有锁的线程,也无法中断。
    • java.util.concurrent.lock.Lock中的锁可以通过lockInterruptibly()方法实现可中断的获取锁操作,当其他线程中断当前线程时,可以中断获取锁的过程。
  4. 锁的公平性:

    • synchronized锁是非公平的,当一个线程释放锁后,任何一个等待该锁的线程都有机会获取锁。
    • java.util.concurrent.lock.Lock中的锁可以是公平的,通过ReentrantLock类的构造函数可以指定是否使用公平锁,默认是非公平锁。
  5. 灵活性和功能:

    • java.util.concurrent.lock.Lock提供了更多的功能,例如可以尝试获取锁、设定获取锁的超时时间、创建多个条件变量等,可以满足更复杂的同步需求。
    • synchronized相对简单,适用于一些简单的同步需求,而且在JVM中使用synchronized的优化措施,如偏向锁、轻量级锁等,可以带来一定的性能优势。

总体而言,对于简单的同步需求,synchronized是较为方便的选择。而对于复杂的同步需求,或者需要更精细地控制锁的行为,java.util.concurrent.lock.Lock提供了更多的灵活性和功能,可以更好地满足需求。

synchronized 和 java.util.concurrent.lock.Lock 之间的区别

synchronizedjava.util.concurrent.lock.Lock都是Java中用于实现线程同步的机制,但它们之间有一些重要的区别:

  1. 锁的类型:

    • synchronized是Java中的关键字,属于内置锁(Intrinsic Lock),可以直接在方法或代码块中使用。每个Java对象都有一个内置锁,通过synchronized关键字来获取。
    • java.util.concurrent.lock.Lock是Java并发包中提供的接口,属于显式锁(Explicit Lock)。它提供了更灵活的锁定和释放机制,并且可以有多个条件变量。
  2. 获取锁的方式:

    • synchronized的获取锁是隐式的,在进入synchronized代码块或方法时自动获取锁,在退出代码块或方法时自动释放锁。
    • java.util.concurrent.lock.Lock的获取锁是显式的,需要手动调用lock()方法来获取锁,调用unlock()方法来释放锁。这样可以更精确地控制锁的范围和持有时间。
  3. 锁的可中断性:

    • synchronized获取锁的过程是不可中断的,即使其他线程尝试中断持有锁的线程,也无法中断。
    • java.util.concurrent.lock.Lock中的锁可以通过lockInterruptibly()方法实现可中断的获取锁操作,当其他线程中断当前线程时,可以中断获取锁的过程。
  4. 锁的公平性:

    • synchronized锁是非公平的,当一个线程释放锁后,任何一个等待该锁的线程都有机会获取锁。
    • java.util.concurrent.lock.Lock中的锁可以是公平的,通过ReentrantLock类的构造函数可以指定是否使用公平锁,默认是非公平锁。
  5. 灵活性和功能:

    • java.util.concurrent.lock.Lock提供了更多的功能,例如可以尝试获取锁、设定获取锁的超时时间、创建多个条件变量等,可以满足更复杂的同步需求。
    • synchronized相对简单,适用于一些简单的同步需求,而且在JVM中使用synchronized的优化措施,如偏向锁、轻量级锁等,可以带来一定的性能优势。

总体而言,对于简单的同步需求,synchronized是较为方便的选择。而对于复杂的同步需求,或者需要更精细地控制锁的行为,java.util.concurrent.lock.Lock提供了更多的灵活性和功能,可以更好地满足需求。

java.util.concurrent.lock.Lock 与 java.util.concurrent.lock.ReadWriteLock 之间的区别

java.util.concurrent.lock.Lockjava.util.concurrent.lock.ReadWriteLock都是Java并发包中用于实现线程同步的接口,它们之间的区别主要在于锁的类型和用途:

1. 锁的类型:

  • java.util.concurrent.lock.Lock是一种通用的锁接口,用于实现基本的互斥锁。它提供了两个基本方法:lock()用于获取锁,unlock()用于释放锁。Lock接口的实现类ReentrantLock是一种可重入锁,它允许同一个线程多次获取同一个锁,避免了死锁的问题。

  • java.util.concurrent.lock.ReadWriteLock是一种读写锁接口,它可以让多个线程同时读取共享资源,但在写操作时需要互斥。ReadWriteLock接口提供了两个锁:读锁(读共享锁)和写锁(写独占锁)。读锁可以被多个线程同时获取,用于读取共享资源;而写锁只能被一个线程获取,用于修改共享资源。

2. 用途:

  • java.util.concurrent.lock.Lock适用于一般性的互斥场景,它提供了灵活的锁定和解锁机制,可以手动控制锁的获取和释放。它的实现类ReentrantLock可以实现公平锁或非公平锁,也支持可中断的获取锁操作。

  • java.util.concurrent.lock.ReadWriteLock适用于读多写少的场景,它可以提高读操作的并发性能。在读多写少的情况下,多个线程可以同时获取读锁来读取共享资源,而只有在写操作时需要排他性,此时才需要获取写锁。

综上所述,Lock接口适用于通用的互斥场景,提供了灵活的锁定和解锁机制;而ReadWriteLock接口适用于读多写少的场景,可以提高读操作的并发性能。在不同的场景下,可以根据需求选择合适的锁实现。

什么是死锁?发生的条件,避免死锁

死锁发生的条件

互斥,共享资源只能被一个线程占用
占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1
不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1
循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源

避免死锁的方法

对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。

对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。

其他三个条件,我们可以尝试

一次性申请所有的资源,破坏 “占有且等待” 条件
占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 “不可抢占” 条件
按序申请资源,破坏 “循环等待” 条件

介绍一下ForkJoinPool的使用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值