Skip to content

GUC并发学习

概念

同步VS异步

同步和异步通常用来形容一次方法调用。同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。比如,在超时购物,如果一件物品没了,你得等仓库人员跟你调货,直到仓库人员跟你把货物送过来,你才能继续去收银台付款,这就类似同步调用。而异步调用了,就像网购,你在网上付款下单后,什么事就不用管了,该干嘛就干嘛去了,当货物到达后你收到通知去取就好。

并发与并行

并发和并行是十分容易混淆的概念。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

阻塞和非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。

临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

创建线程

实现Runnable接口

  • 特点:解耦任务与线程,支持 Lambda 表达式,代码简洁。
java
Thread runnableThread = new Thread(()-> System.out.println("Runnable线程启动"));
        runnableThread.start();

继承Thread重写run()方法

  • 特点:简单直观,但 Java 不支持多继承,限制了类的扩展性。
java
 Thread extendThread = new Thread() {
            @Override
            public void run() {
                super.run();
                System.out.println("继承线程启动");
            }
        };
        extendThread.start();

使用线程池

  • 特点:线程可复用,避免频繁创建销毁的开销,性能最好。
java
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> submit = executorService.submit(() -> {
    System.out.println("线程池线程启动");
    return "线程池线程返回";
});
try {
    String callBack = submit.get();
    System.out.println(callBack);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
} catch (ExecutionException e) {
    throw new RuntimeException(e);
}

线程的状态

image-20260122110828580

状态名称英文核心含义
新建NEW线程对象已创建,但尚未调用 start() 方法启动。
可运行RUNNABLE线程正在运行正在等待 CPU 调度(就绪)。
阻塞BLOCKED线程正在等待获取一个监视器锁(进入 synchronized 块/方法)。
无限等待WAITING线程无限期等待另一个线程执行特定操作(如 notify)。
计时等待TIMED_WAITING线程在指定时间后会自动唤醒(如 sleep(3000))。
终止TERMINATED线程执行完毕或因异常退出,生命周期结束。

当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是BLOCKED状态,而使用java.util.concurrent.locks下lock进行加锁的时候线程切换的是WAITING或者TIMED_WAITING状态,因为lock会调用LockSupport的方法。

线程状态的基本操作

interrupted

interrupted 是 Java 中 Thread 类的一个静态方法,它的作用是检测当前正在执行的线程是否已经被中断,并且它会“顺手”把中断状态给清除。在结束线程时通过中断标志位或者标志位的方式可以有机会去清理资源,相对于武断而直接的结束线程,这种方式要优雅和安全。

方法行为描述
interrupted()静态方法 (作用于当前线程)。 会清除中断状态 (读完即擦除),通常在线程内部检查自己是否该停止
isInterrupted()实例方法 (作用于调用它的线程对象)。只读不改 (状态保持原样),通常在外部检查某个线程的状态

运行中(RUNNABLE)interrupt() 只是设置标志位true。线程是否停止,全看它自己有没有检查这个标志位。

阻塞中(BLOCKED/TIMED_WAITING)interrupt()打断阻塞,强制唤醒线程并抛出 InterruptedException,同时自动清除标志位

join

一个线程A中调用另一个线程B的 join() 方法时,调用者A会立刻停下来等待,直到被调用者B彻底执行完毕,调用者才会继续往下走。

方法行为描述
join()无限等待。死等该线程结束,不等到天荒地老不罢休。
join(long millis)限时等待。最多等 millis 毫秒。如果超时了线程还没结束,就不等了,继续执行。
join(long millis, int nanos)限时等待(纳秒级)。精度更高,但很少用到。

sleep

暂停执行,让出 CPU。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁

方法行为描述
wait()死等。一直等到别人叫醒我(notify),否则永不醒来。
wait(long timeout)限时等待。最多等 timeout 毫秒。时间一到自动醒来。
wait(long timeout, int nanos)限时等待(纳秒级)。精度更高,很少用。

sleep() 和wait()

  1. sleep()方法是Thread的静态方法,而wait是Object实例方法
  2. wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
  3. sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

yield

一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。

守护线程Daemon

它的核心使命是为用户线程(User Thread,即前台线程) 提供服务(如垃圾回收、监控等)。一旦所有的用户线程(主角)都执行完毕,JVM 会立即退出,此时守护线程会被强制终止,无论它手头的工作是否做完。

通过给线程设置 setDaemon(true)可以将线程设置为守护线程。非常适合执行“可丢弃”的后台任务。

守护线程在退出的时候并不会执行finnaly块中的代码,所以将释放资源等操作不要放在finnaly块中执行,这种操作是不安全的

java
 Thread daemon = new Thread(() -> {
            System.out.println("守护线程运行中");
     });
        daemon.setDaemon(true); // 设置为守护线程
        daemon.start();
最近更新