线程间通信
线程间通信有两种方式:消息传递、共享内存,在Java中,线程采用共享内存的并发模式进行通信,这种模式被称为JMM,使用这种模式,一个线程对共享变量写入时,另一个线程是可见的。
线程间的共享变量存放在主内存中,每一个线程有一个私有的变量本地内存,存储了共享变量的副本。
线程A、B通信的流程:
1、线程 A 把本地内存 A 中的共享变量副本刷新到主内存中。
2、线程 B 到主内存中读取线程 A 刷新过的共享变量,再同步到自己的共享变量副本中。
实现通信的方法
1、volatile和synchronized关键字:前者用于修饰成员变量,告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,保证所有线程对变量访问的可见性;后者修饰方法,或者以同步代码块的形式来使用,确保多个线程在同一个时刻,只能有一个线程在执行某个方法或某个代码块。
2、等待/通知机制:使用wait、notify等。
3、管道输入输出流:主要包括了如下 4 种具体实现:PipedOutputStream、PipedInputStream、 PipedReader 和 PipedWriter,前两种面向字节,而后两种面向字符。
4、使用Thread.join:若线程A执行了thread.join()方法,则当前线程 A 等待 thread 线程终止之后才从thread.join()返回。
5、使用ThreadLocal:是 Java 中提供的一种用于实现线程局部变量的工具。它允许每个线程都拥有自己的独立副本,从而实现线程隔离,可用于解决多线程中共享对象的线程安全问题。
线程实现的三种方式
1、继承Thread类,并调用start方法;
2、实现Runnable接口,重写run方法,在启动线程时,需new出重写的方法,并将其作为Thread的参数,如类A实现了Runnable接口,则启动线程时,首先通过Thread t = new Thread(new A())进行创建,再使用t.start()启动;
3、实现Callable接口,重写run方法,启动线程时,若A实现了Callable接口,则先定义FutureTask,使用FutureTask<T> f = new FutureTask<T>(new A()),T为泛型,在实现Callable接口时指定,然后通过Thread t = new Thread(f)创建线程,并调用start方法启动,对于定义的FutureTask,使用get方法获取任务执行的返回值。
为什么要调用start进行线程启动
在JVM中,调用start方法会先创建一条线程,由创建出来的新线程去执行thread的run方法,这才起到多线程的效果,而调用run方法则run方法在主线程中执行,达不到多线程的效果。
sleep与wait的区别
1、sleep是Thread类中的方法,wait是Object类中的方法;
2、执行sleep方法时,不释放和获得任何锁,执行wait方法时,会释放该共享变量的锁;
3、sleep可在任意条件下使用,wait只能在同步代码块或同步方法中使用,使用wait的前提是当前线程必须持有变量的锁;
4、sleep在指定时间后自动唤醒,wait需依靠notify、notifyAll或等待wait中设置的时间到后才能唤醒。
线程上下文切换
CPU资源的分配采用了时间片轮转,即给每个线程分配一个时间片,线程在时间片内占用CPU执行任务,当线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程占用,这就是上下文切换。
守护线程
Java中包含了用户线程、守护线程两种,用户线程必须要所有用户线程结束后,JVM才能退出,若守护线程此时未结束,也会退出,其优先级与主线程一致,且其子线程也为守护线程,通过setDaemon(true)可将线程设置为守护线程,且必须在线程调用start启动前设置。