多线程简介及创建

一、线程简介

  • 普通方法调用和多线程

  • 一个进程中可以有多个线程,如视频中同时出现的声音、图像、字幕等。

Process 与 Thread

  • 进程程序相比,程序是指令和数据的有序集合,本事没有任何进行的含义,是一个静态的概念;进程是执行程序的一次执行过程,是动态的概念,是系统资源分配的单位
  • 通常进程中包含多个线程,一个进程至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
  • 注意:很多线程是模拟出来的,真正的多线程是指有多个CPU(即多核),如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一时间点CPU只能执行一个代码,因为切换的快,就产生同时执行的错觉。

核心概念

  1. 线程:独立的执行路径。
  2. 在程序运行时即使没有自己创建线程,后台也会有多个线程:如主线程(main)、gc线程(垃圾回收线程)。
  3. main()称之为主线程,为系统的入口,用于执行整个程序。
  4. 在一个程序中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
  5. 对同一份资源操作时,会出现资源抢夺问题,需要加入并发控制。
  6. 线程会带来额外的开销,如CPU调度时间,并发控制开销。
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

二、线程创建

三种实现方式

  1. Thread class:继承Thread类(源码是实现了Runnable接口)
    • 常用方法:Thread.currentThread().getName();
  2. Runnable 接口:实现Runnable接口(可实现多线程操作同一对象,如:买票)
  3. Callable 接口:实现Callable接口(好像企业中常用,但是本文介绍的不多)

Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
  • 线程不一定启动,听CPU调度
public class TestThread1 extends Thread{
    @Override//先修改线程执行体
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("a"+i);
        }
    }
    public static void main(String[] args) {
        //main线程,主线程
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start方法开启线程
        testThread1.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("b"+i);
            //结果:a、b交替出现,每次执行结果不同
        }
    }
}
案例
//下载网络图片
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 t1 = new TestThread2("");//括号里填url
        
        t1.start();
    }
}

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异常,download方法出现问题");
        }
    }
}

实现Runnable接口

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程体
  • 创建线程对象,调用start()方法启动线程
//线程创建方式2:实现Runnable接口,实现run()方法
public class TestThread3 implements Runnable{
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("a"+i);
        }
    }

    public static void main(String[] args) {
        //创建Runnable接口实现类对象
        TestThread3 testThread3 = new TestThread3();
//        Thread thread = new Thread(testThread3);
//        thread.start();
        //与
        new Thread(testThread3).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("b"+i);
        }
    }
}
小结
  • 继承Thread类
    • 子类继承Thread类具备多线程能力
    • 启动线程:子类对象.start();
    • 不建议使用,避免OOP单继承的局限性
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start();
    • 推荐使用:避免单继承的局限,方便一个对象被多个线程使用
并发问题:多个线程操作同一个对象
//模拟抢票
//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
public class TestThread4 implements Runnable{
    private int ticketnum = 10;
    
    @Override
    public void run(){
        while(true){
            if(ticket<=0){break;}
            //模拟延时
            try{
                Thread.sleep(200);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"->拿到了第"+ticketnum--+"票");
        }
    }
    public static void main(String[] args){
        TestThread4 ticket = new TestThread();
        new Thread(ticket,"大雄").start(); 
        new Thread(ticket,"胖虎").start(); 
        new Thread(ticket,"小夫").start(); 
    }
}
案例:龟兔赛跑
  1. 首先创建赛道距离,要离终点越来越近
  2. 判断比赛是否结束
  3. 打印胜利者
  4. 龟兔赛跑
  5. 设置兔子睡觉
  6. winner
public class Race implements Runnable{
   private static String winner;

   @Override
   public void run() {
       for (int i = 1; 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();

   }

}

实现Callable接口

  1. 需要返回值类型
  2. 重写call方法
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser= Executor.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务:ser.shutdownNow();
posted on 2022-06-13 09:25  NEK_SSY  阅读(48)  评论(0)    收藏  举报