文章目录
线程简介
-
进程和线程
– 进程(process):执行程序的一次执行过程,是系统资源分配的单位;
– 线程(thread):一个进程可包含多个线程,是cpu和执行的单位。
– 要区别多核(真正的并行)和单核(模拟)
– main主线程,gc线程 -
线程的调度和操作系统相关,不可人工干预
-
同一资源需要并发控制
-
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建
1. Thread:继承
-
步骤:
1.自定义的线程类继承Thread类
2.重写run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程
总结:线程不一定立即执行,而是并行交替进行,由cpu安排调度
-
代码实现:
//1. 继承Thread类 public class TestThread1 extends Thread{ //2. 重写run方法 @Override public void run(){ //线程体 for(int i = 0;i < 20;i++){ System.out.println("run:"+i); } } public static void main(String[] arg){ //main线程,主线程 //创建线程对象 TestThread1 testThread1 = new TestThread1(); //3. 调用start()方法开启线程(交替执行,并行) testThread1.start(); //先run后main(不是并发) // testThread1.run(); for (int i = 0; i < 20; i++) { System.out.println("main:"+i); } } }
-
实例:图片下载器
//实现多线程同步下载图片 public class TestThread2 extends Thread{ private String url; private String name; //保存文件名 public TestThread2(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) { TestThread2 testThread1 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic1.png"); TestThread2 testThread2 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_640,limit_1", "./pic2.png"); TestThread2 testThread3 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic3.png"); //下载结果不是按顺序执行,同时进行的 testThread1.start(); testThread2.start(); testThread3.start(); } } //下载器 class WebDownloader{ //下载方法 public void downloader(String url, String name){ try { //把url返回到一个file FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现问题"); } } }
2. Runnable:实现
-
步骤
1.定义MyRunnable类实现Runnable接口
2.重写run()方法,编写线程执行体
3.创建线程对象,调用Thread对象.start()方法启动线程
总结:推荐使用,避免单继承局限性,方便同一个对象被多个线程使用
-
静态代理模式
– 特点
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
– 好处
代理角色可以做很多真实对象做不了的事
真实对象专注做自己的事情
-
代码
//实现runnable接口 public class TestThread3 implements Runnable{ //2. 重写run方法 @Override public void run(){ //线程体 for(int i = 0;i < 20;i++){ System.out.println("run:"+i); } } public static void main(String[] arg){ //main线程,主线程 //创建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("main:"+i); } } }
-
实例一:修改图片下载器
//实现多线程同步下载图片 public class TestThread4 implements Runnable{ private String url; private String name; //保存文件名 public TestThread4(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) { TestThread2 testThread1 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic1.png"); TestThread2 testThread2 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_640,limit_1", "./pic2.png"); TestThread2 testThread3 = new TestThread2("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic3.png"); //下载结果不是按顺序执行,同时进行的 new Thread(testThread1).start(); new Thread(testThread2).start(); new Thread(testThread3).start(); } }
-
实例二:买票 说明同一个对象被多个线程使用
//发现问题:多个线程不安全 public class TestThread5 implements Runnable{ //票数 private int ticketNums = 10; @Override public void run(){ while(true){ if(ticketNums<=0){ break; } System.out.println(Thread.currentThread().getName() + "-->拿到了第"+ticketNums+"票"); ticketNums--; } } public static void main(String[] args) { TestThread5 ticket = new TestThread5(); new Thread(ticket,"小明").start(); new Thread(ticket,"老师").start(); new Thread(ticket,"黄牛").start(); } }
-
案例三:龟兔赛跑
public class Race implements Runnable{ //胜利者 private static String winner; @Override public void run(){ //模拟赛道 for (int i = 0; i <= 100; i++) { //模拟兔子休息 if(Thread.currentThread().getName().equals("兔子") && i%20==0){ try { Thread.sleep(1); } 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>=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(); }
3. Callable:实现(了解)
-
代码
//实现多线程同步下载图片 //多了返回值 //可以加异常 public class TestCallable implements Callable<Boolean> { private String url; private String name; //保存文件名 public TestCallable(String url,String name){ this.url = url; this.name = name; } //下载图片线程的执行体 @Override public Boolean call(){ WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载了文件名为:" + name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable testThread1 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic1.png"); TestCallable testThread2 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_640,limit_1", "./pic2.png"); TestCallable testThread3 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/a8014c086e061d95fa492c5e79f40ad162d9ca4d?x-bce-process=image/resize,m_lfit,w_220,limit_1", "./pic3.png"); //1.创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //2.提交执行 Future<Boolean> r1 = ser.submit(testThread1); Future<Boolean> r2 = ser.submit(testThread2); Future<Boolean> r3 = ser.submit(testThread3); //3. 获取结果 boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); //4.关闭服务 ser.shutdown(); } }
Lamda表达式
-
必须是函数式接口:任何接口,只有唯一一个抽象方法
– 静态内部类:放在类里
– 局部内部类:放在方法里
– 匿名内部类:没有类的名称,必须借助接口或者父类
– lambda简化:
like = ()->{ System.out.pringln(""); }; like.lambda();
-
多行就用代码块包裹
-
多个参数也可以去掉参数类型,必须加括号
线程状态
1. 前言
-
解释
创建:new
就绪:start
阻塞:sleep,wait,同步锁定等
运行:cpu真正让其运行
死亡:线程中断或结束
-
常用方法
setPriority():设置一个线程的优先级
sleep():强迫一个线程睡眠N毫秒
join():等待线程终止
yield():线程礼让
interrupt():中断【不推荐】
isAlive():判断一个线程是否存活
2. 状态:线程停止
-
方法:通过设置停止标志位,线程调用用户自己写的停止线程的方法,使得线程停止
-
实例:当主线程打印输出900次时,将线程Thread停止
//1. 建议线程正常停止:利用次数,不建议死循环 //2. 建议使用标志位:设置标志位 //3. 不要使用srop destroy等过时方法 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("线程该停住了"); } } } }
3. 状态:线程休眠
-
方法:线程通过调用sleep()方法,实现线程休眠。
-
注意:每个对象都有一个锁,sleep不会释放锁
-
实例:实现10秒倒计时,每打印一个数字,线程休眠一秒,再打印下一个数字。
// 线程休眠 public class TestSleep{ public static void main(String[] args) { try { tenDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void tenDown() throws InterruptedException { int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if (num < 0){ break; } } } }
4. 状态:线程礼让
-
方法:线程通过调用yield()方法,让正在执行的线程暂停,但不阻塞,转为就绪状态。
-
注意:礼让不一定成功,看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() + "线程停止执行"); } }
5. 状态:线程强制执行
-
方法:利用join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞,插队
-
实例
//join方法,插队 public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("线程vip来了"+i); } } public static void main(String[] args) throws InterruptedException { //启动我们的线程 TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); //主线程 for (int i = 0; i < 500; i++) { if(i==200){ thread.join(); } System.out.println("main"+i); } } }
6. 线程状态观测
-
线程状态:
1)NEW:尚未启动的线程处于该状态。
2)RUNNABLE:在java虚拟机中执行的线程处于此状态。
3)BLOCKED:被阻塞等待监视器锁定的线程处于此状态。
4) WAITING:正在等待另一个线程执行特定动作的线程处于此状态。
5)TIMED_WAINTING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。6)TERMINATED:已退出的线程处于此状态。
-
方法:线程调用getState()方法获取线程状态
-
注意:死亡后的线程,不能再次启动
-
案例
public class TestState{ public static void main(String[] args) throws InterruptedException { //利用lambda表达式重写run Thread thread = new Thread(()->{ for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); //1s } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(""); }); //观察状态 Thread.State state = thread.getState(); System.out.println(state); //new //观察启动后 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); //输出状态 } } }
7. 线程优先级
-
内容:线程的优先级有1-10级,数字越大优先级越高。但是优先级越高cpu也不一定优先调用。
-
方法:
获取线程优先级:getPriority()
设置线程优先级:setPriority(int priority) -
注意:先设置优先级再启动
-
实例:设置不同线程的优先级并查看调用结果
public class TestPriority { 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); //先设置优先级,再启动 t1.start(); t2.setPriority(1); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY); t4.start(); // // t5.setPriority(-1); // t5.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); } }
8. 守护线程
-
内容:线程分为用户线程和守护线程。
-
注意:
- 用户线程(main())虚拟机需要等它执行完毕后再停
- 守护线程(gc())虚拟机无需等它执行完毕再停止。守护线程可用于后台记录操作的日志、监控内存、垃圾回收等待时。
-
方法:线程**调用setDaemon(true)**方法。默认为false为用户线程。
-
实例:依照视频中例子,模拟人类(用户线程)的生命有限,而上帝(守护线程)可以无限守护。当用户线程完成停止时,虚拟机停止,守护线程也无法继续进行。
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!======"); } }
线程同步
1. Synchronize
-
并发问题:同一对象被多个线程同时操作(内存)
– 线程不安全,有负数
– ArrayList不安全,copyOnWriteArrayList安全(JUC)
-
同步方法:
条件:队列 + 锁(对象)
synchronized修饰
public synchronized void play(xx){}
默认锁的是this(类本身),可以修改锁的对象(需要增删改)
-
死锁:多个线程各自占有一些对方需要的资源,等待对方释放资源
2. Lock
-
可重入锁,可显式加锁
-
类:ReentrantLock lock
-
方法:lock.lock(); 加锁
lock.unlock(); 解锁
线程协作
1. 生产者和消费者
-
方法:(只能在同步和通信的代码块中用)
wait(): 线程等待,且和sleep不同,会释放锁
notify(): 唤醒
notifyAll():唤醒同一对象上所有调用wait()方法的线程
-
方法一:共用缓冲区(管程法)
方法二:标志位(信号灯法)
线程协作
1. 生产者和消费者
-
方法:(只能在同步和通信的代码块中用)
wait(): 线程等待,且和sleep不同,会释放锁
notify(): 唤醒
notifyAll():唤醒同一对象上所有调用wait()方法的线程
-
方法一:共用缓冲区(管程法)
方法二:标志位(信号灯法)
线程池
-
问题:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
-
解决:创建多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
-
好处:
1) 提高响应速度;
2) 降低资源消耗;
3) 便于线程管理 -
实现步骤
-
创建一个线程池服务
ExecutorService:真正的线程池接口,常见子类为ThreadPoolExecutor。
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
-
执行
service.execute(new MyThread()); service.execute(new MyThread());
-
关闭链接
service.shutdown();
-
本文是参考B站遇见狂神说的相关视频进行学习记录的,如有问题也欢迎大家一起交流~