Java多线程(1)

Java多线程

  • 本系列博客为学习Java多线程(狂神说)时所做的笔记
  • 本篇博客内容为多线程的三种创建方式

进程和线程

进程(Process):进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。

​ 进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列。进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

线程(Thread):线程是程序执行的最小单位,是进程的一个执行流,一个线程由多个线程组成的。

​ 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

—摘自php中文网

程序和进程

由此可知,线程和进程之间有一种包含和被包含的关系,我们可以将进程和程序做一个比较。程序是指令和数据的有序集合,是一个静态的概念,而进程是动态的。

而多线程则是多个线路同时运行,很多多线程都是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,在一个cpu下,同一个时间片内只能执行一个代码,由于时间片很短,切换的很快,看起来想同时执行,有一个宏观和微观的区别。

线程的创建

三种创建方式

继承Thread类(重点)Thread类也继承了Runnable接口

			1. 自定义线程类继承Thread类

  2. 重现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
//创建线程的方式一
public class testThread extends Thread{


    //run方法线程体
    @Override
    public void run() {
        for (int i =0;i<20;i++){
            System.out.println("我在看代码---"+i);
        }
    }

    //main线程
    public static void main(String[] args) {

        //创建一个线程
        testThread thread1 = new testThread();

        //调用线程的start方法
        thread1.start();
        
        /*
        这里我们需要注意的是,run()和start()是不一样的。
        run()方法它是只有主线程再运行,按顺序运行到run()
		时,把run运行完才继续,可以用嵌入式里的中断来理解。
		而start()是不一样的,start时多线程运行的,具体哪
		个时间片运行哪个线程纯看CPU心情。
         */
        //thread1.run();
        
        for (int i =0;i<20;i++){
            System.out.println("我在学习---"+i);
        }
    }
}

小重点:线程开启不一定立即执行,具体有CPU调度。start()方法其实是开启了一个线程,然后这个线程里自行调用run()方法,小细节,嘿嘿嘿。

//多线程同步下载图片
public class TestThread extends Thread{
    private String url;//网络图片地址
    private String name;//保存的文件名字

    public TestThread(String url,String name){
        this.url = url;
        this.name = name;
    }

    //线程执行体
    @Override
    public void run(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.download(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread thread1 = new TestThread("https://img.php.cn/upload/article/000/000/028/5ccfc59f97a82532.jpg","thread1.jpg");
        TestThread thread2 = new TestThread("https://img.php.cn/upload/article/000/000/028/5ccfc5a49e323744.jpg","thread2.jpg");
        TestThread thread3 = new TestThread("https://img.php.cn/upload/article/000/000/028/5ccfc5bfa2c62416.jpg","thread3.jpg");


        //这个start启动不应该是按照顺序执行的吗?是由于启动时间比较久吗
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//下载器
class WebDownloader{
    //下载方法
    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异常,downloader方法出现问题");
        }
    }
}

这是一个小测试,但是这里有一个问题,执行顺序很显然每次运行都是不一样的。但是这三个线程的start方法不应该是按顺序进行的吗?理论上不应该越靠前越有可能先执行吗?(博主还没有得出答案,欢迎大家指教!)

实现Runnable接口(核心重点)

  1. 定义Runnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程

推荐使用Runnable对象,因为Java单继承的局限性

public class TestThread3 implements Runnable{
    //run方法线程体
    @Override
    public void run() {
        for (int i =0;i<20;i++){
            System.out.println("我在看代码---"+i);
        }
    }

    //main线程
    public static void main(String[] args) {
        //创建一个接口的实现类对象
        TestThread3 testThread3 = new TestThread3();
        //创建一个线程对象,通过线程对象来开启
        Thread thread = new Thread(testThread3);
        thread.start();
        //new Thread(testThread3).start();
        for (int i =0;i<20;i++){
            System.out.println("我在学习---"+i);
        }

    }
}

其实Runnable接口也是利用了thread.start(),不过在创建thread的时候需要把实现了Runnable接口的对象作为参数传入其中。

第一种和第二种的部分区别

第一种不建议使用,因为Java不可以多继承,一个对象无法被多个线程使用。

第二章避免了单继承局限性,方便一个对象被多个线程使用。

演示多个线程操作同一个对象

//多个线程操作同一个资源的时候,线程不安全,数据紊乱
//多个线程同时操作一个对象
public class TestThread4 implements Runnable{

    private int ticketNums = 10;

    @Override
    public void run() {
        while(true){
            if (ticketNums <= 0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
        }
    }

    public static void main(String[] args) {
        TestThread4 testThread4 = new TestThread4();

        new Thread(testThread4,"小明").start();
        new Thread(testThread4,"老师").start();
        new Thread(testThread4,"黄牛").start();
        
    }
}

根据狂神说的课程,跟着敲了个龟兔赛跑:

//模拟龟兔赛跑
public class Race implements Runnable{
    //胜利者
    private static String winner;

    @Override
    public void run() {
        //这里i=1吧,按照课程里为0的话,这兔子直接被扼杀在了起跑线,太惨了
        for (int i =1;i<=100;i++){
            if (Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag = gameOver(i);
            if (flag == true){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
        }
    }

    //判断是否结束
    private boolean gameOver(int steps){
        if (winner != null){
            return true;
        } else 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. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行文件:ExecutorService ser = Executors.newFixThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务: ser.shutdownNow();
public class Testdemo5  implements Callable<Boolean> {
    private String url;//网络图片地址
    private String name;//保存的文件名字

    public Testdemo5(String url,String name){
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.download(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Testdemo5 thread1 = new Testdemo5("https://img.php.cn/upload/article/000/000/028/5ccfc59f97a82532.jpg","thread1.jpg");
        Testdemo5 thread2 = new Testdemo5("https://img.php.cn/upload/article/000/000/028/5ccfc5a49e323744.jpg","thread2.jpg");
        Testdemo5 thread3 = new Testdemo5("https://img.php.cn/upload/article/000/000/028/5ccfc5bfa2c62416.jpg","thread3.jpg");

        //1. 创建执行文件:ExecutorService ser = Executors.newFixedThreadPool(1);//创建线程池
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //2. 提交执行:Future<Boolean> result1 = ser.submit(t1);//提交执行
        Future<Boolean> result1 = ser.submit(thread1);
        Future<Boolean> result2 = ser.submit(thread2);
        Future<Boolean> result3 = ser.submit(thread3);
        //3. 获取结果:boolean r1 = result1.get();
        boolean rs1 = result1.get();
        boolean rs2 = result2.get();
        boolean rs3 = result3.get();
        System.out.println(rs1 +"|"+ rs2 +"|"+ rs3);
        //4. 关闭服务: ser.shutdownNow();
        ser.shutdownNow();
    }
}

好处:

  1. 可以检查返回值

  2. 可以抛出异常

posted @ 2021-03-14 17:40  Xavi_ZHANG  阅读(87)  评论(0)    收藏  举报
Live2D