多线程学习

在这里插入图片描述

*多线程实现的通常用法:

1.继承线程或者实现Runnable接口

2.重写run()方法

3.创建一个主函数main函数,从中新建一个这个类的线程的实例化对象,通过创建多个实例化对象从而实现多线程

一、进程与线程

*首先,说起进程,就要说起程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
*而进程则是执行程序的一次执行过程,他是一个动态的概念。是系统资源分配的单位
*通常在一个进程可以包含多个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是cpu调度和执行的单位。

注:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JAkR7dVY-1657988963695)(C:\Users\SirFc\AppData\Local\Temp\1656689479885.png)]

二、普通方法和多线程方法和线程创建的第一种方法

首先普通方法为run()方法。当新创建一个线程的实例化对象时,使用run()方法,则会先调用run()方法中的代码

多线程方法为start()方法,他是多个线程同时进行的方法



代码如下:
//创建线程的方式一:继承Thread类,重写一个run()方法,调用start类开启线程
      public class TestThread1  extends Thread {
          @Override
          public void run(){
//            run方法线程体
          for (int i=0;i<10;i++){
              System.out.println("我喜欢学习Java"+i);
          }
          }

    public static void main(String[] args) {
//              调用run()方法的线程
//        1.创建一个线程对象
        TestThread1 ts=new TestThread1();
//        2.调用start()方法
        ts.run();
        ts.start();

//              main线程,主线程
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程!"+i);
        }

    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-au4wFd65-1657988963697)(C:\Users\SirFc\AppData\Local\Temp\1656691986888.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiYZO2iV-1657988963697)(C:\Users\SirFc\AppData\Local\Temp\1656692176471.png)]

三、多线程图片下载

1.首先要引用一个文件流的包,maven项目则引用依赖,依赖如下:
<dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
 
 2.其次。创建一个文件下载器
 //下载器
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方法出现问题");
        }
    }
3.写主方法类中的代码
//练习Thread,使用多线程下载图片
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 wd=new WebDownloader();
    wd.downloader(url,name);
    System.out.println("下载好的文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread2 ts  = new TestThread2("图片的url", "图片1");
        TestThread2 ts1 = new TestThread2("图片的url","图片2");
        TestThread2 ts2 = new TestThread2("图片的url","图片3");
        ts.start();
        ts1.start();
        ts2.start();
    }
}	

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1rEjV2M-1657988963698)(C:\Users\SirFc\AppData\Local\Temp\1656756204993.png)]

四、创建线程的第二种方法,实现Runnable接口

 //创建线程的第2种方法:实现Runnable接口,重写run方法,执行接口需要调用Runnable接口的实现类,调用start()方法。
public class TestThread3 implements Runnable{
    @Override
    public void run() {
//        run方法线程体
        for (int i = 0; i < 200; i++) {
            System.out.println("我爱学java!"+i);
        }
    }
    public static void main(String[] args){
// 创建一个runnable接口的实现类对象
        TestThread3 ts=new TestThread3();

//   创建一个线程,通过线程对象来开启我们的线程,代理(将原有创建线程丢到这个新的空线程中进行代理!)
//        Thread thread = new Thread(ts);
//         thread.start();
//      这一行代码可以顶如上两行代码
       new Thread(ts).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我爱学习!"+i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RXgC5wK-1657988963699)(C:\Users\SirFc\AppData\Local\Temp\1656756303122.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKt87W1p-1657988963699)(C:\Users\SirFc\AppData\Local\Temp\1656773457902.png)]

五、多个线程操作同一个对象

(未加琐状态,有数据紊乱现象)
//多个线程操作同一个数据,买火车票的例子

//发现问题:多个线程同时操作一个数据的时候,线程不安全,数据紊乱
public class TestThread4 implements Runnable  {
//    票数
     private  int ticket=10;

     @Override
    public void run() {
     while (true){
         if (ticket<=0){
             break;
         }
         ticket--;
//      模拟延时
         try {
             Thread.sleep(200);
         } catch (InterruptedException e) {
             System.out.println("您的购票请求已经超时");
         }
//         Thread.currentThread().getName()方法可以获取线程创建的Name
         System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket+"张票");
     }
    }

    public static void main(String[] args) {
        TestThread4 ts=new TestThread4();
        new Thread(ts,"小明").start();
        new Thread(ts,"黄牛党").start();
        new Thread(ts,"小付").start();
    }
}
有同一张票多个人拿到

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-THF3RSxO-1657988963700)(C:\Users\SirFc\AppData\Local\Temp\1656775757953.png)]

解决问题案例(龟兔赛跑)
//模拟龟兔赛跑
public class Race implements Runnable{

    //     胜利者
    private String winner;

    @Override
    public void run() {



        for (int i = 0; i <= 100; i++) {
            //        模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
//            如果比赛结束了
             if (gameOver(i)){
                 break;
             }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步!");
        }
    }

//    判断是否完成
    private boolean gameOver(int step){
        if (winner!=null){
            return true;
        } {
            if (step>=100){
                winner=Thread.currentThread().getName();
                System.out.println(winner+"是胜利者");
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race=new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y3DfVm4q-1657988963700)(C:\Users\SirFc\AppData\Local\Temp\1656778128822.png)]

六、实现Callable接口(了解即可)

//线程创建三:实现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() throws Exception {
   WebDownloader wd=new WebDownloader();
   wd.downloader(url,name);
        System.out.println("下载文件名未"+name);
   return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable ts=new TestCallable("https://ts4.cn.mm.bing.net/th?id=OIP-C.kB-Ovasi0GW67-rmwnAcwAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.38&pid=3.1&rm=2","付");
        TestCallable ts1=new TestCallable("https://ts4.cn.mm.bing.net/th?id=OIP-C.kB-Ovasi0GW67-rmwnAcwAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.38&pid=3.1&rm=2","付");
        TestCallable ts2=new TestCallable("https://ts4.cn.mm.bing.net/th?id=OIP-C.kB-Ovasi0GW67-rmwnAcwAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.38&pid=3.1&rm=2","付");

//        创建执行服务
        ExecutorService service= Executors.newFixedThreadPool(3);

//        提交执行
        Future<Boolean> r1=service.submit(ts);
        Future<Boolean> r2=service.submit(ts1);
        Future<Boolean> r3=service.submit(ts2);

//     获取结果
        boolean rs1=r1.get();
        boolean rs2=r2.get();
        boolean rs3=r3.get();
//      打印返回职
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
//       关闭服务 : service.shutdownNow();

    }
}

//下载器
class WebDownloader{
    public void downloader(String url,String name){

        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
            System.out.println("下载成功!");
        } catch (IOException e) {
            System.out.println("下载失败!");
        }
    }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mihshqoC-1657988963701)(C:\Users\SirFc\AppData\Local\Temp\1656862281736.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9e077RUu-1657988963702)(C:\Users\SirFc\AppData\Local\Temp\1657035759200.png)]

七、静态代理

静态代理模式总结:
 1、真实对象和代理对象都要实现同一个接口
 2、代理对象要代理真实角色
好处:
1)、代理对象可以做很多真实对象做不了的事情
2)、真实对象可以专注做自己的事情




//静态代理,可表示一个婚庆公司,本人去结婚,婚庆公司去布置现场
public class StacticProxy {
    public static void main(String[] args) {
        WeddingCompany wc=new WeddingCompany(new You());
        wc.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();
    }


    private void after() {
        System.out.println("结婚之后,去收拾场地");
    }

    private void before() {
        System.out.println("结婚之前,去布置场地");
    }
}

八、Lamda表达式(注意:必须要满足函数式接口,才能用Lamda表达式

Lamda表示式是用于Java8之后的,
函数式接口定义:
 1.*任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口,如:
 public interface Runnable{
     public  abstract void run();
 }
 *反例:像这样一个接口中有多个方法的就不是,只有一个接口中有一个方法的才是
 public interface Runnable{
     public abstract void run();
     public abstract void  start();
 }
 2.*对于函数式接口,我们可以通过lamda表达式来创建接口的对象
 
注意:必须要满足函数式接口,才能用Lamda表达式


/*
* 推导Lamda表达式*/
public class TestLamda {

//    3.静态内部类:把实现类放到静态内部类当中
static class like2 implements ILike{

    @Override
    public void run() {
        System.out.println("我喜欢lamda2");
    }
}


//    mian方法
    public static void main(String[] args) {
//       输出的是下面的实现类的
        ILike like = new like();
        like.run();
//        输出的静态类中的方法
        like = new like2();
        like.run();

//        4.局部内部类
        class like3 implements ILike{

            @Override
            public void run() {
                System.out.println("我是lamda3");
            }
        }

        like =new like3();
        like.run();

//        5.匿名内部类,没有类的名称,必须借助接口或者父类,注意,类的后方括号必须加分号(;),因为它相当于一个方法的调用。
        like = new ILike() {
            @Override
            public void run() {
                System.out.println("我是lamda4");
            }
        };
        like.run();

//        6.用lamda简化(目的就是将接口的调用和实现完成,所以要求这个接口中只有一个方法,才能调用lamda表达式)
        like = () ->{
            System.out.println("我是lamda5");
        };
        like.run();
    }
}

//1.创建一个接口
interface ILike{
    public void  run();
}

//2.创建一个实体类来实现接口
class like implements ILike{

    @Override
    public void run() {
        System.out.println("我喜欢lamda");
    }
} 

九、线程状态(五大状态)

1.创建状态
Thread t=new Thread();线程一旦创建就到了新生状态
2.就绪状态
当线程调用了start()方法,线程立即进入到就绪状态,但不意味着立即调度执行
3.阻塞状态
当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞状态解除后,重新进入就绪状态,等待cpu的调度执行。
4.运行状态
当程序进入到运行状态的时候,才真正开始执行线程体的代码块
5.死亡状态
线程中断或者结束,一旦进入死亡状态,就不能再次启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3R5rAJfw-1657988963702)(C:\Users\SirFc\AppData\Local\Temp\1657547778978.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5KJkO5a-1657988963703)(C:\Users\SirFc\AppData\Local\Temp\1657547928918.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yISmIitU-1657988963704)(C:\Users\SirFc\AppData\Local\Temp\1657548478802.png)]

1.线程停止
/测试Stop
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法

public class TestStop implements Runnable {

// 设置一个标志位
    private boolean flag=true;

    @Override
    public void run() {
     int i=0;
     while (flag){
         System.out.println("run...Thread"+i++);
     }
    }

 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 < 99; i++) {
            System.out.println("mian"+i);
            if (i==98){
                testStop.stop();
                System.out.println("线程停止了!");
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahgF3h2G-1657988963704)(C:\Users\SirFc\AppData\Local\Temp\1657549315082.png)]

2.线程休眠(Sleep:每个对象都有一把琐,sleep不会释放琐,Thread.sleep()方法能让问题显露的更清晰)
//模拟网络延时: 放大问题的发生性
public class TestSleep implements Runnable {
//    票数
    private  int ticket=100;

    @Override
    public void run() {
         while (true){
             if (ticket<=0){
                 break;
             }
             ticket--;
             //         模拟延迟

             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
         }

        }




    public static void main(String[] args) {
        TestSleep testSleep = new TestSleep();
        new Thread(testSleep,"小明").start();
        new Thread(testSleep,"小红").start();
        new Thread(testSleep,"小付").start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4EJi1lm-1657988963705)(C:\Users\SirFc\AppData\Local\Temp\1657551180215.png)]

3.线程礼让
//测试礼让线程,看cpu心情
public class TestYield {
    public static void main(String str[]){
        Yield yield = new Yield();
        new Thread(yield,"a").start();
        new Thread(yield,"b").start();
    }
}

//礼让代码
class Yield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程结束");

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MpCOc93U-1657988963705)(C:\Users\SirFc\AppData\Local\Temp\1657553684385.png)]

4.join(相当于一个vip必须它的所有线程全部跑完,别的线程才能继续跑)
//测试join方法
public class TestJoin implements Runnable {

//    vip线程
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("VIP来了:"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
//    主线程
        TestJoin join=new TestJoin();
        Thread thread=new Thread(join);
        thread.start();
        for (int i = 0; i < 500; i++) {
            System.out.println("我是:"+i);
           if (i==50){
            thread.join();
           }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K95El5ok-1657988963706)(C:\Users\SirFc\AppData\Local\Temp\1657554435481.png)]

十、线程状态观测

//测试线程观测状态
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(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("i="+i);
            }
        });
//        观察状态
        Thread.State state=thread.getState();
        System.out.println(state);//new

//        观察启动状态
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);

        while (state !=Thread.State.TERMINATED){
//            只要线程不终止
            Thread.sleep(100);
//            更新线程状态
            state = thread.getState();
            System.out.println(state);
        }
//        线程死亡后不能在启动!
        thread.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZODVpbP-1657988963707)(C:\Users\SirFc\AppData\Local\Temp\1657635796853.png)]

十一、线程优先级(线程的优先级就像中彩票一样,越多,中奖几率越大,而不是1是最大优先级,数字越大,代表优先级越大,范围是1-10,但并不是高的先执行,只是概率大,大多数时候是高的先执行,必须先设置优先级再启动项目)

//测试线程的优先级
public class TestPriority extends Thread {
    public static void main(String[] args) {
        MyPriority myPriority = new MyPriority();
        Thread thread = new Thread(myPriority);
        Thread thread1 = new Thread(myPriority);
        Thread thread2 = new Thread(myPriority);
        Thread thread3 = new Thread(myPriority);
        Thread thread4 = new Thread(myPriority);
        Thread thread5 = new Thread(myPriority);
//      顾名思义,线程的优先级范围为1-10,系统默认优先级为5
        thread.setPriority(1);
        thread.start();
        thread1.setPriority(MAX_PRIORITY);
        thread1.start();
        thread2.setPriority(5);
        thread2.start();
        thread3.setPriority(7);
        thread3.start();
        thread4.setPriority(-1);
        thread4.start();
        thread5.setPriority(0);
        thread5.start();


    }
}

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96480HIp-1657988963707)(C:\Users\SirFc\AppData\Local\Temp\1657636683048.png)]

十二、线程同步机制(多个线程操作同一个资源)

并发:同一个对象被多个线程同时操作

线程同步:就是解决像现实生活当中遇到的问题:“同一个资源,多个人想使用”的问题,最天然的办法就是:排队,一个一个来。

处理多线程的问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是等待机制,多个需要同时访问此对象的线程进入这个对象等待池形成队列,等待前面线程使用完毕,下一个线程再使用
队列和锁
public class TEstLock {
    public static void main(String[] args) {
        Tickes tickes = new Tickes();
        new Thread(tickes).start();
        new Thread(tickes).start();
        new Thread(tickes).start();
    }
}

class Tickes implements Runnable {
    private int tickes = 10;
    // 定义Lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {

            try {
                lock.lock(); // 加锁
                if (tickes <= 0) {
                    return;
                }
                    Thread.sleep(1000);
                System.out.println(tickes--);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock(); // 解锁
            }

        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5YjECGt-1657988963708)(C:\Users\SirFc\AppData\Local\Temp\1657724345967.png)]

十三、线程三大不安全案例

1.线程不安全的集合(以ArrayList为例)

注意:为什么ArrayList线程不安全,因为当使用array.add方法时,可能多个线程同时进行这个方法,从而先创建的线程被后创建的线程覆盖

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> arrayList = new ArrayList<String>();
        for (int i = 0; i < 100000; i++) {
            arrayList.add(Thread.currentThread().getName());
        }
        try {
//            添加Sleep方法,让线程睡眠,更有说服力,正常想要的结果是不为10000,但是大部分时间为10000的原因是cpu执行太快,没得到预期结果
//            为啥ArrayList是线程不安全的,因为它可能俩个或者多个线程共同执行Array.add()操作,导致先创建的被后创建的所覆盖
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(arrayList.size());
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值