回顾多线程
一、线程简介
- 多任务:同一时间有多个任务要去做
- 多线程:原来只有一条路,慢慢的因为车太多了,道路阻塞,效率极低,为了提高使用的效率,能够充分利用道路,于是加了多个车道。从此,麻麻再也不用担心道路阻塞了!
- 普通方法调用和多线程
进程与线程
线程就是独立的执行路径
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc(垃圾回收)线程
main()称之为主线程,为系统的入口,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如cpu调度时间,并发控制开销(让线程排队执行)
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程实现
线程创建
线程的创建有三种方式:
- 继承Thread
- 实现Runnable
- 实现Callable
main函数实自己写的,叫用户线程
gc线程是JVM给的,叫守护线程
继承Thread类(重点)
不建议使用:为了避免OOP单继承局限性
Thread类本身实现了Runnadle接口
- 自定义线程类继承Thread类
- 重写run方法,编写程序执行体
- 创建线程对象,调用start()方法启动线程
总结:线程开启不一定立即执行,由CPU调度执行,直接调用run方法相当于调用普通方法,不会创建新的线程
public class TestThread1 extends Thread {//继承Thread类 public static void main(String[] args) { TestThread1 thread1=new TestThread1(); // thread1.run();//先执行run方法,再执行接下来的代码(没起到多线程的作用) thread1.start();//主线程和子线程交替执行,且不可控,每次执行结果都不一样 for (int i = 0; i < 20; i++) { System.out.println("主线程"+i); } } @Override public void run() { // super.run(); for (int i = 0; i < 20; i++) { System.out.println("子线程"+i); } } }
案例多线程下载图片
需要用到commons-io2.6这个jar包,因为我们需要用到commons-io包里面的FileUtils函数
然后把包拷贝到我们的工程下面,右键 Add as library
package MultiThread; import org.apache.commons.io.FileUtils; import sun.misc.FileURLMapper; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; //实现多线程同步下载网图 public class TestThread2 extends Thread{ private String url;//网络图片地址 private String name;//保存的文件名 @Override public void run() { WebDownLoader webDownLoader=new WebDownLoader(); webDownLoader.downloader(url,name); System.out.println("下载了文件名为"+name); } public TestThread2(String url, String name) { this.url = url; this.name = name; } public static void main(String[] args) { TestThread2 t1=new TestThread2("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&hs=2&pn=2&spn=0&di=68640&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=3788223672%2C2933772794&os=3469738239%2C2819470525&simid=0%2C0&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimage.biaobaiju.com%2Fuploads%2F20191105%2F15%2F1572937434-vjDJNuBAxl.jpg%26refer%3Dhttp%3A%2F%2Fimage.biaobaiju.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1613979841%26t%3D6b74022d98f53152b4e7143934ed2714&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bktw5kwt37_z%26e3Bv54AzdH3Fi5g2geiwtztAzdH3Fl08nc_z%26e3Bip4s&gsm=3&islist=&querylist=","dog.jpeg"); TestThread2 t2=new TestThread2("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&hs=2&pn=5&spn=0&di=26840&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=2057173425%2C3288346039&os=3534193242%2C1744020036&simid=4255926711%2C726475710&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimg.wohaoyun.com%2Fimg_600%2FM00%2F07%2FB8%2FwKjg2lvW-dGAERdjAAEeYGzmpUA906.jpg%26refer%3Dhttp%3A%2F%2Fimg.wohaoyun.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1613979935%26t%3Dbd8042b4b6488deae4c73854e0c4dd6e&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bo5iw5y7g_z%26e3Bv54AzdH3Ff3AzdH3F15g2o7ktzitAzdH3Fda8bAzdH3F88AzdH3F8988_z%26e3Bip4s&gsm=6&islist=&querylist=","dog2.jpeg"); TestThread2 t3=new TestThread2("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&hs=2&pn=4&spn=0&di=13750&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=2136279165%2C2845293579&os=1684394320%2C374773828&simid=3423840844%2C312231934&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fwww.cpnic.com%2FUploadFiles%2Fimg_0_3308088708_3867205912_26.jpg%26refer%3Dhttp%3A%2F%2Fwww.cpnic.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1613980066%26t%3D73944a43d2a1181ed0e4aeb6cd59da1e&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bvrgtv_z%26e3Bv54AzdH3FrtvAzdH3F%25Ec%25Ba%25bF%25Ec%25lE%25bB%25E0%25bB%25l0%25Ec%25ln%25b8%25E0%25A0%25bD%25Ec%25A9%25A0%25Ec%25bc%25Ab%25Ec%25bF%25bA%25Ec%25lB%25BE%25E0%25bl%25b0%25Ec%25bF%25bA%25E9%25BB%25B0%25Em%25Aa%25BCAzdH3F&gsm=5&islist=&querylist=","dog3.jpeg"); t1.start(); t2.start(); t3.start(); } } //下载器 class WebDownLoader{ //下载方法 public void downloader(String url,String name) { try { FileUtils.copyURLToFile(new URL(url),new File(name));//copyURLToFile,把url变成文件 } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现问题"); } } }
实现Runnable接口(重点)
Java是单继承,推荐使用Runnable接口,方便同一个独享被多个线程使用
避免了单继承的局限性:即在Java中一个类只能使用extends继承一个父类,如果继承多个父类,而父类有同名方法时就不知道调用哪一个方法了,另外回事两个类的耦合性增加,如果父类有改动时会直接影响子类。
- 定义MyRunnable类实现Runnable接口
- 实现Run()方法,编写程序执行体
- 创建线程对象,调用start()方法启动线程
package MultiThread; public class TestThread3 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("子线程"+i); } } public static void main(String[] args) { //创建Runnable接口实现类的对象 TestThread3 testThread3 = new TestThread3(); //创建线程类对象,通过线程对象开启我们的线程(代理) // Thread thread=new Thread(testThread3); // thread.start(); new Thread(testThread3).start(); for (int i = 0; i < 20; i++) { System.out.println("主线程"+i); } } }
初始并发问题
多个线程操作同一个资源的情况下出现不同的线程抢到同一张票,线程不安全,数据会紊乱
package MultiThread; //多个线程操作同一个对象 //买火车票 //多个线程操作同一个资源的情况下出现不同的线程抢到同一张票,线程不安全,数据紊乱 public class TestTheread4 implements Runnable{ int ticketNums=100;//票数 public static void main(String[] args) { TestTheread4 testTheread4 = new TestTheread4(); new Thread(testTheread4,"1").start();//1,2,3是线程名字 new Thread(testTheread4,"2").start(); new Thread(testTheread4,"3").start(); } @Override public void run() { while (true) { if(ticketNums<=0) { break; } try { Thread.sleep(200);//模拟延时 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"张票"); } } }
龟兔赛跑
/* 1.首先来个赛道距离,然后要离终点越来越近 2.判断比赛是否结束 3.打印出胜利者 4.龟兔赛跑开始 5.故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉 6.终于,乌龟赢得比赛 **/ public class Race implements Runnable{ private static String winner;//用static,保证只有一个胜利者 public static void main(String[] args) { Race Rubbit=new Race(); Race tortise=new Race(); new Thread(Rubbit,"兔子").start(); new Thread(tortise,"乌龟").start(); } @Override public void run() { for (int i = 0; i <= 1000; i++) { //模拟兔子休息,每10步休息一下 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+"步"); } } //判断是否完成比赛 private boolean gameOver(int steps) { if(winner!=null) { return true; } { if(steps>=1000) { winner= Thread.currentThread().getName(); System.out.println("winneer is"+winner); return true; } } return false; } }
线程状态
五大状态
创建状态:new
就绪状态:start
阻塞状态:sleep(sleep只是其中一个)
死亡状态:正常执行完
停止线程
不推荐使用jdk提供的stop(),destory()方法,建议使用一个标志位进行终止变量,当flag=false,则线程终止运行
public class Teststop implements Runnable { //1.线程中定义线程体使用的标识 private boolean flag = true; @Override public void run (){ / /2 .线程体使用该标识 while (flag) { systepaoit.println ( "run. . . Thread" ); } } //3.对外提供方法改变标识 public void stop(){ this.flag = false; } }
public class TestStop implements Runnable { private boolean flag=true; @Override public void run() { int i=0; while (flag){ System.out.println("run.....Thread"+(i++)); } } public void stop(){ this.flag=false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i <1000 ; i++) { System.out.println("main"+i); if (i==900){ testStop.stop(); System.out.println("run线程停止了!"); } } } }
线程休眠(sleep)
sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时(放大问题的发生性,比如多线程卖票,一票多卖),倒计时等。
每一个对象都有一个锁,sleep不会释放锁。
package MultiThread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.SimpleFormatter; //模拟倒计时 public class TestSleep implements Runnable { public static void main(String[] args) throws InterruptedException { tenDown(); //打印当前系统时间 Date startTime=new Date(System.currentTimeMillis()); while(true) { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime=new Date(System.currentTimeMillis()); } } public static void tenDown() throws InterruptedException {//模拟倒计时 int num=10; while(true) { Thread.sleep(1000); System.out.println(num--); if(num<=0) { break; } } } @Override public void run() { } }
线程礼让(yield)
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功
package MultiThread; public class TestYield { public static void main(String[] args) { MyYield myYield=new MyYield(); new Thread(myYield,"a").start(); new Thread(myYield,"b").start(); } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName()+"线程结束执行"); } }
线程同步
多个线程操作同一个对象的安全问题(买火车票)
现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭﹐每个人都想吃饭﹐最天然的解决办法就是﹐排队﹒一个个来.
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步,线程同步其实就是一种等待机制。多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
一个线程持有锁会导致其它所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
线程同步形成条件:队列+锁
三大不安全案例
①买火车票
线程不安全
package MultiThread.syn; //不安全的买票 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket,"1").start(); new Thread(buyTicket,"2").start(); new Thread(buyTicket,"3").start(); } } class BuyTicket implements Runnable{ private int tickNums=10; boolean flag=true;//标志位,用于线程的外部停止方式 @Override public void run() { //买票 while(flag) { buy(); } } private void buy() { //判断是否有票 if (tickNums <= 0) { flag=false; return; } //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //买票 System.out.println(Thread.currentThread().getName()+"拿到"+tickNums--); } }
线程安全
//synchronized同步方法,锁的是this private synchronized void buy() throws InterruptedException { //判断是否有票 if (tickNums <= 0) { flag=false; return; } //模拟延时 // Thread.sleep(10); //买票 System.out.println(Thread.currentThread().getName()+"拿到"+tickNums--); }
② 银行取钱
package MultiThread.syn; //不安全取钱 public class UnsafeBank { public static void main(String[] args) { //取钱首先得有账户 Account account=new Account(100,"结婚基金"); Drawing you=new Drawing(account,50,"你"); Drawing girl=new Drawing(account,100,"girlFriend"); you.start(); girl.start(); } } class Account{//账户 int money;//余额 String name;//卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行:模拟取款 class Drawing extends Thread{ Account account;//账户 int drawingMoney;//取多少钱 int nMoney;//现在有多少钱 public Drawing(Account account, int drawingMoney, String name) { super(name);//调用父类的方法 this.account = account; this.drawingMoney = drawingMoney; // this.nMoney = nMoney; } @Override public void run() { //super.run(); //判断有没有钱 if(account.money-drawingMoney<0) { System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额 account.money=account.money-drawingMoney; //现在手里的钱 nMoney=nMoney+drawingMoney; System.out.println(account.name+"余额为:"+account.money); //this就是调用当前方法的对象,Drawing继承了Thread,所以this也是一个线程对象,可以调用Thread的getName方法来获取线程的名字 //Thread.currentThread().getName()=this.getName() System.out.println(this.getName()+"手里的钱"+nMoney); } }
线程安全
@Override public void run() { //super.run(); //判断有没有钱 synchronized(account) { if(account.money-drawingMoney<0) { System.out.println(Thread.currentThread().getName()+"钱不够,取不了"); return; } //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额 account.money=account.money-drawingMoney; //现在手里的钱 nMoney=nMoney+drawingMoney; System.out.println(account.name+"余额为:"+account.money); //this就是调用当前方法的对象,Drawing继承了Thread,所以this也是一个线程对象,可以调用Thread的getName方法来获取线程的名字 //Thread.currentThread().getName()=this.getName() System.out.println(this.getName()+"手里的钱"+nMoney); } }
锁的对象就是多个线程共享的对象,如果每个线程的锁对象都不一样synchronized就没有用,锁谁取决于共同操作的对象是谁(且这个对象是要改变的)
③线程不安全的集合
import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(100);//休息一会再打印,电脑运行速度太快了。线程里add还没执行完就执行主线程的打印语句。会导致打印结果偏小,但最后实际结果其实还是10000 System.out.println(list.size()); } }
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问﹐所以我们只需要针对方法提出一套机制。这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块.
同步方法:public synchronized void method(int args)f
synchronized方法控制对“对象”的访问,每个对象对应一把锁﹐每个synchronized方法都必须获得调用该方法的对象的锁才能执行﹐否则线程会阻塞,方法一旦执行﹐就独占该锁,直到该方法返回才释放锁﹐后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
只读代码是不需要加锁的,只有需要进行修改的代码才需要加锁(所以产生了同步方法和同步代码块)
方法里面需要修改的内容才需要锁,锁得太多,浪费资源
同步块
synchronized(Obj){}
Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
注意监视的对象是需要增删改查的对象
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,皆出同步监视器
第二个线程访问,发现同步监视器没有锁
线程安全的集合:CopyOnWriteArrayList
我没有用lambda表达式创建线程,而是实现Runnable接口,结果证明这是可以的,且线程名字也存进了list
package MultiThread.syn; import java.util.concurrent.CopyOnWriteArrayList; public class testJuuc implements Runnable { static CopyOnWriteArrayList<String>list=new CopyOnWriteArrayList<String>(); public static void main(String[] args) throws InterruptedException { testJuuc thread=new testJuuc(); for (int i = 0; i < 10; i++) { new Thread( thread).start(); } Thread.sleep(3000); System.out.println(list.size()); System.out.println(list.toString()); } @Override public void run() { list.add(Thread.currentThread().getName()); } }
posted on 2021-06-10 22:01 JavaCoderPan 阅读(16) 评论(0) 编辑 收藏 举报 来源
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南