Java教程

Java多线程-01-线程的创建

本文主要是介绍Java多线程-01-线程的创建,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

image-20220407111749093


一.线程简介

image-20220407112633482


image-20220407112836822 image-20220407112854611

image-20220407112908693

image-20220407113607755


image-20220407113938740


二.线程创建(重点)

image-20220407114533498


Thread类

image-20220407115131847


package 线程创建.Thread;

public class Thread_Test01 {
    public static void main(String[] args) {
        //main线程,主线程

        //创建一个线程对象
        MyThread t = new MyThread();
        //调用start方法开启线程
        t.start();

        for (int i = 0; i < 2000; i++) {
            System.out.println("当前游戏场景运行中"+i);
        }

    }
}

//创建线程的方式一:继承Thread类,重写run方法,调用start开启线程
class MyThread extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 2000; i++) {
            System.out.println("后台加载下一游戏场景"+i);
        }
    }
}

主线程和子线程并行交替执行

image-20220407130704401
image-20220407131247591
image-20220407131746631

练习:同时下载三张网络图片

需要用到工具类库:commons-io

下载方法参考博客:(54条消息) commons-io的下载和使用_唯有一片炙热的博客-CSDN博客_commons-io

导入到IDEA使用:

1.新建包lib

2.拷贝外部库到lib

3.右键lib,点击“添加为库...”选项

image-20220407135248730
public class Thread_WebPictureDown_Test {
    //main,主线程
    public static void main(String[] args) {
        //创建三个线程
        //地址太长这里省略了
        WebPictureDownLoad_Thread t1 = new WebPictureDownLoad_Thread("https:...", "1");
        WebPictureDownLoad_Thread t2 = new WebPictureDownLoad_Thread("https:...", "2");
        WebPictureDownLoad_Thread t3 = new WebPictureDownLoad_Thread("https:...", "3");

        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

//网图下载线程
class WebPictureDownLoad_Thread extends Thread{

    private String url;//网络图片地址
    private String name;//保存的文件名

    //构造器
    public WebPictureDownLoad_Thread(String url, String name){
        this.url = url;
        this.name = name;
    }

    //网图下载线程的执行体
    @Override
    public void run() {
        DownLoader downLoader = new DownLoader();
        downLoader.downLoad(this.url, this.name);
        System.out.println(name+"下载完毕");
    }
}

//下载器
class DownLoader{
    //下载方法
    public void downLoad(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downLoad方法出现问题");
        }
    }
}
image-20220407143041972 image-20220407143024773

Runnable接口(推荐)

具体原理在静态代理部分介绍

image-20220407145324844


public class Runnable_Test {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        LoadScene Scene_city_of_tear = new LoadScene("City_Of_Tear");
        //创建线程对象,通过线程对象来开启我们的线程 (代理)
        Thread thread = new Thread(Scene_city_of_tear);
        thread.start();

        for (int i = 0; i < 2000; i++) {
            System.out.println("当前游戏场景运行情况:"+i);
        }
    }
}

//创建线程的方式二:实现Runnable接口,重写run方法,执行线程需要丢入Runnable接口实现类,调用start方法
class LoadScene implements Runnable{

    private String levelName;

    public LoadScene(String levelName){
        this.levelName = levelName;
    }

    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 2000; i++) {
            System.out.println("后台加载场景:"+this.levelName+"  加载情况:"+i);
        }
    }
}
image-20220407152605400

小结:对比Thread类和Runnable接口

image-20220407153600317

实现Runnable接口的好处

1.接口可以多继承,更灵活方便

2.方便同一个对象被多个线程使用


初识并发问题

例子:买电影票

//《阿凡达2》就要上映了,电影院一共放出10张票,小明,小红,小玉都想去看(并且想看很多次!,所以他们都要抢很多票)
public class BuyTicket {
    public static void main(String[] args) {
        Ticket_Selling_Service Buy_AFanDa = new Ticket_Selling_Service("阿凡达2");

        //第二个参数是线程的name,可用.getName()获得
        new Thread(Buy_AFanDa, "小明").start();
        new Thread(Buy_AFanDa, "小红").start();
        new Thread(Buy_AFanDa, "小玉").start();
    }
}

//售票服务
class Ticket_Selling_Service implements Runnable{
    //电影的名字
    private String movieName;
    //阿凡达电影票总数
    private int ticketNums_AFanDa = 10;

    public Ticket_Selling_Service(String movieName){
        this.movieName = movieName;
    }

    @Override
    public void run() {
        while(true){
            if(this.ticketNums_AFanDa <= 0){
                System.out.println("尊敬的用户"+Thread.currentThread().getName()+"你好,《阿凡达2》电影票已售完");
                break;
            }

            //模拟延时(数据量小,cpu跑太快了,不便于测试)
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"抢到了"+this.movieName+"的第"+this.ticketNums_AFanDa-- +"张票");
        }
    }
}
image-20220407161804138

发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱

在后面的线程同步部分解决这个问题


龟兔赛跑

//龟兔赛跑,兔子比乌龟跑快,兔子每隔10米睡一次觉
public class Race_Test {
    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}

class Race implements Runnable{
    //赛道总长100米
    private static int TotalLength = 100;
    //获胜者
    private static String winner = null;

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("兔子")){
            //模拟兔子跑步,一轮循环加25米
            for (int howFarHasRuned = 0; howFarHasRuned <= 100; howFarHasRuned = howFarHasRuned + 25){
                //模拟兔子睡觉,每10米睡1纳秒
                if(howFarHasRuned % 10 == 0){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("兔子-->已经跑了"+howFarHasRuned+"米");
                //判断比赛是否已经结束了
                if(GameOver(howFarHasRuned))      break;
            }
        }
        else{
            //模拟乌龟跑步,一轮循环加1米
            for (int howFarHasRuned = 0; howFarHasRuned <= 100; howFarHasRuned++) {
                System.out.println("乌龟-->已经跑了"+howFarHasRuned+"米");
                //判断比赛是否已经结束了
                if(GameOver(howFarHasRuned))      break;
            }
        }
    }

    //判断比赛是否结束
    private boolean GameOver(int howFarHasRuned){
        if(winner != null)      return true;//已经有获胜者了
        else{
            if(howFarHasRuned >= 100){//获胜者产生
                winner = Thread.currentThread().getName();
                System.out.println("获胜者是-->"+winner);
            }
        }
        return false;
    }
}
image-20220407205712800 image-20220407205724797

Callable接口

image-20220407212625814


image-20220407212525868


image-20220407212323000
image-20220407212504456

静态代理

结婚的例子

image-20220408120511175
//静态代理模式总节
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色

//好处
//代理对象可以做很多真实对象做不了的事情
//真实对象只需要专注于做自己的事情

//一下是我目前的理解:
//举个现实生活中的例子
//个人游戏开发者A是程序高手可是不会其他的
//那么A可以把音乐代理给音乐创作者B,把美术代理给美术创作者C
//A自己只需要专注于把程序部分做好就可以了

//简而言之:把专业的事情代理给专业的对象去做,每个对象专精于自己的事情

public class Marry_Test {
    public static void main(String[] args) {
        MarryPeople people_Alice = new MarryPeople("Alice");

        WeddingCompany weddingCompany = new WeddingCompany(people_Alice);
        weddingCompany.HappyMarry();
    }
}

//结婚接口
interface Marry{
    void HappyMarry();
}

//真实角色:结婚的人
class MarryPeople implements Marry{
    private String name;

    //构造器
    public MarryPeople(String name){
        this.name = name;
    }

    @Override
    public void HappyMarry() {
        System.out.println(this.name+"-->结婚");
    }
}

//代理角色:婚庆公司帮助别人结婚
class WeddingCompany implements Marry{
    //代理目标(实现了结婚接口的真实对象)
    private Marry target;

    //构造器
    public WeddingCompany(Marry target){
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//真实对象
        after();
    }

    private void before() {
        System.out.println("---结婚前---");
    }

    private void after() {
        System.out.println("---结婚后---");
    }
}
image-20220408115227768

静态代理模式总节:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色

好处:
代理对象可以做很多真实对象做不了的事情
真实对象只需要专注于做自己的事情

我目前的理解:
举个现实生活中的例子
个人游戏开发者A是程序高手可是不会其他的
那么A可以把音乐代理给音乐创作者B,把美术代理给美术创作者C
A自己只需要专注于把程序部分做好就可以了

简而言之:把专业的事情代理给专业的对象去做,每个对象专精于自己的事情


多线程中的静态代理

第一行代码是多线程中的静态代理

第二行代码是之前结婚的例子

image-20220408115954588


Thread(代理对象)和蓝色框里的东西(真实对象)都实现了Runnnable接口

(这里用到了Lamda表达式,之后会介绍)


猜测:

Thread调用start()方法后会调用自己的run()方法 (具体怎么调的不太清楚)

在run()方法中调用真实对象的run()方法

image-20220408121230944

Lamda表达式

image-20220408124746021


image-20220408124930963


image-20220408125344958


推导Lamda表达式

使用前提:接口为函数式接口(接口只有一个方法且是抽象方法)

场景:名叫Apple的类只使用一次

逐步优化,把代码变简单


1.外部类

public class Lamda_Test01 {
    public static void main(String[] args) {
        Food apple = new Apple();
        apple.lamda();
    }
}

//外部类
class Apple implements Food{
    @Override
    public void lamda() {
        System.out.println("吃了苹果");
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void lamda();
}

2.静态内部类

public class Lamda_Test02 {
    //静态内部类
    static class Apple implements Food{
        @Override
        public void lamda() {
            System.out.println("吃了苹果");
        }
    }

    public static void main(String[] args) {
        Food apple = new Apple();
        apple.lamda();
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void lamda();
}

3.局部内部类

public class Lamda_Test03 {
    public static void main(String[] args) {
        //局部内部类
        class Apple implements Food{
            @Override
            public void lamda() {
                System.out.println("吃了苹果");
            }
        }
        Food apple = new Apple();
        apple.lamda();
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void lamda();
}

4.匿名内部类

public class Lamda_Test04 {
    public static void main(String[] args) {
        //匿名内部类
        Food apple = new Food() {//new了一个继承Food接口但是没有名称的实现类
            @Override
            public void lamda() {
                System.out.println("吃了苹果");
            }
        };
        apple.lamda();
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void lamda();
}

5.用lamda简化

public class Lamda_Test05 {
    public static void main(String[] args) {
        //lamda
        Food apple = ()->{
          System.out.println("吃了苹果");
        };
        apple.lamda();
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void lamda();
}

6.简化大括号(当方法内只有一行语句时)

public class Lamda_Test05 {
    public static void main(String[] args) {
        //lamda
        Food apple = ()->System.out.println("吃了苹果");
        apple.lamda();
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void lamda();
}

推导完毕


带参数的lamda表达式

public class Lamda_Test06 {
    public static void main(String[] args) {
        //lamda
        Food apple = (int i)->System.out.println("吃了"+i+"个苹果");
        apple.eat(3);
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat(int i);
}

1.简化参数类型

public class Lamda_Test07 {
    public static void main(String[] args) {
        //lamda
        Food apple = (i)->System.out.println("吃了"+i+"个苹果");
        apple.eat(3);
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat(int i);

2.简化小括号(函数只有一个参数时)

public class Lamda_Test08 {
    public static void main(String[] args) {
        //lamda
        Food apple = i->System.out.println("吃了"+i+"个苹果");
        apple.eat(3);
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat(int i);
}

两个参数,有多行语句的例子

public class Lamda_Test09 {
    public static void main(String[] args) {
        //lamda
        Food apple = (name,i)->{
            System.out.println(name+"吃了"+i+"个苹果");
            System.out.println(name+"吃饱了");
        };
        apple.eat("Alice", 7);
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat(String name, int i);
}

使用举例

无参数

public class Lamda_Test {
    public static void main(String[] args) {
        //lamda
        Food apple = ()->System.out.println("吃了苹果");
        apple.eat();
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat();
}

一个参数

public class Lamda_Test {
    public static void main(String[] args) {
        //lamda
        Food apple = i->System.out.println("吃了"+i+"个苹果");
        apple.eat(7);
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat(int i);
}

多个参数,多行语句

public class Lamda_Test {
    public static void main(String[] args) {
        //lamda
        Food apple = (name,i)->{
            System.out.println(name);
            System.out.println("吃了"+i+"个苹果");
        };
        apple.eat("Alice", 7);
    }
}

//定义函数式接口
interface Food{
    //隐式声明为 public abstract
    void eat(String name, int i);
}

这篇关于Java多线程-01-线程的创建的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!