多线程
多线程
线程的三种创建方式
-
Thread class:继承Thread类
-
自定义线程类继承Threadlei类
-
重写run()方法,编写线程执行体
-
创建线程对象,调用start()启动线程
package com.thread; //创建线程方式1:继承Thread类,重写run方法,调用start方法开启线程 public class ThreadTest01 extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("i am watch code:" + i); } } public static void main(String[] args) { //main线程 //创建一个线程对象 ThreadTest01 threadTest01 = new ThreadTest01(); //调用start()方法开启线程 //start()方法是主线程和子线程一起执行 //run()方法是先执行run()方法的子线程,再执行主线程 //注意:线程开启不一定立即执行,需要等CPU的调度处理 threadTest01.start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程:" + i); } } }
代码实现多线程下载图片
package com.thread; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; //利用thread实现多线程同步下载图片 public class ThreadTest02 extends Thread { private String url; private String name; public ThreadTest02(String url, String name) { this.url = url; this.name = name; } //下载图片线程的执行体 @Override public void run() { webDownloader webDownloader = new webDownloader(); webDownloader.downloader(url, name); System.out.println("下载了文件名为:" + name); } public static void main(String[] args) { ThreadTest02 test01 = new ThreadTest02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190513%2F7cb9afc55171450eaa17604c5e4fc765.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=030319dc87f0a7ce129ba69ab5bda5e5", "1.jpg"); ThreadTest02 test02 = new ThreadTest02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.youxi369.com%2Farticle%2Fcontents%2F2021%2F01%2F07%2F2021010740735362.jpg&refer=http%3A%2F%2Fimg.youxi369.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=97f6180af2ab121b58f15c7f300a0ebb", "2.jpg"); ThreadTest02 test03 = new ThreadTest02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201802%2F12%2F20180212094815_MWrv5.jpeg&refer=http%3A%2F%2Fb-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=316c717e8b3e03682f56422dfa92fba0", "3.jpg"); test01.start(); test02.start(); test03.start(); } } //下载器 class webDownloader { public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现异常"); } } } 结果:
-
下载成功,注意:要先导入commons-IO包 验证可得:start()方法没有按程序顺序执行,先后顺序看CPU调度的执行情况
-
Runnable接口 :实现Runnable接口
package com.thread; //创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,调用start()方法 public class ThreadTest03 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("i am watch code:" + i); } } public static void main(String[] args) { //创建Runnable接口实现类对象 ThreadTest03 threadTest03 = new ThreadTest03(); //创建线程对象,通过线程对象来开启我们的线程 代理 new Thread(threadTest03).start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习多线程:" + i); } } } -
Callable接口:实现Callable接口
package com.thread; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; //创建线程方式3:实现callable接口 /** * callable的好处: * 1、可以定义返回值 * 2、可以抛出异常 */ public class CallableTest01 implements Callable<Boolean> { private String url; private String name; public CallableTest01(String url, String name) { this.url = url; this.name = name; } //下载图片线程的执行体 @Override public Boolean call() { webDownloader01 webDownloader01 = new webDownloader01(); webDownloader01.downloader(url, name); System.out.println("下载了文件名为:" + name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { CallableTest01 t1 = new CallableTest01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190513%2F7cb9afc55171450eaa17604c5e4fc765.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=030319dc87f0a7ce129ba69ab5bda5e5", "1.jpg"); CallableTest01 t2 = new CallableTest01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.youxi369.com%2Farticle%2Fcontents%2F2021%2F01%2F07%2F2021010740735362.jpg&refer=http%3A%2F%2Fimg.youxi369.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=97f6180af2ab121b58f15c7f300a0ebb", "2.jpg"); CallableTest01 t3 = new CallableTest01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201802%2F12%2F20180212094815_MWrv5.jpeg&refer=http%3A%2F%2Fb-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=316c717e8b3e03682f56422dfa92fba0", "3.jpg"); //创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); //获取结果 boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); //关闭服务 ser.shutdownNow(); } } //下载器 class webDownloader01 { public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现异常"); } } }
并发问题
买火车票模拟代码:
package com.thread; import java.nio.channels.NonReadableChannelException; //多个线程操作同一个对象 //买火车票的例子 public class TreadTest04 implements Runnable { //火车票 private int ticketNum = 10; @Override public void run() { while (true) { if (ticketNum <= 0) { break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->拿到了第 " + ticketNum-- + " 张票"); } } public static void main(String[] args) { TreadTest04 treadTest04 = new TreadTest04(); new Thread(treadTest04, "小明").start(); new Thread(treadTest04, "老师").start(); new Thread(treadTest04, "黄牛").start(); } }
运行结果:
问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
案例:龟兔赛跑
-
首先来个赛道距离,然后要离终点越来越近
-
判断比赛是否结束
-
打印出胜利者
-
龟兔赛跑开始
-
要保证乌龟胜利,兔子需要睡觉,用sleep来模拟 兔子睡觉
-
乌龟赢得比赛
package com.thread; public class Race implements Runnable { //胜利者 private static String winner; @Override public void run() { for (int i = 1; i <= 100; i++) { //模拟兔子睡觉 if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } //判断比赛是否结束 boolean flag = gameOver(i); if (flag) { break; } System.out.println(Thread.currentThread().getName() + "--> 跑了 " + i + " 步"); } } //判断比赛是否结束 public boolean gameOver(int steps) { //判断是否存在胜利者 if (winner != null) {//胜利者已经存在 return true; } { if (steps == 100) { winner = Thread.currentThread().getName(); System.out.println("winner is " + winner); return true; } } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race, "兔子").start(); new Thread(race, "乌龟").start(); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现