Java内置了对多线程的支持, 线程和进程的区别:进程是操作系统资源分配的基本单位,线程是操作系统的最小单元,操作系统运行一个程序时,就会为其创建一个进程,进程中可能包含多个线程 * 例子:一个Java程序,从最开始的mian()方法执行,然后按照既定的逻辑执行,看似没有其它的线程参与,但是,Java程序本身就是一个多线程的程序,是main线程和多个其它线程同时运行。 * 为什么使用多线程:执行一个Hello World,却启动了那么多无关线程,是不是把简单的问题复杂化了? 使用多线程的原因:更多的处理器核心、更快的响应时间、更好的编程模型 两种线程:用户线程、守护线程 * 用户线程:用户自定义创建的线程,主线程停止,用户线程不会停止 * 守护线程:当进程不存在或主线程停止,守护线程也会被停止。使用thread.setDaemon(true)方法设置为守护线程。 如何终止一个线程? * 1.执行结束 * 2.new Thread.stop()、new Thread.destroy() ,不推荐使用,都已过期 * 3.中断线程,最优雅的方式,new Thread.interrupt() 或 Thread.interrupted() 区别:interrupted():返回当前线程的中断标志位,并设置中断标志位false; interrupt():设置线程对象的中断标志位为 true; isInterrupted():返回线程对象的中断标志位; interrupted()属于类方法,而interrupt()和isInterrupted()属于对象方法。 内存模型相关概念:计算机在执行程序时,每条指令都是在CPU中执行,而执行指令过程中,势必涉及到数据的读取和写入,临时数据是存放在主存(物理内存),速度慢,所以需要cpu高速缓存。 * 由于CPU执行速度很快,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中 JMM(Java内存模型):屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能 达到一致的内存访问效果。它定义了程序中代码的执行次序 什么是线程安全:可见性,原子性,有序性(也是并发编程中的三个概念) * 原子性:在Java中,对基本数据类型变量的读取和赋值操作是原子性操作,即这些操作不可被中断,要么执行,要么不执行 * 可见性:Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,会去内存中读取新值。 * 有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。通过volatile可保证一定的“有序性”,另外可以通过synchronized和Lock来保证有序性
获取程序执行的线程信息
public class MultiThread {
public static void main(String[] args) {
// 获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " +
threadInfo.getThreadName());
}
// =》得出结论:Java程序执行,是main线程和多个其它线程同时运行。
}
}
线程生命周期 * 1、新建状态:使用 new 或其子类建立一个线程对象后,该线程就处于新建状态。它保持这个状态直到调用start() * 2、就绪状态:当线程对象调用start()方法后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,等待JVM里线程调度器的调度 * 3、运行状态:就绪状态的线程获取 CPU 资源后,就会执行 run(),此时线程便处于运行状态。运行状态的线程最复杂,它可以变为阻塞状态、就绪状态和死亡状态。 * 4、阻塞状态:一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源后,就会进入阻塞状态,在睡眠时间已到或获得设备资源后可以重新进入就绪状态。 可以分为三种情况: 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。 * 5、死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时(如调用interrupt()方法),该线程就切换到终止状态。 为什么线程执行不直接run()启动执行,而要先start()启动,再run()? 线程启动使用start(),run()只是一个回调方法而已。如果你直接调用run()方法,JVM是不会去创建线程的,run()方法只能用于已有线程中。Java里面创建线程之后必须要调用start方法才能真正的创建一个线程,该方法会调用虚拟机启动一个本地线程,本地线程的创建会调用当前系统创建线程的方法进行创建,并且线程被执行的时候会回调 run方法进行业务逻辑的处理。 thread.join() 控制线程执行顺序: t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒,不影响同一时刻处在运行状态的其他线程。 Yield方法 thread.yield():暂停当前正在执行的线程,并执行其他线程。(可能没有效果) 使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中 线程的执行顺序: 真正取决于CPU调度器(在Java中是JVM来调度),我们无法控制 可控操作:设置线程优先级,maxThread.setPriority(Thread.MAX_PRIORITY); 将线程 maxThread设置为最大值10 -》具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源 -》但是,线程优先级并不能一定保证线程执行的顺序,因为它有很大的随机性,只有抢到CPU资源的线程才会执行,只是优先级高的线程抢占到CPU资源的机会更大, 多执行几遍下面程序,可发现线程max并不总是先执行
public class PriorityDemo {
public static void main(String[] args) {
//创建线程max最大
Thread max = new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("max");
}
}
};
//创建线程min最小
Thread min = new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("min");
}
}
};
//创建线程norm默认
Thread norm = new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("norm");
}
}
};
//将线程max设置为最大值10
max.setPriority(Thread.MAX_PRIORITY);
//将线程min设置为最小大值1
min.setPriority(Thread.MIN_PRIORITY);
min.start();
norm.start();
max.start();
}
}
线程实例化3种方式
* 1、继承Thread类,重写run方法
* 2、实现Runnable接口,实现run方法
* 3、实现Callable接口,重写call方法
区别:Callable的任务执行后可返回值,Runnable不能;call方法可以抛出异常,run方法不可以
(当需要让子线程在执行完成以后,提供一个返回值给到当前的主线程,使用Callable接口会非常适用)
通过 Callable 和 Future 创建线程,带返回值的,示例如下:
* 1)创建 Callable 实现类实例,使用 FutureTask类 来包装该Callable实现类实例对象
* 2)创建并启动新线程,使用 FutureTask 对象作为 新线程 对象(即子线程)的 target
* 3)调用 FutureTask 对象的 get() 方法来获得子线程执行 call() 结束后的返回值
public class ImplCallable implements Callable<Integer> {
public static void main(String[] args) {
ImplCallable callable = new ImplCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
if (i == 2) {
new Thread(futureTask, "有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() {
int i = 0;
for (; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}