多任务、进程、线程、多线程
多任务:相当于大脑同时做多件事情
多线程:相当于一条路划分几条道供多辆车走,提高效率
普通方法调用和多线程图示:
一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等
程序是指令和数据的有序集合,静态概念,进程则是执行程序的一次执行过程,动态概念,一个进程可以包含若干个线程(至少有一个线程),线程是CPU调度和执行的单位,真正的多线程是有多个CPU,如果是模拟出来的多线程则只有一个CPU切换很快,有同时执行的错觉
本章核心概念:
三种方式:
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
示例:
package com.thread.demo01; //创建线程方式一:继承Thread类,重写run方法,调用start开启线程 public class TestThread extends Thread{ @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码..." + i); } } public static void main(String[] args) { //main线程 //创建一个线程对象 TestThread testThread = new TestThread(); //调用start方法开启线程 testThread.start(); for (int i = 0; i < 1000; i++) { System.out.println("我在学习多线程..." + i); //会交替执行 } } }
百度搜索commos-io的jar包,在项目目录中创建lib目录并将jar包放入lib中
多线程下载图片示例:
package com.thread.demo02; import org.apache.commons.io.FileUtils; import org.xml.sax.ext.Attributes2; import java.io.File; import java.io.IOException; import java.net.URL; //练习Thread,实现多线程同步下载图片 public class TestThread1 extends Thread{ private String url; private String name; public TestThread1(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) { TestThread1 t1 = new TestThread1("https://ss3.bdstatic.com/70cFv8Sh_" + "Q1YnxGkpoWK1HF6hhy/it/u=3754629430,458249399&fm=26&gp=0.jpg","IU1.jpg"); TestThread1 t2 = new TestThread1("https://ss1.bdstatic.com/70cFuXSh_" + "Q1YnxGkpoWK1HF6hhy/it/u=3403073521,3211226630&fm=26&gp=0.jpg","IU2.jpg"); TestThread1 t3 = new TestThread1("https://gimg2.baidu.com/image_sear" + "ch/src=http%3A%2F%2Fnimg.ws.126.net%2F%3Furl%3Dhttp%253A%252F%252Fdingyue.ws.126.net%252F2021%252F0328%252F26c99c68p00qqorn0006qc000g000dkm.png%26thumbnail%3D650x2147483647%26quality%3D80%26type%3Djpg&refer=http%3A%2F%2Fnimg.ws.126.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1620829018&t=9ae0295956a27b269391da693ac796c8","IU3.jpg"); //自己上网找图片下载地址即可 //不按顺序执行 t1.start(); t2.start(); t3.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方法出现异常"); } }
注意,如果出现错误极可能是CPU不够用,可以关闭其他程序重新运行试试
推荐使用Runnable对象,因为Java单继承的局限性!!!
示例:
package com.thread.demo03; import com.thread.demo01.TestThread; public class TestThread2 implements Runnable{ @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码..." + i); } } public static void main(String[] args) { //main线程 //创建一个Runnable接口的实现类对象 TestThread2 testThread2 = new TestThread2(); //创建线程对象,通过线程对象来开启我们的线程,代理 new Thread(testThread2).start(); for (int i = 0; i < 1000; i++) { System.out.println("我在学习多线程..." + i); //会交替执行 } } }
火车站抢票示例:
package com.thread.demo03; /* 多个线程同时操作同一个对象 买火车票例子 多个线程操作同一个资源的情况下,线程不安全,数据紊乱 */ public class TestThread3 implements Runnable{ private int tickets = 10; @Override public void run() { while (true){ if (tickets < 1){ break; } System.out.println(Thread.currentThread().getName() + " 拿到了第" + tickets-- + "张票"); //模拟延时 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { TestThread3 testThread3 = new TestThread3(); new Thread(testThread3,"小明").start(); new Thread(testThread3,"小红").start(); new Thread(testThread3,"小刚").start(); } }
package com.thread.demo04; public class Race implements Runnable{ private static String winner; @Override public void run() { for (int i = 0; i <= 1001; i++) { //判断比赛是否结束 boolean flag = gameOver(i); if (flag){ break; } System.out.println(Thread.currentThread().getName() + " 跑了" + i + "米"); //模拟兔子领先近半成赛程,开始睡觉 if (Thread.currentThread().getName().equals("兔子") && i%900==0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } //模拟乌龟的慢速 if (Thread.currentThread().getName().equals("乌龟")){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } private boolean gameOver(int steps){ if (winner != null){ return true; } else if (steps == 1001){ winner = Thread.currentThread().getName(); System.out.println("赢的是" + winner); return true; } else{ return false; } } public static void main(String[] args) { Race race = new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); } }
package com.thread.demo05; public class StaticProxy { public static void main(String[] args) { You you = new You(); //Thread也是代理 new Thread( ()-> System.out.println("我爱你") ).start(); //使用了Lambda表达式 /* 婚庆公司 = Thread(); You() =要调用的线程的对象,调用的方法以及run()在这里就是Lambda表达式实现的方法 Thread()是代理,和Runnable接口都有run()方法,最后调用的是Thread的start方法,但实际执行的还是Runnable中的run方法中的方法体 */ new WeddingCompany(you).happyMarry(); /* WeddingCompany weddingCompany = new WeddingCompany(you); weddingCompany.happyMarry();*/ } } //结婚 interface Marry{ void happyMarry(); } class You implements Marry{ @Override public void happyMarry() { System.out.println("结婚进行时"); } } //代理 class WeddingCompany implements Marry{ private Marry target; public WeddingCompany(Marry target){ this.target = target; } @Override public void happyMarry() { before(); this.target.happyMarry();; after(); } public void before(){ System.out.println("结婚之前布置会场"); } public void after(){ System.out.println("结婚之后幸福生活"); } }
理解Functional Interface(函数式接口)是学习Java8 Lambda表达式的关键所在
函数式接口的定义:
package com.thread.demo06; public class TestLambda01 { //3. 静态内部类 static class Like2 implements ILike{ @Override public void lambda() { System.out.println("I like lambda1"); } } public static void main(String[] args) { Like iLike = new Like(); iLike.lambda(); Like2 like = new Like2(); like.lambda(); //4. 局部内部类 class Like3 implements ILike{ @Override public void lambda() { System.out.println("I like lambda2"); } } Like3 like1 = new Like3(); like1.lambda(); //5. 匿名内部类,没有类的名称,必须借助接口或者父类 ILike like4 = new ILike() { @Override public void lambda() { System.out.println("I like lambda3"); } }; like4.lambda(); //6. 用lambda简化 ILike like5 = () -> System.out.println("I like lambda4"); like5.lambda(); } } //1. 定义一个函数式接口 interface ILike{ void lambda(); } //2. 实现类 class Like implements ILike{ @Override public void lambda() { System.out.println("I like lambda"); } }
带参数:
package com.thread.demo06; public class TestLambda02 { //静态内部类 static class Love1 implements ILove{ @Override public void love(int a) { System.out.println("I love you --> " + a); } } public static void main(String[] args) { Love love = new Love(); love.love(1); Love1 love1 = new Love1(); love1.love(2); //Lambda表达式 ILove love2 = (int a) -> System.out.println("I love you --> " + a); //简化参数类型 love2 = (a) -> System.out.println("I love you --> " + a); //简化小括号,注意要是多个参数必须加上括号 love2 = a -> System.out.println("I love you --> " + a); love2.love(3); } } interface ILove{ void love(int a); } //实现类 class Love implements ILove{ @Override public void love(int a) { System.out.println("I love you --> " + a); } }
小结:
Lambda表达式只能有一行代码的情况下,如果有多行,那么就用代码块包裹
前提是接口为函数式接口
多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
五大状态:
package com.thread.demo07; /* 测试stop 建议线程正常停止,利用次数,不建议死循环 建议使用标志位,设置一个标志位 不要使用stop或者destroy等过时或者JDK不建议使用的方法 */ public class TestStop implements Runnable{ //1. 设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while (flag){ System.out.println("run...Thread" + i++); } } //2. 设置一个公开的方法停止线程,转换标志位 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){ //调用自己写的stop方法切换标志位,让线程停止 testStop.stop(); System.out.println("线程该停止了"); } } } }
package com.thread.demo07; import sun.nio.cs.StreamEncoder; import java.text.SimpleDateFormat; import java.util.Date; //模拟倒计时 public class TestSleep2 { public static void main(String[] args) throws Exception { //打印当前系统时间 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()); //更新当前时间 } //tenDown(); //倒计时 } public static void tenDown() throws Exception{ int num = 10; while (true) { Thread.sleep(1000); System.out.println(num--); if (num<=0){ break; } } } }
package com.thread.demo07; import com.sun.corba.se.impl.encoding.CDROutputObject; //测试礼让线程 //礼让不一定成功,看CPU心情 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() + "线程停止执行"); } }
线程状态。 线程可以处于以下状态之一:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
一旦进入死亡状态,线程就不能被再次启动
package com.thread.demo08; //观察测试线程的状态 public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("......"); } }); //观察状态 Thread.State state = thread.getState(); System.out.println(state); //观察启动后 thread.start(); //启动线程 state = thread.getState(); System.out.println(state); //Run while (state != Thread.State.TERMINATED){ //只要线程不终止就一直输出状态 Thread.sleep(100); state = thread.getState(); //更新线程状态 System.out.println(state); //输出状态 } } }
线程调度器监控就绪状态的所有线程,按照优先级决定调度哪个线程来执行
线程的优先级用数字表示,范围从1~10
使用getPriority()、setPriority(int xxx)获取优先级
设置优先级要在start前面
优先级低只是先获得调度的概率低,并不是优先级低就不会被先调用,具体要看CPU的安排,性能倒置一般不会发生
package com.thread.demo08; //测试线程优先级 public class TestPriority extends Thread{ public static void main(String[] args) { //主线程的优先级 System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); Thread t6 = new Thread(myPriority); t1.start(); t2.setPriority(1); t2.start(); t3.setPriority(3); t3.start(); t4.setPriority(MAX_PRIORITY); t4.start(); /*t5.setPriority(-1); //超过优先级范围报错 t5.start(); t6.setPriority(11); t6.start();*/ } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); } }
守护(daemon)线程
package com.thread.demo08; //测试守护线程 //上帝守护你 public class TestDaemon { public static void main(String[] args) { God god = new God(); You you = new You(); Thread thread = new Thread(god); thread.setDaemon(true); //默认是false表示是用户线程,正常的线程都是用户线程 thread.start(); new Thread(you).start(); //用户线程启动 } } //上帝 class God implements Runnable{ @Override public void run() { while (true){ System.out.println("上帝保佑你"); } } } //你 class You implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("幸福生活"); } System.out.println("Goodbye!World!"); } }
多个线程操作同一个资源
并发:同一个对象被多个线程同时操作
示例:上万人同时抢100张票
线程同步是一种等待机制,多个需要访问同一个对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
形成条件:队列+锁
不安全线程案例:
package com.thread.demo08; //线程不安全,有负数 public class UnsafeBuyTickets { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"小魏").start(); new Thread(station,"小罗").start(); new Thread(station,"小宇").start(); } } class BuyTicket implements Runnable{ int tickets = 10; boolean flag = true; @Override public void run() { //买票 while(flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void buy() throws InterruptedException { //判断是否有票 if (tickets<=0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "张票"); //票会变成负数 } }
package com.thread.demo08; //不安全的取钱,两个人去银行取钱,账户 public class UnsafeBank { public static void main(String[] args) { //账户 Account account = new Account(100,"结婚基金"); Drawing you = new Drawing(account,50,"you"); Drawing girlFriend = new Drawing(account,100,"girlFriend"); you.start(); girlFriend.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 cash; public Drawing(Account account,int drawingMoney, String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } @Override public void run() { //判断有没有钱 if (account.money - drawingMoney <= 0){ System.out.println(Thread.currentThread().getName() + "取不了钱(钱不够了)"); return; } //sleep可以方法问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //取钱 account.money = account.money - drawingMoney; //手里的钱 cash = cash + drawingMoney; System.out.println(account.name + "余额为:" + account.money); //this.getName() = Thread.currentThread().getName() System.out.println(this.getName() + "手里的钱" + cash); //余额会变成负数 } }
package com.thread.demo08; import java.util.ArrayList; import java.util.List; //线程不安全 public class UnsafeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread( ()-> list.add(Thread.currentThread().getName()) ).start(); } Thread.sleep(5000); System.out.println(list.size()); //没有10000个元素 } }
方法里面需要修改的内容才需要锁,锁的太多,浪费资源
安全抢票:
package com.thread.demo08; //线程安全 public class UnsafeBuyTickets { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"小魏").start(); new Thread(station,"小罗").start(); new Thread(station,"小宇").start(); } } class BuyTicket implements Runnable{ int tickets = 10; boolean flag = true; @Override public void run() { //买票 while(flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } //synchronized 同步方法,锁的是this private synchronized void buy() throws InterruptedException { //判断是否有票 if (tickets<=0){ flag = false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- + "张票"); //票会变成负数 } }
同步块:
安全取钱:
package com.thread.demo08; public class UnsafeBank { public static void main(String[] args) { //账户 Account account = new Account(1000,"结婚基金"); Drawing you = new Drawing(account,50,"you"); Drawing girlFriend = new Drawing(account,100,"girlFriend"); you.start(); girlFriend.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 cash; public Drawing(Account account,int drawingMoney, String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } @Override public void run() { //同步块,锁住共享资源 synchronized (account){ //判断有没有钱 if (account.money - drawingMoney <= 0){ System.out.println(Thread.currentThread().getName() + "取不了钱(钱不够了)"); return; } //sleep可以方法问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //取钱 account.money = account.money - drawingMoney; //手里的钱 cash = cash + drawingMoney; System.out.println(account.name + "余额为:" + account.money); //this.getName() = Thread.currentThread().getName() System.out.println(this.getName() + "手里的钱" + cash); } } }
同步方法锁的是对象,锁对象在多个线程之间必须是共享的才能锁住,每个线程的锁对象都不一样就要使用同步块
锁的对象必须是变化的量,必须增删改
安全添加元素:
package com.thread.demo08; import java.util.ArrayList; import java.util.List; public class UnsafeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread( ()-> { synchronized (list){ list.add(Thread.currentThread().getName()); } } ).start(); } Thread.sleep(100); System.out.println(list.size()); } }
JUC并发:
package com.thread.demo08; import java.util.concurrent.CopyOnWriteArrayList; //测试JUC安全类型的集合 public class TestJuc { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); //已经写好的安全类 for (int i = 0; i < 10000; i++) { new Thread( () ->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
某一个同步块同时拥有”两个以上对象的锁“时,发生”死锁“的问题。相互等待对方释放资源
产生死锁的四个必要条件:
只要想办法解决一个条件即可避免死锁发生
示例:
package com.thread.demo08; import org.omg.PortableServer.THREAD_POLICY_ID; //多个线程互相抱着对方需要的资源,然后形成僵持 public class TestLock { public static void main(String[] args) { Makeup g1 = new Makeup(0,"小红"); Makeup g2 = new Makeup(1,"小丽"); g1.start(); g2.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } //化妆 class Makeup extends Thread { //需要的资源只有一份,使用static来保证 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice; //选择 String girl; //需要化妆的女孩 public Makeup(int choice, String girl) { this.choice = choice; this.girl = girl; } @Override public void run() { //化妆 try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } public void makeup() throws InterruptedException { if (choice == 0){ synchronized (lipstick){ //拿到口红 System.out.println(this.girl + "获得口红的锁"); Thread.sleep(1000); } synchronized (mirror){ //这一串同步块不放在上面块中就可以避免死锁 System.out.println(this.girl + "想获得镜子的锁"); } } else { synchronized (mirror){ //拿到镜子 System.out.println(this.girl + "获得镜子的锁"); Thread.sleep(2000); } synchronized (lipstick){ System.out.println(this.girl + "想获得口红的锁"); } } } }
Lock代码格式:
ReentrantLock可重入锁,安全抢票应用:
package com.thread.demo09; import java.util.concurrent.locks.ReentrantLock; public class TestReLock { public static void main(String[] args) { TestReLock1 testReLock1 = new TestReLock1(); new Thread(testReLock1,"小明").start(); new Thread(testReLock1,"小红").start(); new Thread(testReLock1,"小刚").start(); } } class TestReLock1 implements Runnable { int tickets = 10; //定义Lock锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock(); //加锁 if (tickets <= 0){ break; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "拿到了第" + tickets-- +"张票"); } finally { lock.unlock(); //解锁 } } } }
synchronized与Lock对比:
生产者消费者模式:
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常
解决线程通信的方式:
package com.thread.demo09; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //测试线程池 public class TestPool { public static void main(String[] args) { //1.创建服务,创建线程池 //newFixedThreadPool 参数为:线程池大小 ExecutorService service = Executors.newFixedThreadPool(10); //2.执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //3.关闭连接 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }