多线程
GitHub - SoftLeaderGy/StartThread
一、线程的相关简介
二、线程的创建(Thread、Runnable)
(Thread、Runnable、Callable)
1、方式一(继承Thread类)
- 步骤:
- 继承Thread类
- 重写run()方法
- 主线程调用start()方法开启线程
- 总结:
- 线程开启不一定立即执行,由cpu就行调度
- 代码:
/** * 创建线程类 * 重写run()方法 * 调用start开启线程 */ public class TestThread01 extends Thread{ @Override public void run() { // run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我在看代码--" + i); } } public static void main(String[] args) { // 创建一个线程对象 TestThread01 testThread01 = new TestThread01(); // 调用start方法开启线程 testThread01.start(); for (int i = 0; i < 20; i++) { System.out.println("我在学习--" + i); } } }
2、方式二(实现Runnable)
- 步骤:
- 实现Runnable接口具有多线程的能力
- 启动线程:传入目标对象 + Thread对象.start()
- 代码
package com.yang.demo01; /** * 实现Runnable接口,实现多线程 * 实现Runnable接口、重写run方法、执行线程需要丢入Runnable接口的实现类、调用start方法 */ public class TestThread03 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("我在看书 -->" + i); } } public static void main(String[] args) { TestThread03 testThread03 = new TestThread03(); // 开启线程 new Thread(testThread03).start(); for (int i = 0; i < 100; i++) { System.out.println("我在学习 -->" + i); } } }
总结
○推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
三、初识并发问题
- 买票案例(10张车票,三个人同时买票)
- 代码
package com.yang.demo01; public class TestThread04 implements Runnable{ // 定义车票一共100张票 private int TICKET = 10; @Override public void run() { while (true) { if(TICKET <= 0){ break; } // 电脑速度过快,添加延时 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第--->"+ TICKET-- + "张票"); } } public static void main(String[] args) { TestThread04 testThread04 = new TestThread04(); new Thread(testThread04,"小明").start(); new Thread(testThread04,"小李").start(); new Thread(testThread04,"小王").start(); } }
- 发现问题
- 多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
四、线程创建(Callable)
- 步骤
- 实现Callable接口,需要返回值类型
- 重写Callable接口,需要抛出异常
- 创建实现Callable接口的目标对象
- 创建执行服务:
- // 创建服务 Executors.newFixedThreadPool(3); 创建线程数为3个
- ExecutorService executorService = Executors.newFixedThreadPool(3);
- 提交执行
- // 提交执行任务,c1为实现Callable接口实现类的对象
- Future s1 = executorService.submit(c1);
- 获取结果
- // 获取执行线程返回结果
- System.out.println(s1.get());
- 关闭服务
- executorService.shutdownNow();
- 代码
package com.yang.demo02; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; public class TestCallable01 implements Callable<Boolean> { public TestCallable01(String url, String name) { this.url = url; this.name = name; } private String url; private String name; @Override public Boolean call() throws Exception { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载文件完成,文件名为--"+ name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable01 c1 = new TestCallable01("https://lmg.jj20.com/up/allimg/4k/s/02/2109242332225H9-0-lp.jpg", "1.jpg"); TestCallable01 c2 = new TestCallable01("https://lmg.jj20.com/up/allimg/tp01/1ZZQ20QJS6-0-lp.jpg","2.jpg"); TestCallable01 c3 = new TestCallable01("https://lmg.jj20.com/up/allimg/tp04/1Z92G92I25110-0-lp.jpg","3.jpg"); // 创建服务 Executors.newFixedThreadPool(3); 创建线程数为3个 ExecutorService executorService = Executors.newFixedThreadPool(3); // 提交执行任务 Future<Boolean> s1 = executorService.submit(c1); Future<Boolean> s2 = executorService.submit(c2); Future<Boolean> s3 = executorService.submit(c3); // 获取执行线程返回结果 System.out.println(s1.get()); System.out.println(s2.get()); System.out.println(s3.get()); // 关闭服务 executorService.shutdownNow(); } } /** * 下载器 */ @Slf4j class WebDownloader { public void downloader(String url, String name) { try { // commons.io包 下载文件工具包 // 通过URL下载图片 FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); log.info("IO异常,downloader方法出现异常!"); } } }
五、守护线程(daemon)
- 线程
- 用户线程(main)
- 守护线程(gc)
- 虚拟机必须要确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等。
- 测试代码
package com.yang.state; /** * @Description: 测试守护线程 * @Author: Guo.Yang * @Date: 2022/02/24/23:01 */ public class TestDaemon { public static void main(String[] args) { You you = new You(); God god = new God(); // 开启上帝线程 Thread thread = new Thread(god); // 设置上帝线程为守护线程 thread.setDaemon(true); // 开启用户线程 thread.start(); //开启用户线程 new Thread(you).start(); } } // 你(用户进程) class You implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("开心的活着"); } System.out.println("good bye"); } } // 上帝线程(守护线程) class God implements Runnable { @Override public void run() { // 正常的线程 应该为上帝线程会一直执行下去 // 但由于是守护线程,也就是用户线程停止后,守护线程会自动结束 while (true) { System.out.println("上帝守护着你"); } } }
- God线程在不设置守护线程的情况下会是一直执行下去的进程
- 但是设置成守护线程后,用户线程执行完毕后会自动结束线程
- 测试截图
六、线程不安全性
1、测试List集合
- 测试List集合线程不安全性
- 众所周知List集合不安全分析下原因
- 测试代码
/** * @Description: 测试List 的线程不安全性 * @Author: Guo.Yang * @Date: 2022/02/25/21:05 */ public class TestList { public static void main(String[] args) { ArrayList<String> list = new ArrayList(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } System.out.println(list.size()); } } // 输出结果:9999
预计输出情况:10000
但是在运行后,有时后出现 小于 10000
- 原因
- 当两个线程同时操作list的同一个位置的时候,会覆盖掉上一个线程添加的元素
2、测试买票
- 测试代码
import lombok.SneakyThrows; /** * @Description: * @Author: Guo.Yang * @Date: 2022/02/25/21:48 */ public class BuyTicket{ public static void main(String[] args) { A buyticket = new A(); new Thread(buyticket, "张").start(); new Thread(buyticket, "王").start(); new Thread(buyticket,"李").start(); } } class A implements Runnable{ private int ticketNums = 10; private Boolean flag = true; @SneakyThrows @Override public void run() { while (flag) { buy(); } } public void buy() throws InterruptedException { if(ticketNums <= 0){ flag = false; return; } Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "拿到了"+ ticketNums--); } }
- 结果输出
- 数据出现问题
// 张拿到了10 // 王拿到了9 // 李拿到了9 // 李拿到了8
- 解决方案
- 同步方法
- synchronized方法
- synchronized方法控制“对象”的方法,每个对象对应一把锁,每个synchronized凡法规都必须获得到该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后边被阻塞的线程才能获得这个锁,继续执行
- 缺陷;若将一个大的方法申明为synchronized将会影响效率
- synchronized块
- synchronized(obj){}
- obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需执行同步监视器,因为同步方法的同步监视器就是this,就是对像本身,活着是class
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程防伪,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没锁锁,然后锁定并访问
- synchronized方法
- 测试代码
import lombok.SneakyThrows; /** * @Description: * @Author: Guo.Yang * @Date: 2022/02/25/21:48 */ public class BuyTicket{ public static void main(String[] args) { A buyticket = new A(); new Thread(buyticket, "张").start(); new Thread(buyticket, "王").start(); new Thread(buyticket,"李").start(); } } class A implements Runnable{ private int ticketNums = 10; private Boolean flag = true; @SneakyThrows @Override public void run() { while (flag) { buy(); } } public void buy() throws InterruptedException { if(ticketNums <= 0){ flag = false; return; } Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "拿到了"+ ticketNums--); } }
* synchronized块 * 代码块锁对象(一定要锁共享资源的变量,谁变锁谁) * 测试代码
/** * @Description: 测试List 的线程不安全性 * @Author: Guo.Yang * @Date: 2022/02/25/21:05 */ public class TestList { public static void main(String[] args) throws InterruptedException { ArrayList<String> list = new ArrayList(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); Thread.sleep(1); } System.out.println(list.size()); } }
七、线程池
- 线程池的API:ExecutorService 和 Executors
- ExecutorService:真正的线程池接口。常见的子类ThreadPoolExecutor
- void execute(Runnable command); 执行任务、命令没有返回值,一般用来执行Runable
- Future submit(Callable task); 执行任务,有返回值,一般用来执行Callable
- void shutdown(); 关闭链接
- Excutors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池