前言多线程是大一 Java 课程的重点内容也是期末考试、计算机二级的高频考点。很多同学刚学多线程时分不清进程和线程写售票程序时经常出现车票超卖不知道怎么解决线程安全问题。本篇文章全部使用大一能看懂的基础语法搭配完整可运行代码把线程的全部基础知识点讲清楚。一、进程和线程的区别1. 基础概念进程电脑上运行的软件就是一个进程。比如你打开 QQ、浏览器每一个软件都会单独占用一块内存进程之间互不干扰。进程是操作系统分配电脑内存资源的最小单位。线程一个进程里面可以同时运行多条线程。比如音乐软件一边播放歌曲一边下载歌词播放音乐、下载歌词就是两条独立的线程。线程是 CPU 执行任务的最小单位。2. 通俗例子把进程比作一家工厂工厂有厂房、原材料线程就是工厂里的工人多名工人共用工厂的厂房和物料各自干自己的活。3. 简单对比对比项进程线程资源有独立内存空间共用进程的内存开销开启、关闭很消耗电脑性能创建开销很小运行关系进程互相独立一条线程出错整个程序会崩溃二、Java 创建线程的 3 种方式大一考试常考 3 种创建写法全部可以直接运行。方式 1继承 Thread 类自定义类继承Thread重写run()方法线程运行的代码全部写在 run 方法里调用start()启动线程。public class TestThread extends Thread { Override public void run() { // 线程要执行的代码 for (int i 0; i 5; i) { System.out.println(Thread.currentThread().getName() i); } } public static void main(String[] args) { TestThread t new TestThread(); t.setName(子线程); t.start(); // 开启新线程 } }易错点直接调用run()只是普通方法调用不会开启新线程只有start()才能创建线程。方式 2实现 Runnable 接口Java 一个类只能继承一个父类如果已经继承了别的类就可以实现 Runnable 接口来创建线程。public class TestRunnable implements Runnable { Override public void run() { for (int i 0; i 5; i) { System.out.println(Thread.currentThread().getName() i); } } public static void main(String[] args) { TestRunnable task new TestRunnable(); Thread t new Thread(task, 子线程); t.start(); } }优点同一个任务可以交给多个线程执行代码耦合度更低。方式 3Callable 实现带返回值的线程Runnable 线程运行结束后拿不到结果Callable 可以在线程执行完成后返回计算结果配合 FutureTask 获取返回值。import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class TestCallable implements CallableInteger { Override public Integer call() throws Exception { // 计算1到100的和 int sum 0; for (int i 1; i 100; i) { sum i; } return sum; } public static void main(String[] args) throws Exception { CallableInteger callable new TestCallable(); FutureTaskInteger futureTask new FutureTask(callable); new Thread(futureTask).start(); // 获取线程运行结果 System.out.println(1~100求和 futureTask.get()); } }三、线程的 6 种状态大一期末必考线程生命周期一共 6 种状态新建 NEWnew 出线程对象还没有调用 start () 方法。就绪 RUNNABLE调用 start () 之后线程排队等待 CPU 分配时间片。运行 RUNNINGCPU 拿到时间片线程开始执行 run 方法。阻塞 BLOCKED争抢锁失败线程卡在门外等待锁释放。等待 WAITING调用无参 wait ()、join ()线程一直等待必须由别的线程唤醒。超时等待 TIMED_WAITING调用sleep(1000)等待 1 秒之后自动恢复就绪状态。终止 TERMINATEDrun 方法运行结束线程彻底结束。四、常用线程方法sleep(毫秒)让当前线程休眠休眠期间不会释放锁。join()等待子线程运行完毕主线程再继续往下走。setDaemon(true)设置守护线程主线程结束守护线程自动关闭。currentThread()获取当前正在运行的线程对象。五、线程安全问题期末考试高频大题1. 为什么会出现线程安全问题多个线程同时操作同一个共享变量CPU 随时切换线程就会出现数据错乱。 经典售票案例多个窗口售卖车票不加锁会出现一张车票被多次卖出。2. 3 种解决办法方案 1synchronized 同步锁Java 自带的关键字锁有 3 种写法修饰普通方法锁对象为 this修饰静态方法锁对象为类的 class 对象同步代码块自己指定锁对象//同步代码块 Object lock new Object(); synchronized (lock){ //售票业务代码 }方案 2Lock 显式锁JUC 包下的 ReentrantLock手动上锁、手动解锁。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Ticket { private Lock lock new ReentrantLock(); public void sale(){ lock.lock(); try { //售票业务 }finally { lock.unlock(); } } }注意解锁必须写在 finally 里面防止程序报错锁无法释放。方案 3volatile 关键字volatile 可以保证多个线程看见同一个变量最新的值但是不能解决 i 这类加减运算的安全问题只适合简单变量读写。六、线程池入门频繁创建销毁线程会消耗电脑性能线程池可以重复使用线程大一只需要掌握基础概念即可。1. 核心参数corePoolSize核心线程数量核心线程不会被回收maximumPoolSize线程池最大线程数keepAliveTime多余线程空闲之后的存活时间workQueue等待队列任务排队执行拒绝策略任务满了之后的处理方案2. JDK 自带 4 种线程池newFixedThreadPool固定线程数量newSingleThreadExecutor只有一条线程newCachedThreadPool线程数量自动扩容newScheduledThreadPool定时执行任务
Java 多线程保姆级教程:线程创建、状态流转、锁机制、线程池,一篇讲透
前言多线程是大一 Java 课程的重点内容也是期末考试、计算机二级的高频考点。很多同学刚学多线程时分不清进程和线程写售票程序时经常出现车票超卖不知道怎么解决线程安全问题。本篇文章全部使用大一能看懂的基础语法搭配完整可运行代码把线程的全部基础知识点讲清楚。一、进程和线程的区别1. 基础概念进程电脑上运行的软件就是一个进程。比如你打开 QQ、浏览器每一个软件都会单独占用一块内存进程之间互不干扰。进程是操作系统分配电脑内存资源的最小单位。线程一个进程里面可以同时运行多条线程。比如音乐软件一边播放歌曲一边下载歌词播放音乐、下载歌词就是两条独立的线程。线程是 CPU 执行任务的最小单位。2. 通俗例子把进程比作一家工厂工厂有厂房、原材料线程就是工厂里的工人多名工人共用工厂的厂房和物料各自干自己的活。3. 简单对比对比项进程线程资源有独立内存空间共用进程的内存开销开启、关闭很消耗电脑性能创建开销很小运行关系进程互相独立一条线程出错整个程序会崩溃二、Java 创建线程的 3 种方式大一考试常考 3 种创建写法全部可以直接运行。方式 1继承 Thread 类自定义类继承Thread重写run()方法线程运行的代码全部写在 run 方法里调用start()启动线程。public class TestThread extends Thread { Override public void run() { // 线程要执行的代码 for (int i 0; i 5; i) { System.out.println(Thread.currentThread().getName() i); } } public static void main(String[] args) { TestThread t new TestThread(); t.setName(子线程); t.start(); // 开启新线程 } }易错点直接调用run()只是普通方法调用不会开启新线程只有start()才能创建线程。方式 2实现 Runnable 接口Java 一个类只能继承一个父类如果已经继承了别的类就可以实现 Runnable 接口来创建线程。public class TestRunnable implements Runnable { Override public void run() { for (int i 0; i 5; i) { System.out.println(Thread.currentThread().getName() i); } } public static void main(String[] args) { TestRunnable task new TestRunnable(); Thread t new Thread(task, 子线程); t.start(); } }优点同一个任务可以交给多个线程执行代码耦合度更低。方式 3Callable 实现带返回值的线程Runnable 线程运行结束后拿不到结果Callable 可以在线程执行完成后返回计算结果配合 FutureTask 获取返回值。import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class TestCallable implements CallableInteger { Override public Integer call() throws Exception { // 计算1到100的和 int sum 0; for (int i 1; i 100; i) { sum i; } return sum; } public static void main(String[] args) throws Exception { CallableInteger callable new TestCallable(); FutureTaskInteger futureTask new FutureTask(callable); new Thread(futureTask).start(); // 获取线程运行结果 System.out.println(1~100求和 futureTask.get()); } }三、线程的 6 种状态大一期末必考线程生命周期一共 6 种状态新建 NEWnew 出线程对象还没有调用 start () 方法。就绪 RUNNABLE调用 start () 之后线程排队等待 CPU 分配时间片。运行 RUNNINGCPU 拿到时间片线程开始执行 run 方法。阻塞 BLOCKED争抢锁失败线程卡在门外等待锁释放。等待 WAITING调用无参 wait ()、join ()线程一直等待必须由别的线程唤醒。超时等待 TIMED_WAITING调用sleep(1000)等待 1 秒之后自动恢复就绪状态。终止 TERMINATEDrun 方法运行结束线程彻底结束。四、常用线程方法sleep(毫秒)让当前线程休眠休眠期间不会释放锁。join()等待子线程运行完毕主线程再继续往下走。setDaemon(true)设置守护线程主线程结束守护线程自动关闭。currentThread()获取当前正在运行的线程对象。五、线程安全问题期末考试高频大题1. 为什么会出现线程安全问题多个线程同时操作同一个共享变量CPU 随时切换线程就会出现数据错乱。 经典售票案例多个窗口售卖车票不加锁会出现一张车票被多次卖出。2. 3 种解决办法方案 1synchronized 同步锁Java 自带的关键字锁有 3 种写法修饰普通方法锁对象为 this修饰静态方法锁对象为类的 class 对象同步代码块自己指定锁对象//同步代码块 Object lock new Object(); synchronized (lock){ //售票业务代码 }方案 2Lock 显式锁JUC 包下的 ReentrantLock手动上锁、手动解锁。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Ticket { private Lock lock new ReentrantLock(); public void sale(){ lock.lock(); try { //售票业务 }finally { lock.unlock(); } } }注意解锁必须写在 finally 里面防止程序报错锁无法释放。方案 3volatile 关键字volatile 可以保证多个线程看见同一个变量最新的值但是不能解决 i 这类加减运算的安全问题只适合简单变量读写。六、线程池入门频繁创建销毁线程会消耗电脑性能线程池可以重复使用线程大一只需要掌握基础概念即可。1. 核心参数corePoolSize核心线程数量核心线程不会被回收maximumPoolSize线程池最大线程数keepAliveTime多余线程空闲之后的存活时间workQueue等待队列任务排队执行拒绝策略任务满了之后的处理方案2. JDK 自带 4 种线程池newFixedThreadPool固定线程数量newSingleThreadExecutor只有一条线程newCachedThreadPool线程数量自动扩容newScheduledThreadPool定时执行任务