• java 多线程(一)—— 创建线程的3种方法


    一、复习

    1. 程序、进程、线程

    程序:静态的

    进程:程序的一次执行过程,动态的

    线程:一个QQ.exe是一个进程,聊天发信息是一个线程,聊天接收信息是一个线程,视频通话是一个线程。

    进程是操作系统分配资源的单位,线程是CPU调度的单位。

    2. 单线程和多线程的区别

    学习了下面实现Runnable接口实现多线程之后,我们知道其步骤是先new Thread(new XXX()),然后调用start()方法,start()方法会自动调用run方法,可以直接调用 Thread 类的 run 方法吗?

    答可以呀。只不过不是多线程了,而是单线程了。start()方法的作用是开启一个新线程,那我们跳过了start()方法直接调run()方法,就是不开新线程呗。

    3. C++中如何开启子线程

    【C++】解决子线程没有被执行的问题_玛丽莲茼蒿的博客-CSDN博客_c++ 线程不执行

    4.java中默认的线程

    任何一个程序在java中都是多线程的

    main线程是肯定有的,还有gc线程。即使这个程序只有System.out.println("hello"),后台也会自动开启gc线程

    二、线程的创建的3种方法

    但是我们在真实项目中其实是写一个资源类,然后在主函数中创建资源类的对象,再把这个对象扔到new Thread里

    推荐使用Runnable接口

    2.1 继承Thread类

    2.1.1记忆点

    1. 分3步

    2.  主线程拥有对CPU的优先使用权。但是和C++不同的是,主线程执行完后,即便不使用sleep,子线程依然能够被执行。这或许是因为Java后台自带的守护进程???

     2.1.2 简单演示

    1. public class NewThread extends Thread{
    2. @Override
    3. public void run() {
    4. for(int i=0; i<200; i++){
    5. System.out.println(i+" +++++++++++++");
    6. }
    7. }
    8. public static void main(String[] args) {
    9. //子线程
    10. NewThread newThread = new NewThread();
    11. newThread.start();
    12. //主线程
    13. for(int i=0; i<200; i++){
    14. System.out.println(i+" =============");
    15. }
    16. }
    17. }

    2.1.3 实战——多线程下载网络图片

    本次实战要用到Apache提供的一个第三方包,官网免费下载:

    Commons IO – Download Apache Commons IO

    解压后找到下图中的jar包 

     

     按照下方的流程导入jar包java I/O(四)—— 使用第三方jar包_玛丽莲茼蒿的博客-CSDN博客导入第三方jar包实现I/Ohttps://blog.csdn.net/qq_44886213/article/details/127397913?spm=1001.2014.3001.5501

    1. import org.apache.commons.io.FileUtils;
    2. import java.io.File;
    3. import java.io.IOException;
    4. import java.net.MalformedURLException;
    5. import java.net.URL;
    6. public class DownLoadURLThread extends Thread{
    7. private String url;
    8. private String name;
    9. public DownLoadURLThread(String url, String name) {
    10. this.url = url;
    11. this.name = name;
    12. }
    13. //线程执行体
    14. @Override
    15. public void run() {
    16. DownLoader downLoader = new DownLoader();
    17. downLoader.download(this.url,this.name);
    18. System.out.println(this.name+"下载完毕");
    19. }
    20. public static void main(String[] args) {
    21. DownLoadURLThread thread1 = new DownLoadURLThread("https://bkimg.cdn.bcebos.com/pic/6c224f4a20a4462309f77194d977650e0cf3d6ca79b5","1.jpg");
    22. DownLoadURLThread thread2 = new DownLoadURLThread("https://img9.doubanio.com/view/photo/l/public/p2880712216.webp","2.jpg");
    23. DownLoadURLThread thread3 = new DownLoadURLThread("https://img2.baidu.com/it/u=855433571,726115657&fm=253&fmt=auto&app=138&f=JPEG?w=1080&h=408","3.jpg");
    24. thread1.start();
    25. thread2.start();
    26. thread3.start();
    27. }
    28. }
    29. class DownLoader{
    30. public void download(String url,String name){
    31. try {
    32. FileUtils.copyURLToFile(new URL(url), new File(name));
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. }
    36. }
    37. }

    2.2 实现runnable接口

     

     区别在于:

    2.1.2 实战——模拟龟兔赛跑

    要求

            兔子的速度是乌龟的100倍

            兔子中途睡了一觉

            乌龟先达到终点

    思路:乌龟和兔子同时在跑,所以这是两个线程在同时跑。由于兔子和乌龟的动作行为不同(速度不同,而且兔子需要睡一觉),所以我们写了一个兔子类和一个乌龟类,这两个类都实现了Runnable接口。

    要注意的是,乌龟和兔子赛跑我们写了两个类,但是双十一有1亿个用户并发,我们不可能写1亿个User类各自实现Runnable接口,因为1亿个用户的行为都是一样的,我们写一个类就够了(详见下面的抢票系统)。之所以兔子、乌龟写成两个是兔子乌龟的行为不一样。

    1. /**
    2. * 模拟龟兔赛跑
    3. */
    4. public class RabbitTortoiseRace {
    5. public static void main(String[] args) {
    6. new Thread(new Rabbit()).start();
    7. new Thread(new Tortoise()).start();
    8. }
    9. }
    10. class Runway{
    11. static int length =200; //200米的跑道
    12. }
    13. class Rabbit implements Runnable{
    14. @Override
    15. public void run() {
    16. int length = Runway.length;
    17. int rabbitRunLength =0;
    18. while(rabbitRunLength
    19. //for循环模拟兔子跑1米的时间,比乌龟快100倍
    20. for(int i=0; i<=100; i++){
    21. }
    22. rabbitRunLength++;
    23. System.out.println("兔子--->跑到了"+rabbitRunLength+"米");
    24. //模拟兔子跑到50米时睡了一觉
    25. if(rabbitRunLength == 50){
    26. try {
    27. Thread.sleep(2);
    28. } catch (InterruptedException e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }
    33. System.out.println("兔子到达终点");
    34. }
    35. }
    36. class Tortoise implements Runnable{
    37. @Override
    38. public void run() {
    39. int length = Runway.length;
    40. int tortoiseRunLength =0;
    41. while(tortoiseRunLength
    42. //模拟乌龟跑1米的时间
    43. for(int i=0; i<=1000; i++){
    44. }
    45. tortoiseRunLength++;
    46. System.out.println("乌龟--->跑到了"+tortoiseRunLength+"米");
    47. }
    48. System.out.println("乌龟到达终点");
    49. }
    50. }

     

     2.2.3 实战 —— 模拟抢票系统

    这里只是演示如果“多线程的行为相同”,只写一个实现Runnable的类就够了。实现Runnable的类和开启的线程之间的关系就像是docker的镜像和容器之间的关系(因为开启的容器本身就是一个个的线程),一个镜像可以开启多个容器,还可以在开启的时候给容器命名.

    1. docker run --name="第1个centos" centos:0.1
    2. docker run --name="第2个centos" centos:0.1
    3. docker run --name="第3个centos" centos:0.1

    下面的代码中,我们在开启线程的时候,也可以给线程命名。 

    1. public class RailwayTicketSystem{
    2. public static void main(String[] args) {
    3. BuyTicket buyer = new BuyTicket();
    4. new Thread(buyer,"黑黑").start();
    5. new Thread(buyer,"白白").start();
    6. new Thread(buyer,"黄牛党").start();
    7. }
    8. }
    9. class BuyTicket implements Runnable{
    10. private int ticketNums = 10; //系统里有10张票
    11. //抢票行为
    12. @Override
    13. public void run() {
    14. while(ticketNums>0){
    15. try {
    16. Thread.sleep(100); //模拟延时,放大问题
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. System.out.println(Thread.currentThread().getName()+"--->抢到了第"+ticketNums+"张票");
    21. ticketNums--;
    22. }
    23. }
    24. }

    因为火车票是临界资源,需要进行同步处理,但是这里没有处理,所以出现了重复拿票的情况。

    2.2.4 实现Runnable接口本质上是静态代理

    下面一节我们就来看看什么是静态代理

    三、静态代理

     1.1 记忆点

    实现步骤

    1. 写一个抽象接口/类

    2. 真实角色和代理角色都要实现同一个接口。

    implements Runnable接口实现多线程本质上其实是静态代理。这里的代理是Thread类。Thread类和目标类都实现了Runnable接口。你看下面这两行代码,是不是一样的意思:

    1. //婚庆公司代为举办婚礼的例子
    2. new WeddingCompany(bride,groom).have_a_wedding();
    3. //Thread类代理的例子
    4. new Thread(目标对象).start();

    问题:为什么真实角色和代理角色要实现同一个接口?直接把真实角色作为代理角色的成员变量不行吗?

    回答:拿下面结婚的例子来说,婚庆公司要接手的新郎和新娘可不止一对,有很多对,而且这些人他们在婚礼上想干的事情hava_a_wedding()不一样。所以真实角色和代理角色都要实现接口,并且重写hava_a_wedding方法,真实角色的hava_a_wedding方法要被代理角色的hava_a_wedding方法调用

    1.2 静态代理例子

    以婚庆公司为例

    1. import javax.management.MBeanFeatureInfo;
    2. public class StaticProxy {
    3. public static void main(String[] args) {
    4. Bride bride = new Bride();
    5. Groom groom = new Groom();
    6. WeddingCompany weddingCompany = new WeddingCompany(bride,groom); //新娘和新郎来找中介
    7. weddingCompany.have_a_wedding(); //一切都由婚庆公司操办
    8. }
    9. }
    10. //真实对象和代理对象都要实现同一个接口
    11. interface WeddingStuff{
    12. void have_a_wedding();
    13. }
    14. //真实对象:新娘
    15. class Bride implements WeddingStuff{
    16. @Override
    17. public void have_a_wedding() {
    18. System.out.println("新娘去结婚了!");
    19. }
    20. }
    21. //真实对象:新郎
    22. class Groom implements WeddingStuff{
    23. @Override
    24. public void have_a_wedding() {
    25. System.out.println("新郎去结婚了!");
    26. }
    27. }
    28. //代理对象:婚庆公司
    29. class WeddingCompany implements WeddingStuff{
    30. Bride bride;
    31. Groom groom;
    32. public WeddingCompany(Bride bride,Groom groom){
    33. this.bride = bride;
    34. this.groom = groom;
    35. }
    36. @Override
    37. public void have_a_wedding() {
    38. before_wedding();
    39. bride.have_a_wedding(); //新娘做新娘的事
    40. groom.have_a_wedding(); //新郎做新郎的事
    41. after_wedding();
    42. }
    43. public void before_wedding(){
    44. System.out.println("布置场地,租婚车,配置摄像团队,筹办酒席");
    45. }
    46. private void after_wedding(){
    47. System.out.println("结束,收钱");
    48. }
    49. }

    1.3 缺点

    一个真实对象就要产生一个代理对象,要开3个线程,就要new出来3个Thread对象,开发效率低。如何解决?动态代理! 

    PS:学了一周设计模式然后做了一个SuperRouter的大作业以后,无论是写代码还是看代码都进步了一大截。以前跟着网课敲了几个月的例子从来没有这种感觉。所以还是要注重“输出”!要用项目需求带动技术的学习

    四、run方法可以传参吗

    不可以。那我们想给线程传递参数怎么办?

    (1)如果是MyThread extends Thread方式实现的线程,将想传递的参数定义为MyThread的成员变量,然后new MyThread(参数)的时候把参数传递进去:

    1. class MyThread extends Thread {
    2. private String message;
    3. public MyThread(String message) {
    4. this.message = message;
    5. }
    6. public void run() {
    7. System.out.println(message);
    8. }
    9. }
    10. public class Main {
    11. public static void main(String[] args) {
    12. MyThread thread = new MyThread("Hello, World!");
    13. thread.start();
    14. }
    15. }

    (2)如果是 implements Runnable实现的线程,也一样

    1. class MyRunnable implements Runnable {
    2. private String message;
    3. public MyRunnable(String message) {
    4. this.message = message;
    5. }
    6. public void run() {
    7. System.out.println(message);
    8. }
    9. }
    10. public class Main {
    11. public static void main(String[] args) {
    12. MyRunnable runnable = new MyRunnable("Hello, World!");
    13. Thread thread = new Thread(runnable);
    14. thread.start();
    15. }
    16. }

  • 相关阅读:
    Windows 10 + Jenkins 2.4 安装插件时https 的证书问题及解决
    设置ZIP文件打开密码的两种方法
    MethodArgumentNotValidException 与 ConstraintViolationException
    SpringCloud微服务第2章
    标准差椭圆算法实现
    Linux进程控制
    Linux安装MySQL8
    WPF中的绑定知识详解(含案例源码分享)
    【图形学】29 更复杂的光照
    【Android Studio】常用布局 --- 滚动视图ScrollView
  • 原文地址:https://blog.csdn.net/qq_44886213/article/details/127648846