Java并发编程之入门-线程相关

1.Java并发编程定义

在Java程序中处理多线程和多任务并发执行的技术和方法。

2.Java并发编程关键概念和技术

  • 线程:程序执行的最小单元,Java中通过Thread类或实现Runnable接口来创建和管理线程。
  • 并发:程序中多个线程同时执行,Java提给了多线程编程的支持,支持多个任务并发执行。
  • 同步:通过同步机制来保证线程访问共享资源的安全性,Java提供了synchronized关键字、Lock接口等同步机制。
  • 锁:Java中的锁机制用于控制对共享资源的访问,包括内置锁(Intrinsic Lock)、显式锁(ReentrantLock)、读写锁(ReentrantReadWriteLock)等。
  • 原子操作:Java提供了原子操作类(AtomicInteger、AtomicLong等)来保证多线程下的原子性操作。
  • 并发集合:Java提供了线程安全的集合类(如ConcurrentHashMap、CopyOnWriteArrayList等)来处理多线程下的并发访问。
  • Future和CompletableFuture:用于异步任务的执行和结果获取,Future表示异步计算的结果,CompletableFuture提供更多的异步操作方法。
  • volatile关键字:用于保证变量的可见性,多线程环境下对volatile变量的读写操作都会直接在主内存中进行。
  • 线程间通信:通过wait、notify、notifyAll等方法实现线程间的协作和通信。

3.题外话

程序是一组指令的集合,是一段静态的代码,进程是操作系统中的一个运行中的程序实例,是程序在内存中的一次执行过程。

进程是操作系统资源分配的基本单位,线程是操作系统调度的基本单元。线程可以共享进程的资源,线程之间的切换开销较小。

进程是操作系统中的一个独立的运行实体,具有独立的内存、代码、数据段。

线程是进程的一个执行单元,同一个进程里面的多个线程可以共享同一份数据。

4.线程

创建

线程的创建可以通过以下几种方式实现:

  1. 继承Thread类:创建一个继承自Thread类的子类,重写run()方法,并通过调用start()方法启动线程。
public class MyThread extends Thread {
    public void run() {
        // 线程执行的逻辑
    }
}

MyThread thread = new MyThread();
thread.start();
  1. 实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法,并通过将其传递给Thread类的构造函数来创建线程。
public class MyRunnable implements Runnable {
    public void run() {
        // 线程执行的逻辑
    }
}

MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
  1. 使用匿名内部类:可以在创建线程时使用匿名内部类来实现Runnable接口或重写Thread类的run()方法。
Thread thread = new Thread(new Runnable() {
    public void run() {
        // 线程执行的逻辑
    }
});
thread.start();
  1. 使用线程池:通过Java提供的Executor框架和线程池来管理线程的创建和执行,可以提高性能和资源利用率。
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new Runnable() {
    public void run() {
        // 线程执行的逻辑
    }
});
executor.shutdown();

状态

线程在Java中有以下几种状态:

  1. 新建状态(New):当通过new Thread()new Thread(Runnable)创建线程对象时,线程处于新建状态。此时线程对象已经被创建,但还没有调用start()方法。

  2. 就绪状态(Runnable):当调用线程对象的start()方法后,线程进入就绪状态。此时线程已经准备好运行,但还没有被分配CPU执行时间。

  3. 运行状态(Running):当线程获取CPU执行时间,开始执行run()方法时,线程处于运行状态。此时线程正在执行任务。

  4. 阻塞状态(Blocked):线程在某些情况下会进入阻塞状态,例如调用sleep()、wait()、join()方法,或者等待某个资源时。在阻塞状态下,线程暂时停止执行,直到条件满足后才能继续执行。

  5. 等待状态(Waiting):线程调用Object.wait()、Thread.join()、LockSupport.park()等方法时,会进入等待状态。处于等待状态的线程需要通过其他线程的唤醒来继续执行。

  6. 超时等待状态(Timed Waiting):线程调用Thread.sleep()、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()等方法时,会进入超时等待状态。在指定的时间内等待,超时后会自动唤醒。

  7. 终止状态(Terminated):线程执行完run()方法或者调用了Thread.stop()方法终止线程后,线程进入终止状态。线程终止后不能再次启动,生命周期结束。

是不是还是不清晰怎么流转的,看个简单的流程图

新建状态
就绪状态
运行状态
阻塞状态
等待状态
超时等待状态
终止状态

在这个流程图中:

  • 线程从新建状态开始,然后转换到就绪状态。
  • 当获取CPU执行时间时,线程进入运行状态。
  • 在某些情况下,线程会进入阻塞状态,等待条件满足后继续执行或唤醒。
  • 线程也可能进入等待状态或超时等待状态,等待其他线程唤醒或一定时间后自动唤醒。
  • 最终,线程执行完毕或被终止后,进入终止状态,生命周期结束。

调度

这块我记不住~~~凑点资料吧,反正写这个文章也是自我学习的一个过程,等我学会了,再细细说来。
贴资料:
在Java中,线程的调度是由操作系统的线程调度器来负责的,它决定了哪个线程在某一时刻可以运行。线程调度器根据一定的策略来决定线程的优先级和执行顺序,以实现公平性和性能的平衡。常见的线程调度策略包括:

  1. 抢占式调度:操作系统可以在任何时候中断当前运行的线程,将CPU分配给其他线程。通过时间片轮转的方式,各个线程轮流获取CPU的执行时间。

  2. 优先级调度:线程可以设置不同的优先级,优先级高的线程更有可能获得CPU执行时间。但是,不同操作系统对线程优先级的支持可能有所不同。

  3. 协同式调度:线程在一定条件下主动让出CPU执行时间,让其他线程运行。这种调度方式需要线程自己合作,否则可能导致某个线程长时间占用CPU。

  4. 公平调度:尽量保证所有线程获得相等的CPU执行时间,避免某个线程长时间占用CPU而其他线程无法执行。

同步和互斥

  • 同步:同步是指多个线程之间协调和合作,以确保线程之间的操作按照一定的顺序执行。通过同步机制,可以控制线程之间的执行顺序,避免数据竞争和并发问题。

  • 互斥:互斥是一种同步机制,用于保证在同一时刻只有一个线程可以访问共享资源。通过互斥锁或信号量等机制,可以防止多个线程同时访问共享资源,避免数据冲突。

同步和互斥是相辅相成的概念,通常在多线程编程中同时使用。在实际应用中,同步可以依靠互斥实现,即通过互斥机制来实现线程间的同步操作。例如,通过使用synchronized关键字或ReentrantLock类来实现互斥访问共享资源,从而实现线程之间的同步操作。

概念懂了,上代码,java中通过什么方式可以实现同步和互斥呢?

  1. synchronized关键字

    • synchronized关键字是Java中最常用的同步机制,可以用于修饰方法或代码块,确保在同一时刻只有一个线程执行被修饰的代码。
    • 示例:
    public synchronized void synchronizedMethod() {
        // 同步操作
    }
    
  2. ReentrantLock类

    • ReentrantLock是Java.util.concurrent包中的一个锁实现类,提供了比synchronized更加灵活的锁机制。
    • 示例:
    ReentrantLock lock = new ReentrantLock();
    // 使用lock()方法获取锁,如果获取成功则可以进入临界区执行代码,如果获取失败则会被阻塞直到获取到锁。
    lock.lock();
    try {
        // 临界区代码
    } finally {
        lock.unlock();// 在finally块中释放锁,确保锁的释放
    }
    
    • 可以使用tryLock()方法尝试获取锁,如果获取成功则返回true,否则返回false。
    if (lock.tryLock()) {
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    } else {
        // 未获取到锁的处理逻辑
    }
    
    • 可以使用tryLock()方法的重载版本tryLock(long timeout, TimeUnit unit)来设置超时时间,在超时时间内尝试获取锁。
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    } else {
        // 未在指定时间内获取到锁的处理逻辑
    }
    
  3. Object类的wait()和notify()方法

    • wait()和notify()方法可以实现线程之间的协作和同步。当一个线程调用wait()方法时,会释放对象的锁并进入等待状态,直到其他线程调用notify()方法唤醒它。
    • 示例:
    synchronized (lock) {
        while (condition) {
            lock.wait();
        }
        // 执行同步操作
        lock.notify();
    }
    
  4. CountDownLatch类

    • CountDownLatch是一个同步工具类,可以让一个或多个线程等待其他线程完成操作后再继续执行。
    • 示例:
    CountDownLatch latch = new CountDownLatch(3);
    
    // 创建并启动3个子线程
    for (int i = 0; i < 3; i++) {
       new Thread(() -> {
           // 子线程执行任务
           latch.countDown(); // 操作完成,计数器减1
       }).start();
    }
    
    try {
       latch.await(); // 主线程等待所有子线程完成操作
       System.out.println("所有子线程操作已完成");
    } catch (InterruptedException e) {
       // 处理中断异常
    }
    
  5. Semaphore类

    • Semaphore是一个计数信号量,可以用来限制同时访问某个资源的线程数量。可以通过acquire()和release()方法获取和释放信号量来实现线程间的同步。
    Semaphore semaphore = new Semaphore(3);
    
    // 创建并启动5个线程
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            try {
                semaphore.acquire(); // 获取许可
                // 访问共享资源的代码
                semaphore.release(); // 释放许可
            } catch (InterruptedException e) {
                // 处理中断异常
            }
        }).start();
    }
    
  6. Condition接口

    • Condition接口结合ReentrantLock类可以实现更加灵活的线程等待/通知机制,类似于Object的wait()和notify()方法。
    • 示例:
    • 可以使用Condition接口实现更加灵活的线程间通信,比如在生产者消费者模式中控制生产者和消费者的操作。
    ReentrantLock lock = new ReentrantLock();
    Condition notEmpty = lock.newCondition();
    Condition notFull = lock.newCondition();
    
    // 生产者线程
    lock.lock();
    try {
        while (队列已满) {
            notFull.await(); // 等待队列不满
        }
        // 执行生产操作
        notEmpty.signal(); // 通知消费者线程队列不为空
    } finally {
        lock.unlock();
    }
    
    // 消费者线程
    lock.lock();
    try {
        while (队列为空) {
            notEmpty.await(); // 等待队列不空
        }
        // 执行消费操作
        notFull.signal(); // 通知生产者线程队列不满
    } finally {
        lock.unlock();
    }
    
    
    
  7. Atomic类

    • Atomic类提供了一系列原子操作,可以保证在多线程环境下进行简单的原子性操作,避免数据竞争。
    • 示例:
    AtomicInteger counter = new AtomicInteger(0);
    counter.incrementAndGet();
    

通信

在多线程编程中,线程之间的通信是指多个线程之间通过共享的内存或其他机制进行信息交换和协作的过程。线程之间的通信可以帮助不同线程之间协调工作,共享数据以及控制执行顺序。以下是几种常见的线程间通信方式:

  1. 共享内存

    • 多个线程共享同一块内存区域,通过读写共享内存来实现线程间的数据共享和通信。需要注意的是,在多线程环境下,需要保证对共享内存的访问是线程安全的。
  2. 信号量

    • 信号量是一种用于控制并发访问数量的同步工具,可以用来限制同时访问某个资源的线程数量。通过信号量,可以实现线程之间的协调和同步。
  3. 管道

    • 管道是一种在进程间通信中常用的通信方式,也可以用于线程间通信。管道可以实现一个线程向另一个线程发送数据。
  4. 锁和条件变量

    • 锁和条件变量通常与Lock和Condition对象结合使用,用于实现更灵活的线程间通信和同步。线程可以在特定条件下等待,当条件满足时被通知继续执行。
  5. 阻塞队列

    • 阻塞队列是一种线程安全的队列数据结构,可以用于在生产者消费者模式中实现线程间的数据交换。生产者线程将数据放入队列,消费者线程从队列中取出数据。
  6. 线程间通信工具类

    • Java并发包中提供了一些用于线程间通信的工具类,如CountDownLatch、CyclicBarrier、Semaphore等,可以帮助实现不同线程之间的协作和同步。

优先级

在Java多线程编程中,每个线程都有一个优先级(priority),用于指定线程在竞争CPU资源时的执行顺序。Java中线程的优先级范围是1到10,默认优先级是5。线程的优先级是一个整数值,具有较高优先级的线程在竞争CPU资源时会被优先执行,但不能保证高优先级的线程一定比低优先级的线程先执行。

以下是关于线程优先级的一些注意事项:

  1. 设置线程优先级

    • 可以使用Thread类的setPriority()方法来设置线程的优先级。优先级范围是1(最低优先级)到10(最高优先级)。
    Thread thread = new Thread();
    thread.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
    
  2. 获取线程优先级

    • 可以使用Thread类的getPriority()方法来获取线程的当前优先级。
    int priority = thread.getPriority();
    
  3. 优先级继承

    • 在Java中,子线程会继承父线程的优先级。如果不设置子线程的优先级,子线程会和父线程具有相同的优先级。
  4. 优先级调度

    • 调度器会根据线程的优先级来决定哪个线程获得CPU资源。具有更高优先级的线程会在竞争CPU资源时被优先执行,但是这并不是绝对的,仅代表更有可能获得CPU资源。
  5. 不同操作系统的影响

    • 线程优先级在不同的操作系统上可能会有不同的实现和影响,因此不应该过度依赖线程优先级来控制程序的执行顺序。

线程池

通过线程池来管理和复用线程,避免频繁创建和销毁线程,提高程序性能
在Java中,线程池是通过java.util.concurrent包中的Executor框架来实现的。Executor框架提供了一组接口和类,用于管理线程池,提交任务,控制线程数量等。Java中线程池的主要实现类是ThreadPoolExecutor,通常通过Executors工厂类来创建不同类型的线程池。
下面是使用Java中线程池的一般步骤:

  1. 创建线程池

    • 可以通过Executors工厂类来创建不同类型的线程池,如FixedThreadPool、CachedThreadPool等。例如,创建一个固定大小的线程池:
    ExecutorService executor = Executors.newFixedThreadPool(10);
    
  2. 提交任务

    • 使用线程池的execute()方法或submit()方法来提交任务给线程池执行。
    executor.execute(new RunnableTask());
    
  3. 关闭线程池

    • 在程序结束时需要手动关闭线程池,释放资源。可以调用shutdown()方法来平缓关闭线程池,或调用shutdownNow()方法来立即关闭线程池。
    executor.shutdown();
    
  4. 自定义线程池

    • 可以通过ThreadPoolExecutor类来自定义线程池的参数,如核心线程数、最大线程数、任务队列等。
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    
  5. 异常处理

    • 在使用线程池时需要注意处理任务中可能抛出的异常,可以通过try-catch块来捕获异常并处理。
  6. 监控和调优

    • 可以通过ThreadPoolExecutor提供的方法和参数来监控线程池的状态,调整线程池的参数以优化性能。

守护线程

  1. 守护线程是一种在程序运行时在后台提供服务的线程,当所有的非守护线程结束时,守护线程会自动退出。
  2. 守护线程通常用于执行后台任务,如垃圾回收、监控、定时任务等。
  3. 创建守护线程时,可以通过setDaemon(true)方法将线程设置为守护线程。
  4. 守护线程适用于不需要阻止程序正常退出的后台任务。
  5. 守护线程不能持有程序中的非守护线程所持有的资源,应设计为可以在任何时候安全退出。

5.volatile关键字

  1. 可见性

    • 当一个线程修改了volatile修饰的变量的值时,这个新值会立即被其他线程看到。volatile变量的修改对所有线程都是可见的。
  2. 禁止指令重排序

    • volatile关键字可以禁止对volatile变量的指令重排序,保证了程序的正确性。
  3. 不保证原子性

    • 虽然volatile变量保证了可见性和禁止指令重排序,但它并不保证原子性。对volatile变量的单个读/写操作是原子的,但复合操作不是原子的。
      原子性是指一个操作在执行过程中不会被中断,要么全部执行成功,要么全部不执行,不存在部分执行的情况。
  4. 适用场景

    • volatile适合用于多个线程之间共享变量,并且这个变量的值会被多个线程频繁修改的情况。也适合变量的值不依赖于当前值,或者变量的值不需要计算就能被正确更新的场景。
  5. 轻量级

    • volatile是一种轻量级的线程同步机制,相比锁的开销更小,适用于某些特定的场景。
  6. 不适合替代锁

    • 虽然volatile可以保证可见性和禁止指令重排序,但并不适合替代锁来实现更复杂的线程同步操作。在需要进行线程同步和保证原子性的情况下,应该使用锁或Atomic类来实现。

6.并发集合

并发集合是一种特殊的数据结构,用于在多线程环境下安全地操作和管理数据。由于在多线程程序中,多个线程可能同时访问和修改同一个集合,普通的集合类(如ArrayList、HashMap等)可能会出现线程安全性问题。因此,针对并发环境,Java提供了一些并发集合类,它们提供了线程安全的操作和更好的性能。

以下是一些常用的Java并发集合类:

  1. ConcurrentHashMap

    • ConcurrentHashMap是HashMap的线程安全版本,它支持并发读写操作而不需要额外的同步操作。在多线程环境下,ConcurrentHashMap能够提供更好的性能和线程安全性。
  2. CopyOnWriteArrayList

    • CopyOnWriteArrayList是ArrayList的线程安全版本,它通过每次修改操作都会复制一份新的数组来实现线程安全。适用于读操作频繁、写操作较少的场景。
  3. ConcurrentLinkedQueue

    • ConcurrentLinkedQueue是一个线程安全的队列实现,采用无锁算法实现并发操作。适合用于生产者消费者场景。
  4. ConcurrentSkipListMapConcurrentSkipListSet

    • ConcurrentSkipListMap和ConcurrentSkipListSet是基于跳表(Skip List)的并发实现,提供了高效的并发操作和有序性。
  5. BlockingQueue

    • BlockingQueue是一个支持阻塞操作的队列,常用于实现生产者消费者模式。常见的实现类有ArrayBlockingQueue、LinkedBlockingQueue等。

7.Executor框架

Executor接口是Executor框架的基础接口。
ExecutorService接口继承自Executor接口,提供了更丰富的任务执行功能。
ThreadPoolExecutor是ExecutorService接口的一个实现类,用于管理线程池。
ScheduledExecutorService接口继承自ExecutorService接口,提供了定时执行任务的功能。

Executor
ExecutorService
ThreadPoolExecutor
ScheduledExecutorService

8.ThreadPoolExecutor

ThreadPoolExecutor是Java中Executor框架提供的一个线程池实现类,实现了ExecutorService接口,用于管理线程池中的线程、执行任务等。ThreadPoolExecutor是Executor框架中最常用的线程池实现类之一,提供了丰富的配置选项和灵活的线程池管理功能。

ThreadPoolExecutor的构造方法如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

其中各个参数的含义如下:

  1. corePoolSize:核心线程数,线程池中始终保持的活动线程数,即使线程处于空闲状态也不会被销毁。
  2. maximumPoolSize:最大线程数,线程池中允许的最大线程数,当任务队列满时会创建新的线程,直到达到最大线程数。
  3. keepAliveTime:线程空闲超时时间,当线程池中的线程数量超过核心线程数时,多余的空闲线程在空闲时间超过keepAliveTime时会被销毁。
  4. unit:keepAliveTime的时间单位。
  5. workQueue:任务队列,用于存放等待执行的任务。
  • ThreadPoolExecutor提供了一系列方法来执行任务、管理线程池,如submit、execute、shutdown等。通过ThreadPoolExecutor,可以灵活地控制线程池的大小、任务队列的策略、拒绝策略等,来满足不同场景下的需求。

  • 在实际开发中,通常会使用ThreadPoolExecutor来创建线程池,并通过submit或execute方法提交任务执行。通过合理配置ThreadPoolExecutor的参数,可以有效地管理线程池中的线程,提高程序的并发性能。

9.Executors工厂类

是Java中Executor框架提供的一个工厂类,用于创建不同类型的ExecutorService实例。

  • Executors工厂类是Java中Executor框架提供的一个工厂类,用于创建不同类型的ExecutorService实例。如newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor等。
  • Executors工厂类也提供了newScheduledThreadPool方法用于创建定时任务线程池,可以用来执行定时任务和周期性任务。
  • 虽然Executors工厂类提供了方便的方法来创建线程池,但在实际生产环境中,建议根据具体需求使用ThreadPoolExecutor来创建线程池,以便更好地控制线程池的参数、任务队列、拒绝策略等。因为Executors工厂类创建的线程池实例具有一些默认的参数配置,可能不适合所有情况。
  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值