18 - Java 线程

简介

进程(process)

对一个程序的运行状态, 以及在运行中所占用的资源(内存, CPU)的描述;

一个进程可以理解为一个程序; 但是反之, 一个程序就是一个进程, 这句话是错的。

进程的特点:

  • 独立性: 不同的进程之间是相互独立的, 相互之间资源不共享;
  • 动态性: 进程在程序中不是静止不动的, 而是一直是活动状态;
  • 并发性: 多个进程可以在一个处理器上同时运行, 互不影响;

线程(thread)

是进程的一个组成部分, 一个进程中可以包含多个线程, 每一个线程都可以去处理一项任务;

进程在开辟的时候, 会自动的创建一个线程, 这个线程叫做 主线程;

一个进程包含多个线程, 且至少是一个, 如果一个进程中没有线程了, 这个进程会被终止;

多线程的执行是抢占式的, 多个线程在同一个进程中并发执行任务, 其实就是CPU快速的在不同的线程之间进行切换。

进程与线程的关系和区别

  • 一个程序运行后, 至少有一个进程;
  • 一个进程包含多个线程, 至少一个线程;
  • 进程之间是资源不共享的, 但是线程之间是资源共享的;
  • 系统创建进程的时候, 需要为进程重新分配系统资源, 而创建线程则容易很多, 因此使用多线程在进行并发任务的时候, 效率比多进程高;

区别并行和并发

并行:指在同一时刻,有多条指令在多个处理器上同时执行 ;

并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行;

创建线程

实现 Runnable 接口

步骤
  • 声明类 implements Runnable 接口;
  • 重写 Runnable 接口中的 run() 方法,线程执行体;
  • 创建 Runnable 实现类的对象,并把这个对象作为 Thread 的 target 进行 Thread 对象的实例化,这个 Thread 对象才是真正的线程对象 ;采用 Runnable 接口的方式创建的多个线程可以共享同一个 target 对象的实例变量;
  • 调用 start 方法, 来启动线程 ;
//自定义线程执行任务类
public class MyRunnable implements Runnable{//定义线程要执行的run方法逻辑@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("我的线程:正在执行!"+i);}}
}public class Demo {public static void main(String[] args) {//创建线程执行目标类对象Runnable runn = new MyRunnable();//将Runnable接口的子类对象作为参数传递给Thread类的构造函数Thread thread = new Thread(runn);Thread thread2 = new Thread(runn);//开启线程thread.start();thread2.start();for (int i = 0; i < 10; i++) {System.out.println("main线程:正在执行!"+i);}}
}

继承 Thread 类

Thread 是所有线程类的父类, 实现了对线程的抽取和封装;

方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

步骤
  • 继承 Thread 类, 写一个 Thread 的子类 ;
  • 在子类中, 重写父类中的 run 方法, run 方法就代表了这个线程需要处理的任务(希望这个线程处理什么任务,就把这个任务写到 run 方法中), 因此, run 方法也被称为线程执行体 ;
  • 实例化这个子类对象,即是开辟了一个线程 ;
  • 调用 star t方法, 来执行这个线程需要处理的任务(启动线程);
//自定义线程类
public class MyThread extends Thread {//定义指定线程名称的构造方法public MyThread(String name) {//调用父类的String参数的构造方法,指定线程的名称super(name);}/*** 重写run方法,完成该线程执行的逻辑*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName()+":正在执行!"+i);}}
}public class Demo{public static void main(String[] args) {//创建自定义线程对象MyThread mt = new MyThread("新的线程!");//开启新线程mt.start();//在主方法中执行for循环for (int i = 0; i < 10; i++) {System.out.println("main线程!"+i);}}
}// 使用匿名内部类的方式创建
new Thread() {public void run() {}
}.start();

通过 Callable 和 Future 创建线程

Callable 接口
  • Callable 接口提供了一个 call() 方法(可以有返回值,可以声明抛出异常)可以作为线程执行体,Callable 接口里的泛型形参类型与 call() 方法返回值类型相同。
  • V call():计算结果,如果无法计算结果,则抛出一个异常。
Future 接口
  • Future 接口代表 Callable 接口里 call() 方法的返回值,表示异步计算的结果;
  • Future 接口的常用方法 V get():返回 Callable 任务里 call() 方法的返回值,如果计算抛出异常将会抛出 ExecutionException 异常,如果当前的线程在等待时被中断将会抛出 InterruptedException 异常(调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值);
  • V get(long timeout, TimeUnit unit):返回 Callable 任务里 call() 方法的返回值,该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常
  • boolean cancel(boolean maylnterruptlfRunning):试图取消该 Future 里关联的 Callable 任务 boolean isCancelled():如果在 Callable 任务正常完成前被取消,则返回 true ;
  • boolean isDone():如果 Callable 任务已完成,则返回 true;
FutureTask 类
  • FutureTask 实现类实现了 RunnableFuture 接口(RunnableFuture接口继承了 Runnable 接口和Future 接口)
  • 构造器:FutureTask(Callable callable)、FutureTask(Runnable runnable, V result)(指定成功完成时 get 返回给定的结果为 result)
步骤
  • 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值;
  • 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;
  • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
  • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值;
public class CallableThreadTest implements Callable<Integer> {public static void main(String[] args)  {  CallableThreadTest ctt = new CallableThreadTest();  FutureTask<Integer> ft = new FutureTask<>(ctt);  for(int i = 0;i < 100;i++)  {  System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  if(i==20)  {  new Thread(ft,"有返回值的线程").start();  }  }  try  {  System.out.println("子线程的返回值:"+ft.get());  } catch (InterruptedException e)  {  e.printStackTrace();  } catch (ExecutionException e)  {  e.printStackTrace();  }  }@Override  public Integer call() throws Exception  {  int i = 0;  for(;i<100;i++)  {  System.out.println(Thread.currentThread().getName()+" "+i);  }  return i;  }  
}

创建线程的三种方式对比

继承 Thread 类

  • 线程类已经继承了 Thread 类,不能再继承其它父类
  • 如果需要访问当前线程,直接使用 this 即可获得当前线程
  • 多个线程之间无法共享线程类中的实例变量

实现 Runnable、Callable 接口的方式创建多线程

  • 线程类只是实现了 Runnable 接口,还可以继承其它类
  • 如果需要访问当前线程,则必须使用 Thread. currentThread() 方法
  • 所创建的 Runnable 对象只是线程的 target,而多个线程可以共享同一个 target 对象的实例变量,所以适合多个相同线程来处理同一份资源的情况

strart() 和 run() 的区别

start() 方法会开辟一个线程, 然后在这个新的线程中执行 run() 中的逻辑,但是如果直接调用 run(),则表示需要在当前的线程中执行逻辑。

Runnable与Callable

相同点

  • 都是接口,都可以编写多线程程序,都采用Thread.start()启动线程;

不同点

  • Runnable没有返回值;Callable可以返回执行结果,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果;
  • Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛;
  • Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。;

线程的生命周期

操作系统中线程的生命周期

操作系统中线程的生命周期通常包括以下五个阶段:

  • 新建状态(New):使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态(Runnable):当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态(Running):如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态(Blocked):如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 终止状态(Terminated):一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

Java 中线程的生命周期

Java 中线程的生命周期可以细化为以下几个状态:

  • New(初始状态):线程对象创建后,但未调用 start()方法。
  • Runnable(可运行状态):调用 start()方法后,线程进入就绪状态,等待 CPU 调度。
  • Blocked(阻塞状态):线程试图获取一个对象锁而被阻塞。
  • Waiting(等待状态):线程进入等待状态,需要被显式唤醒才能继续执行。
  • Timed Waiting(含等待时间的等待状态):线程进入等待状态,但指定了等待时间,超时后会被唤醒。
  • Terminated(终止状态):线程执行完成或因异常退出。

线程优先级

Java 程序中的每个线程并不是平均分配CPU时间的,为了使得线程资源分配更加合理,Java采用的是抢占式调度方式,优先级越高的线程,优先使用CPU资源。

希望CPU花费更多的时间去处理更重要的任务,而不太重要的任务,则可以先让出一部分资源。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY ),默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

线程的优先级一般分为以下三种:

  • MIN_PRIORITY  最低优先级
  • MAX_PRIORITY  最高优先级
  • NOM_PRIORITY  常规优先级
public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("线程开始运行!");});t.start();t.setPriority(Thread.MIN_PRIORITY);  //通过使用setPriority方法来设定优先级
}

优先级越高的线程,获得CPU资源的概率会越大,并不是说一定优先级越高的线程越先执行。

线程安全

保证多线程环境下共享的、可修改的状态的正确性。

线程安全需要保证几个基本特性:

常用的线程安全措施

  • 同步锁:通过 synchronized 关键字或 ReentrantLock 实现对共享资源的同步控制。
  • 原子操作类:Java 提供的 AtomicInteger、AtomicReference 等类确保多线程环境下的原子性操作。
  • 线程安全容器:如ConcurrentHashMap、CopyOnWriteArrayList 等,避免手动加锁。
  • 局部变量:线程内独立的局部变量天然是线程安全的,因为每个线程都有自己的栈空间(线程隔离)。
  • ThreadLocal:类似于局部变量,属于线程本地资源,通过线程隔离保证了线程安全。

线程同步

  • 原子操作(atomic operation):不可被中断的一个或一系列操作。
  • 只需要对那些会改变共享资源的、不可被中断的操作进行同步即可。
  • 保证在任一时刻只有一个线程可以进入修改共享资源的代码区,其它线程只能在该共享资源对象的锁池中等待获取锁。
  • 在 Java 中,每一个对象都拥有一个锁标记(monitor),也称为监视器。
  • 线程开始执行同步代码块或同步方法之前,必须先获得对同步监视器的锁定才能进入同步代码块或者同步方法进行操作。
  • 当前线程释放同步监视器:当前线程的同步代码块或同步方法执行结束,遇到 break 或 return 语句,出现了未处理的 Error 或 Exception,执行了同步监视器对象的 wait() 方法或 Thread.join() 方法。
  • 当前线程不会释放同步监视器:当前线程的同步代码块或同步方法中调用 Thread. sleep()、Thread.yield() 方法其它线程调用了该线程的 suspend() 方法。

synchronized

同步代码块

通常推荐使用可能被并发访问的共享资源作为同步监视器对象。

//同步代码块
synchronized(同步监视器对象) { // 得到对象的锁,才能操作同步代    码需要被同步代码;
}
同步方法
  • 使用 synchronized 关键字来修饰某个方法,就相当于给调用该方法的对象加了锁
  • 对于实例方法,同步方法的同步监视器是 this,即调用该方法的对象
  • 对于类方法,同步方法的同步监视器是当前方法所在类的字节码对象(如 ArrayUtil.class)
  • 不要使用 synchronized 修饰 run() 方法,而是把需要同步的操作定义在一个新的同步方法中,再在 run() 方法中调用该方法
//同步方法
public synchronized void m(String name){需要被同步代码;
}
public class SellTicket {public static void main(String[] args) {SellTicket03 sellTicket03 = new SellTicket03();new Thread(sellTicket03).start();//第1个线程-窗口new Thread(sellTicket03).start();//第2个线程-窗口new Thread(sellTicket03).start();//第3个线程-窗口}
}//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {private int ticketNum = 100;//让多个线程共享 ticketNumprivate boolean loop = true;//控制run方法变量Object object = new Object();//同步方法(静态的)的锁为当前类本身//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class//2. 如果在静态方法中,实现一个同步代码块./*synchronized (SellTicket03.class) {System.out.println("m2");}*/public synchronized static void m1() {}public static  void m2() {synchronized (SellTicket03.class) {System.out.println("m2");}}//1. public synchronized void sell() {} 就是一个同步方法//2. 这时锁在 this对象//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法synchronized (/*this*/ object) {if (ticketNum <= 0) {System.out.println("售票结束...");loop = false;return;}//休眠50毫秒, 模拟try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2}}@Overridepublic void run() {while (loop) {sell();//sell方法是一共同步方法}}
}

synchronized 使用方式

  • 修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
  • 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
  • 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

总结

  • synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
  • synchronized 关键字加到实例方法上是给对象实例上锁。
  • 尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能。

锁(Lock)

锁的分类名词

锁的分类名词,有的是指锁的状态、有的指锁的特性、有的指锁的设计。

公平锁/非公平锁

公平锁是指多个线程按照申请所的顺序来获取锁。

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能是后申请的线程比先申请的线程有限获取锁。有可能,会造成优先级反转或者饥饿现象。

非公平锁的优点在于吞吐量比公平锁大。

在Java中,synchronized是一种非公平锁。

ReentrantLock则可以通过构造函数指定该锁是否公平锁,默认是非公平锁。ReentrantLock通过AQS来实现线程调度,实现公平锁。

可重入锁

可重入锁又名递归锁,是指在同一个线程在持有锁的前提下,再遇到需要申请同一个锁的情况时可自动获取锁。而非可重入锁遇到这种情况会形成死锁,也就是“我申请我已经持有的锁,我不会释放锁也申请不到锁,所以形成死锁。”

Java中,synchronized在JDK 1.6优化后,属于可重入锁。

ReentrantLock,即Re entrant Lock,可重入锁。

synchronized void A(){System.out.println("A获取锁!");B();
}
synchronized void B(){System.out.println("B锁重入成功!");
}

 独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有,共享锁是指该锁可被多个线程所持有。

在Java中,

synchronized属于独享锁。

ReentrantLock也属于独享锁。

而Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证高效的并发读,但是读写、写读、写写的过程是互斥的,防止脏读、数据丢失。独享锁和共享锁也是通过AQS实现的。

互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

互斥锁在Java中的具体实现就是ReentraLock。

读写锁在Java中的具体实现就是ReadWriteLock。

乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

悲观锁认为对同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式,悲观地认为,不加锁的并发操作一定会出现问题。

乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断更新的方式更新数据,乐观地认为,不加锁的并发操作是没有事情的。

从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

悲观锁在Java中的使用就是利用各种锁。

乐观锁在Java中的使用就是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为segment,它类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道它要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不死放在一个酚酸中,就实现了真正的并行插入。

但是在统计size的时候,即获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

分段锁的设计目的就是细化锁的粒度,当操作不需要更新整个数组的时候,就针对数据的一项进行加锁操作。

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

这三种所是指锁的状态,并且是针对synchronized。在 java 6通过引入锁的升级机制来实现高效synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。

轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

自旋锁

在Java中。自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

死锁

死锁是一个经典的多线程问题。避免死锁重要吗?一旦一组Java线程发生死锁,那么这组线程及锁涉及其已经持有锁的资源区将不再可用--除非重启应用。

死锁是设计上的bug,它并不一定发生,但它有可能发生,而且发生的情况一般出现在极端的高负载的情况下。

那么有什么办法为了避免死锁?

  1. 让程序每次至多只能获得一个锁。但这个在多线程环境下通常不现实。
  2. 设计时考虑清楚锁的顺序,尽量减少潜在的加锁交互数量
  3. 避免使用synchronized,为线程等待设置等待上限,避免无限等待。

Java 中锁的使用

JDK 1.8 后的锁:

  • synchronized :非公平锁;可重入锁;独享锁;偏向锁/轻量级锁/重量级锁;
  • ReentrantLock:非公平锁;可重入锁;独享锁;互斥锁;
  • ReadWriteLock:读锁是共享锁,写锁是独享锁;读写锁;
  • StampedLock:读写锁;乐观锁;

在Java中,有多种方式可以实现线程锁,以下是一些常见的方式:

使用 synchronized 关键字

synchronized 关键字可以用来实现方法或者代码块的同步,确保同一时刻只有一个线程执行被同步的代码。

public synchronized void synchronizedMethod() {// 需要同步的代码
}// 或者使用同步代码块public void nonSynchronizedMethod() {synchronized (this) {// 需要同步的代码}
}

使用 ReentrantLock

ReentrantLock 是一个可重入的锁,它比 synchronized 更灵活,提供了更多的功能,比如等待可中断、可实现公平锁等候时间最长的线程优先获取锁等。

import java.util.concurrent.locks.ReentrantLock;public class LockExample {private final ReentrantLock lock = new ReentrantLock();public void lockMethod() {lock.lock();try {// 需要同步的代码} finally {lock.unlock();}}
}

使用 StampedLock

StampedLock 是JDK8引入的新的锁机制,它提供了一种乐观锁的实现,相比于 ReentrantLock,它的读写锁机制更加灵活,可以有更高的并发。

import java.util.concurrent.locks.StampedLock;public class StampedLockExample {private final StampedLock lock = new StampedLock();public void readMethod() {long stamp = lock.tryReadLock();try {// 读操作} finally {lock.unlockRead(stamp);}}public void writeMethod() {long stamp = lock.writeLock();try {// 写操作} finally {lock.unlockWrite(stamp);}}
}

使用 ReadWriteLock

ReadWriteLock 接口定义了读写锁的两个锁,一个是只对数据进行读操作的锁,另一个是只对数据进行写操作的锁。

import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockExample {private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();private final Lock r = rwl.readLock();private final Lock w = rwl.writeLock();public void readMethod() {r.lock();try {// 读操作} finally {r.unlock();}}public void writeMethod() {w.lock();try {// 写操作} finally {w.unlock();}}
}

线程控制

Java中,线程控制主要涉及到以下几个方面:

  • 线程的启动
Thread t1 = new Thread(new Runnable() {public void run() {System.out.println("线程启动");}
});
t1.start();
  • 线程的等待
Thread t2 = new Thread(new Runnable() {public void run() {try {Thread.sleep(1000); // 休眠1秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程等待结束");}
});
t2.start();
  • 线程的中断
Thread t3 = new Thread(new Runnable() {public void run() {while (!Thread.currentThread().isInterrupted()) {// 执行任务}System.out.println("线程被中断");}
});
t3.start();
t3.interrupt();
  • 线程的优先级控制
Thread t4 = new Thread(new Runnable() {public void run() {System.out.println("线程的优先级: " + Thread.currentThread().getPriority());}
});
t4.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
t4.start();
  • 线程的合并
Thread t5 = new Thread(new Runnable() {public void run() {System.out.println("被合并的线程");}
});
t5.start();
t5.join(); // 等待t5线程结束,然后继续执行主线程
  • 线程的同步与协调
final Object lock = new Object();Thread t6 = new Thread(new Runnable() {public void run() {synchronized (lock) {try {lock.wait(); // 线程等待} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程被唤醒");}
});Thread t7 = new Thread(new Runnable() {public void run() {synchronized (lock) {lock.notify(); // 唤醒等待的线程}}
});t6.start();
// 确保t6线程已经开始等待
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
t7.start();

用户线程和守护线程

  • 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束 。
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束setDaemon(True) 。
  • 常见的守护线程:垃圾回收机制 。

线程组

  • ThreadGroup 类,表示一个线程的集合,可以对一组线程进行集中管理(同时控制这批线程)
  • 在默认情况下,子线程和创建它的父线程处于同一个线程组内

线程让步

  • 让执行的线程暂停,进入就绪状态。
  • 当某个线程调用了 yield() 方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。

同步机制与 ThreadLocal

  • 如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制。
  • 如果仅仅需要隔离多个线程之间的共享冲突,则可以使用 ThreadLocal。

线程通信

线程通信机制

并发模型

通信机制

同步机制

共享内存

线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信

同步是显式进行的,即必须显式指定某个方法或某段代码需要在线程之间互斥执行

消息传递

线程之间通过显式的发送消息来达到交互目的,如 Actor 模型

由于消息的发送必须在消息的接收之前,因此同步是隐式进行的

  • Java 的线程间通过共享内存的方式进行通信

使用 Object 类中的方法

  • Object 类中用于操作线程通信的实例方法
    • wait():调用该方法的当前线程会释放对该同步监视器(调用者)的锁定,JVM 把该线程存放到等待池中,等待其他的线程唤醒该线程(该方法声明抛出了 InterruptedException 异常)(为了防止虚假唤醒,此方法应始终在循环中使用,即被唤醒后需要再次判断是否满足唤醒条件)
    • notify():调用该方法的当前线程唤醒在等待池中的任意一个线程,并把该线程转到锁池中等待获取锁
    • notifyAll():调用该方法的当前线程唤醒在等待池中的所有线程,并把该线程转到锁池中等待获取锁
  • 这些方法必须在同步块中使用,且只能被同步监视器对象来调用,否则会引发 IllegalMonitorStateException 异常
public class ShareResource {// 标识数据是否为空(初始状态为空)private boolean empty = true;// 需要同步的方法public synchronized void doWork() {try {while (!empty) { // 不空,则等待this.wait();}... // TODOempty = false; // 修改标识this.notifyAll(); // 通知其它线程} catch (InterruptedException e) {e.printStackTrace();}}
}

使用 Condition 接口中的方法

  • java.util.concurrent.locks 包中,Condition 接口中的 await()、signal()、signalAll() 方法替代了 Object 监视器方法的使用(await() 方法也声明抛出了 InterruptedException 异常)
  • 通过 Lock 对象调用 newCondition() 方法,返回绑定到此 Lock 对象的 Condition 对象
public class ShareResource {// 创建使用 private final 修饰的锁对象private final Lock lock = new ReentrantLock();// 获得指定 Lock 对象对应的 Conditionprivate final Condition cond = lock.newCondition();// 标识数据是否为空(初始状态为空)private boolean empty = true;// 需要同步的方法public void doWork() {lock.lock(); // 进入方法后,立即获取锁try {while(!empty) { // 判断是否方法阻塞cond.await();}... // TODOempty = false; // 修改标识cond.signalAll(); // 通知其它线程} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock(); // 使用 finally 块释放锁}}
}

线程常用方法

Thread 对象调用的方法(实例方法)

public void start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法,只能被处于新建状态的线程调用,否则会引发 IllegalThreadStateException 异常。

public void run():如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

public final void setName(String name):改变线程名称,使之与参数 name 相同,为线程设置名字,在默认情况下,主线程的名字为 main,用户启动的多个线程的名字依次为 Thread-0、Thread-1、Thread-2、…、Thread-n 等。

public final void setPriority(int priority):更改线程的优先级(范围是 1~10 之间)。

public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程,on 为"true"时,将该线程设置成守护线程,该方法必须在 start() 之前调用,否则会引发 IllegalThreadStateException 异常。

boolean isDaemon():判断该线程是否为守护线程。

public final void join(long millisec):等待该线程终止的时间最长为 millis 毫秒,而当前正在执行的线程进入阻塞状态(联合线程)(该方法声明抛出了 InterruptedException 异常)。

public void interrupt():中断线程。

public final boolean isAlive():测试线程是否处于活动状态。

Thread 类的静态方法(类方法)

public static void yield():暂停当前正在执行的线程对象,并执行其他线程,转入就绪状态(线程让步)。

public static void sleep(long millisec):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响,并进入阻塞状态(线程睡眠)(该方法声明抛出了 InterruptedException 异常)。

public static boolean holdsLock(Object x):当且仅当,当前线程在指定的对象上保持监视器锁时,才返回 true。

public static Thread currentThread():返回对当前正在执行的线程对象的引用。

public static void dumpStack():将当前线程的堆栈跟踪打印至标准错误流。

线程相关方法

阻塞和唤醒的方法,是 Object 中的方法,必须在synchronized修饰的代码块或者方法内部才可以调用(因为要操作基于某个对象的锁的信息维护)。

wait():让获取 synchronized 锁资源的线程,进入锁的等待池,并且释放锁资源。

notify():让获取 synchronized 锁资源的线程,唤醒等待池中的线程,并且添加到锁池中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/33608.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

芯科科技突破性超低功耗Wi-Fi 6和低功耗蓝牙5.4模块加速设备部署

致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;今日宣布推出SiWx917Y超低功耗Wi-Fi 6和低功耗蓝牙&#xff08;Bluetooth LE&#xff09;5.4模块。 作为成功的第二代无线开发平台的新产品&…

视频码率到底是什么?详细说明

视频码率&#xff08;Video Bitrate&#xff09;是指在单位时间内&#xff08;通常是每秒&#xff09;传输或处理的视频数据量&#xff0c;用比特&#xff08;bit&#xff09;表示。它通常用来衡量视频文件的压缩程度和质量&#xff0c;码率越高&#xff0c;视频质量越好&#…

MongoDB分片集群架构实战

分片集群架构 分片简介 分片&#xff08;shard&#xff09;是指在将数据进行水平切分之后&#xff0c;将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”。不同的点在于&#xff0c;MongoDB本身就自带了分片管理的能力&#…

基于SpringBoot+Vue的宠物咖啡馆系统-无偿分享 (附源码+LW+调试)

目录 1. 项目技术 2. 功能菜单 3. 部分功能截图 4. 研究背景 5. 研究目的 6. 可行性分析 6.1 技术可行性 6.2 经济可行性 6.3 操作可行性 7. 系统设计 7.1 概述 7.2 系统流程和逻辑 7.3 系统结构 8. 数据库设计 8.1 数据库ER图 &#xff08;1&#xff09;宠物订…

请求响应:常见参数接收及封装(Json参数及路径参数)

Json参数 Json格式的数据具有轻量级、易于阅读和编写、易于解析等诸多优点。在前后端交互时&#xff0c;大部分情况下请求体中的数据会以JSON格式进行传递。前端的请求在请求体中携带了Json格式数据&#xff0c;后端程序需要对其进行解析并封装使用&#xff0c;而接收Json参数…

TDengine 新功能 Count 计数窗口

TDengine v3.3.0.0 版本是一个新增了大量新功能的大版本&#xff0c;从体系结构变化及新增功能的规模仅次于从 2.6 版本升级至 3.0 的变动。从今天开始我逐渐向大家介绍这个版本的新功能&#xff0c; 今天先从一个简单的 Count 计算窗口开始。 Count 窗口可以使用在按固定数量进…

华为、华三交换机纯Web下如何创关键VLANIF、操作STP参数

华为交换机WEB操作 使用的是真机S5735&#xff0c;目前主流的版本都适用&#xff08;V1R5~V2R1的就不在列了&#xff0c;版本太老了&#xff0c;界面完全不一样&#xff0c;这里调试线接的console口&#xff0c;电脑的网络接在ETH口&#xff09; 「模拟器、工具合集」复制整段内…

如何将 Docker 镜像打包为 ZIP 文件便于分享和转发

在开发和部署中&#xff0c;我们常常需要将 Docker 镜像转发给其他团队成员或部署到不同的环境。如果无法直接访问镜像仓库&#xff08;如 Docker Hub 或私有镜像仓库&#xff09;&#xff0c;可以选择将镜像保存为文件&#xff0c;并通过 ZIP 格式打包后进行分享。 本文将介绍…

【从零开始的LeetCode-算法】11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示例…

cuda12.1版本的pytorch环境安装记录,并添加到jupyter和pycharm中

文章目录 前置准备使用anaconda prompt创建虚拟环境创建虚拟环境激活pytorch虚拟环境把pytorch下载到本地使用pip把安装包安装到pytorch环境中进入python环境检验是否安装成功将环境添加到jupyter在pycharm中使用该环境&#xff1a; 前置准备 安装anaconda&#xff0c;我的版本…

Alogrithm:费式数列

1. 说明 Fibonacci 为1200年代的欧洲数学家&#xff0c;在他的著作中曾经提到&#xff1a;「若有一只免子每个月生一只小免子&#xff0c;一个月后小免子也开始生产。起初只有一只免子&#xff0c;一个月后就有两只免子&#xff0c;二个月后有三只免子&#xff0c;三个月后有五…

Docker Compose 和 Kubernetes 之间的区别?

一、简介&#x1f380; 1.1 Docker Compose Docker Compose 是 Docker 官方的开源项目&#xff0c;负责实现对 Docker 容器集群的快速编排&#xff0c;可以管理多个 Docker 容器组成一个应用。你只需定义一个 YAML 格式的配置文件 docker-compose.yml &#xff0c;即可创建并…

如何利用python爬虫获取淘宝评论item_review接口

根据搜索结果&#xff0c;以下是利用Python爬虫获取淘宝评论item_review接口的方法&#xff1a; 创建应用并获取API密钥&#xff1a; 在淘宝开放平台创建一个新的应用&#xff0c;并选择合适的API权限。创建完成后&#xff0c;你将获得一个App Key和App Secret&#xff0c;用于…

Istio sidecar 和 ambient 模式的网络成本对比

深入对比 Istio sidecar 和 ambient 模式的网络成本与性能&#xff0c;分析其本地性感知及排查方法。 阅读原文请转到&#xff1a;https://jimmysong.io/blog/istio-sidecar-vs-ambient-network-cost-performance/ 在服务网格架构不断演进的过程中&#xff0c;了解不同部署模式…

管理表(四)

创建表空间 CREATE TABLESPACE assm DATAFILE /u01/oradata/denver/assm_1.dbf SIZE 100M EXTENT MANAGEMENT LOCAL UNIFORM SIZE 128K SEGMENT SPACE MANAGEMENT AUTO;size 100M&#xff1a;设定了这个数据文件的初始大小为100兆字节&#xff08;MB&#xff09;。exterl mana…

《无畏契约》运行时提示“d3dcompiler_43.dll丢失”是什么原因?“找不到d3dcompiler_43.dll文件”如何解决?

《无畏契约》运行时提示“d3dcompiler_43.dll丢失”是什么原因&#xff1f;“找不到d3dcompiler_43.dll文件”如何解决&#xff1f; 作为一位软件开发从业者&#xff0c;我深知在游戏运行过程中&#xff0c;遇到各种文件丢失、文件损坏和系统报错等问题是多么令人头疼。今天&a…

5.12【机器学习】卷积模型搭建

softmax输出时不可能为所有模型提供精确且数值稳定的损失计算 model tf.keras.models.Sequential([tf.keras.layers.Flatten(input_shape(28, 28)),tf.keras.layers.Dense(128, activationrelu),tf.keras.layers.Dropout(0.2),tf.keras.layers.Dense(10) ]) mnist tf.keras…

【H2O2|全栈】Node.js与MySQL连接

目录 前言 开篇语 准备工作 初始配置 创建连接池 操作数据库 封装方法 结束语 前言 开篇语 本节讲解如何使用Node.js实现与MySQL数据库的连接&#xff0c;并将该过程进行函数封装。 与基础部分的语法相比&#xff0c;ES6的语法进行了一些更加严谨的约束和优化&#…

Stable Diffusion Controlnet常用控制类型解析与实战课程 2

本节内容&#xff0c;给大家带来的是stable diffusion Controlnet常用控制类型解析与实战的第二节课程。在上期课程中&#xff0c;我们已经了解了关于线稿类控制类型的特征和用法&#xff0c;本节课程&#xff0c;我们将继续讲解一些常用的控制类型。 一&#xff1a;OpenPose …

TC3xx系列芯片--GPT12模块介绍

1、模块介绍 GPT1/2&#xff08;General Purpose Timer Unit)是 Aurix TC3XX 内部的通用定时器模块&#xff0c;提供高精度定时功能&#xff0c;GPT1/2 包含 GPT1 和 GPT2 两个子模块&#xff0c;通用定时器单元块 GPT1 和 GPT2 具有非常灵活的多功能定时器结构&#xff0c;可…