Java多线程入门上从零认识线程三种创建方式与常用方法想象一下每年春节你和几百万人在12306上抢票。如果网站只有一个工作人员单线程在处理所有人的请求那画面太美不敢看——你得排到元宵节才能买到票。而多线程就像是同时开了几十个售票窗口大家各抢各的效率直接起飞。这就是多线程的魅力。一、什么是多线程它到底有什么好处通俗地说线程是程序里的一条执行路径。你平时写的代码默认都是在main方法里从上往下执行的这就是单线程。而多线程就是在一个程序里同时跑多条执行路径它们互不干扰或者说看起来互不干扰共同完成复杂的任务。为什么要用多线程提高程序效率把大任务拆成小任务多个线程并行执行减少等待时间提升用户体验比如下载文件时一个线程负责下载一个线程负责更新进度条界面不会卡死充分利用CPU资源现代电脑都是多核CPU单线程只能用一个核多线程才能把所有核心都跑起来。生活例子你去银行办业务。单线程 只有一个柜台开放所有人排队多线程 同时开放多个柜台大家并行办理。12306 抢票就是最经典的例子——如果只有一个线程处理全国人民的请求服务器早炸了。二、创建线程的三种方式Java 中创建线程主要有三种方式我们由浅入深逐个讲解。注意启动线程必须使用start()方法而不是直接调用run()。方式一继承 Thread 类这是最简单粗暴的方式写一个类继承Thread重写run()方法把你要做的事写进去。packageduoxiancheng;/** * 继承 Thread 类创建线程 */classMyThreadextendsThread{Overridepublicvoidrun(){for(inti0;i5;i){System.out.println(子线程执行第 i 次);}}}publicclassThreadDemo1{publicstaticvoidmain(String[]args){// 创建线程对象MyThreadtnewMyThread();// 启动线程注意调用 start()不是 run()t.start();// 主线程的任务for(inti0;i5;i){System.out.println(主线程执行第 i 次);}}}运行结果每次可能不一样因为线程是并发执行的⚠️ 重点start() 和 run() 的区别start()告诉 JVM 开启一个新线程新线程会单独执行run()里的代码这才是真正的多线程。run()如果直接调用run()那就跟调用普通方法一样代码还是在当前线程里顺序执行没有开启新线程。// ❌ 错误示范直接调用 run()相当于还是单线程执行t.run();// ✅ 正确示范调用 start()JVM 会开启新线程执行 run() 里的逻辑t.start();❌ 这种方式的缺点Java 是单继承语言一旦你extends Thread就不能再继承其他类了。这在实际开发中非常不灵活所以这种方式不推荐。方式二实现 Runnable 接口既然继承不灵活那就用实现接口的方式。写一个类实现Runnable接口重写run()方法然后把对象传给Thread的构造器这里有一个容易忽视的点就是Thread类实现了Runnable接口packageduoxiancheng;/** * 实现 Runnable 接口创建线程 */classMyRunnableimplementsRunnable{Overridepublicvoidrun(){for(inti0;i5;i){System.out.println(子线程执行第 i 次);}}}publicclassThreadDemo2{publicstaticvoidmain(String[]args){// 1. 创建 Runnable 任务对象MyRunnabletasknewMyRunnable();// 2. 把任务交给 Thread 线程对象ThreadtnewThread(task);// 3. 启动线程t.start();// 主线程的任务for(inti0;i5;i){System.out.println(主线程执行第 i 次);}}}进阶写法匿名内部类如果你不想专门写一个类可以用匿名内部类简化packageduoxiancheng;publicclassThreadDemo3{publicstaticvoidmain(String[]args){// 使用匿名内部类创建 Runnable 对象ThreadtnewThread(newRunnable(){Overridepublicvoidrun(){for(inti0;i5;i){System.out.println(子线程执行第 i 次);}}});t.start();// 主线程的任务for(inti0;i5;i){System.out.println(主线程执行第 i 次);}}}关键理解Runnable 本质上就是一个任务描述书。Thread 才是真正的工人。你把任务书交给工人工人按照任务书去干活。这种方式解耦了任务和执行者比继承 Thread 灵活得多。❌ 以上两种方法的共同缺点Thread和Runnable的run()方法都没有返回值也不能抛出受检异常。如果你希望线程执行完后返回一个结果这两种方式都做不到。方式三实现 Callable 接口直接创建线程时功能最强Callable 是 JDK 5 引入的可以说是直接创建线程时功能最强的选择。它的call()方法既能返回值也能抛出异常。而且后文我们要讲的线程池接收的主要就是Callable和Runnable类型的任务。不过Callable 不能直接与 Thread 挂钩需要通过FutureTask进行中转。packageduoxiancheng;importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.FutureTask;/** * 实现 Callable 接口创建线程 */classMyCallableimplementsCallableString{OverridepublicStringcall()throwsException{intsum0;for(inti1;i100;i){sumi;}return计算完毕1到100的和是sum;}}publicclassThreadDemo4{publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{// 1. 创建 Callable 任务对象MyCallablecallablenewMyCallable();// 2. 把 Callable 封装成 FutureTaskFutureTask 本质上就是一个 RunnableFutureTaskStringfutureTasknewFutureTask(callable);// 3. 把 FutureTask 交给 ThreadThreadtnewThread(futureTask);// 4. 启动线程t.start();// 5. 获取线程执行完毕后的结果如果线程还没执行完这里会阻塞等待StringresultfutureTask.get();System.out.println(result);}}为什么需要 FutureTaskFutureTask本质上是一个Runnable 线程任务对象所以它可以交给Thread去执行。同时它又包装了Callable因此可以通过get()方法获取线程执行完毕后的返回值。你可以把它理解成一个双料间谍对外表现成 Runnable能交给 Thread对内封装了 Callable能拿到返回值。⚠️注意futureTask.get()是一个阻塞方法。如果子线程还没执行完主线程会在这一句一直等直到子线程返回结果。 先别急着兴奋Callable 确实比前两种方式强大但正如后文线程池部分会详细讲到的在实际项目开发中我们很少直接new Thread()而是把 Callable 或 Runnable 任务提交给线程池去执行。现在先掌握它的用法即可。三、线程的六大状态生命周期线程从创建到死亡会经历不同的状态。理解这些状态能帮你更好地排查线程卡死、线程不执行等问题。状态说明NEW新建状态。线程刚被创建还没调用start()。RUNNABLE可运行状态。调用了start()线程正在运行或等待CPU调度。BLOCKED阻塞状态。线程试图获取一个锁但锁被别的线程占用了。WAITING无限等待状态。线程调用了wait()或join()且无超时需要被唤醒。TIMED_WAITING限时等待状态。线程调用了sleep(时间)或join(时间)时间到自动恢复。TERMINATED终止状态。线程执行完毕或异常退出。packageduoxiancheng;publicclassThreadStateDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadtnewThread(()-{try{Thread.sleep(100);// 进入 TIMED_WAITING}catch(InterruptedExceptione){e.printStackTrace();}});System.out.println(新建后t.getState());// NEWt.start();System.out.println(启动后t.getState());// RUNNABLE或 TIMED_WAITINGThread.sleep(10);// 让主线程稍等确保子线程进入 sleepSystem.out.println(子线程睡眠中t.getState());// TIMED_WAITINGt.join();// 等待子线程执行完毕System.out.println(子线程结束后t.getState());// TERMINATED}}重要结论线程进入BLOCKED、WAITING、TIMED_WAITING状态时都会让出 CPU不会一直占着资源。四、线程的常用方法与构造器常用方法方法说明run()线程要执行的逻辑直接调用不会开启新线程。start()启动线程让 JVM 调度执行run()。getName()获取线程名字默认是Thread-0、Thread-1这种格式。setName(String name)给线程起个自定义名字方便排查问题。Thread.currentThread()获取当前正在执行的线程对象。sleep(long millis)让当前线程休眠指定毫秒进入TIMED_WAITING。join()让当前线程等待调用者线程执行完毕再继续执行。常见构造器// 1. 给线程起个名字Threadt1newThread(下载线程-A);// 2. 传入 Runnable 任务Threadt2newThread(newMyRunnable());// 3. 传入 Runnable 任务同时起名字最常用Threadt3newThread(newMyRunnable(),下载线程-B);代码演示packageduoxiancheng;publicclassThreadMethodDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadtnewThread(()-{System.out.println(子线程名字Thread.currentThread().getName());try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(子线程执行完毕);},我的工作线程);t.start();// 主线程等待子线程执行完毕t.join();System.out.println(主线程继续执行);}}写在最后作为初学者你只需要记住这三条核心结论创建线程有三种方式继承 Thread不推荐、实现 Runnable无返回值、实现 Callable有返回值直接创建时功能最强启动线程一定用start()直接调用run()就是普通方法线程有六大状态其中BLOCKED、WAITING、TIMED_WAITING都会让出 CPU。当然光是创建线程还不够。当多个线程同时操作同一份数据时就会引出线程安全问题——这也是中篇要重点讲的线程同步与锁机制。