栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java学习之多线程基础

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java学习之多线程基础

文章目录
  • 前言
  • 一、概念引入
    • 1、程序
    • 2、 进程
    • 3、 线程
    • 4、 并行与并发
    • 5、 单核CPU和多核CPU的理解
  • 二、多线程意义
  • 三、线程的创建与使用
    • 1、 通过继承Thread
    • 2、 通过实现Runnable接口
    • 3、 继承大战实现
  • 四、线程中的常用方法
  • 五、线程的优先级

前言

好久不见!狗子我在啃完了MySQL的基础之后,便转回头继续学习Java了,毕竟Java只掌握到异常时远远不够的,有着大厂梦的陈宝子总得加油冲!奥里给!若阅读过程中存在什么问题欢迎评论留言。

一、概念引入
1、程序

程序(program)是为完成特定任务、用某种语言编写的一组指令集合,即指一段静态的代码,静待对象。

2、 进程

进程(process)是程序的一次执行过程,或是正在运行的一个程序,这类似于我们电脑里面的任务管理器。是一个动态的过程,有它自身的产生、存在和消亡的过程,即生命周期。

  • 程序是静态的,进程是动态的;
  • 进程作为资源分配的单元,系统在运行时会为每个进程分配不同的内存区域。

3、 线程

线程(thread)是由进程中细化得来的,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的;
  • 线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效,但多个线程操作共享的系统资源可能就会带来安全的隐患。

4、 并行与并发
  • 并行:多个CPU同时执行多个任务。比如,多个人同时做不同的事情、机场同时起飞飞往不同地方的飞机;
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:假期多人同时做同一件事、假期间某数字软件抢票。
5、 单核CPU和多核CPU的理解
  • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来;
  • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的);
  • 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
二、多线程意义

我们以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?这就需要我们从多线程的优点入手:

  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验;
  • 提高计算机系统CPU的利用率;
  • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

那么现在问题又来了,我们到底该什么时候会用到多线程呢?主要有以下使用场景:

  • 程序需要同时执行两个或多个任务;
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等;
  • 需要一些后台运行的程序时。
三、线程的创建与使用
1、 通过继承Thread

在创建一个分线程的时候,我们可直接继承Thread类,通过该类实例化的对象进行开启线程。具体步骤如下:

  1. 创建一个继承于 Thread类 的子类
  2. 重写 Thread类 中的 run(),将此线程执行的操作声明在 run() 中
  3. 创建 Thread类 的子类的对象
  4. 通过此对象调用 start()
//1. 创建一个继承于Thread类的子类
public class CreateThread extends Thread{
    
    public static void main(String[] args) {
        //3. 创建Thread类的子类的对象
        CreateThread t1 = new CreateThread();
        CreateThread t2 = new CreateThread();
        //4. 通过对象调用start()方法
        t1.start();
        t2.start();
        //在主线程中添加输出语句进行验证两个线程是否同时执行
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);     //输出线程名,检测各线程的执行
        }
    }

    //2. 重写Thread类中的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if(i%2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i); //输出线程名,检测各线程的执行
            }
        }
    }
}

但在创建和使用线程的过程中需要注意下面两点:

  1. 不能通过直接调用 run()方法 启动线程,这不是多线程,只是调用了方法而已
  2. 若想再启动一个线程,不可用已经调用了 start() 的线程去执行,需重新创建一个线程的对象才行,否则会报IllegalThreadStateException

由于有些线程只使用一次,因此我们可以通过匿名子类的方式对线程的核心内容进行编写,下面通过练习进行演示(创建两个线程,一个线程输出奇数,一个线程输出偶数)

public class ThreadDemo01 {
    //可按部就班根据步骤书写两个线程类继承Thread类,也可利用匿名子类
    public static void main(String[] args) {
        
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    if(i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
        
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    if(i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
    }
}
2、 通过实现Runnable接口

Java 中的 lang包 下内置了 Runnable 接口,我们可通过实现 Runnable 接口中的 run() 方法进行创建线程。具体步骤如下:

  1. 创建一个实现了 Runnable 接口的类
  2. 实现类去实现 Runnable 中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到 Thread 类中的构造器中,创建 Thread 类中的对象
  5. 通过 Thread 类中的对象调用 start(),其中调用了 Runnable 类型的 target 对象
//1. 创建一个实现了 Runnable 接口的类
public class ThreadRunnable implements Runnable{
    public static void main(String[] args) {
        //3. 创建实现类的对象
        ThreadRunnable threadRunnable = new ThreadRunnable();
        //4. 将此对象作为参数传递到 Thread 类中的构造器中,创建 Thread 类中的对象
        Thread t1 = new Thread(threadRunnable);
        Thread t2 = new Thread(threadRunnable);     //方式二中可通过 new 一个新的 Thread 对象开启新线程,共用一个实现类

        t1.setName("线程1");
        t2.setName("线程2");

        //5. 通过 Thread 类中的对象调用 start()
        t1.start();
        t2.start();
        for (int i = 0; i < 50; i++) {
            if(i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }

    //2. 实现类去实现 Runnable 中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if(i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}
3、 继承大战实现

细心的伙伴可能已经发现,Thread类 其实也实现了 Runnable 接口,Thread类 也是 Runnable 接口的一个特殊的实现类。无论我们选择继承 Thread类 还是实现 Runnable接口 的方式进行创建线程,我们都需要重写 run(),将线程要执行的逻辑声明在 run() 中,都需要通过 start() 方法进行开启线程。那么这两种我们到底选择那种呢?

开发中,优先选择实现 Runnable接口 的方式,原因如下并通过多个窗口卖票的案例进行展示:

  • 实现的方式没有类的单继承性的局限性
  • 实现的方式更适合来处理多个线程有共享数据的情况
public class ThreadTicket01 {
    public static void main(String[] args) {
        //创建三个窗口卖票线程
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        //设置三个线程名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        //开启三个线程
        w1.start();
        w2.start();
        w3.start();
    }
}
//创建继承了Thread类的子类
class Window extends Thread {
    private static int ticket = 100;        //设置为静态变量使得定义的多个变量公用共用同一个ticket变量
    @Override
    public void run() {
        while(ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ": 当前票号为: " + ticket--);
        }
    }
}


public class ThreadTicket02{
    public static void main(String[] args) {
        //实例化实现类对象
        NewWindow newWindow = new NewWindow();
        //实例化三个窗口的线程
        Thread w1 = new Thread(newWindow);
        Thread w2 = new Thread(newWindow);
        Thread w3 = new Thread(newWindow);
        //重命名三个线程
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        //启动三个线程
        w1.start();
        w2.start();
        w3.start();
    }
}

//实现 Runnable 接口
class NewWindow implements Runnable {
    private int ticket = 100;       //由于三个线程共用一个实现类,因此不需要使用static

    @Override
    public void run() {
        while(ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ": 当前票号为: " + ticket--);
        }
    }
}
四、线程中的常用方法
  1. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中;

    @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                if(i%2 == 0) {
                    System.out.println(i); 
                }
            }
        }
    
  2. start(): 启动当前线程;调用当前线程的run();

    MyThread t1 = new MyThread();
    t1.start();
    
  3. currentThread(): 静态方法,返回执行当前代码的线程;

  4. getName(): 获得当前线程的名字;

  5. setName(): 设置当前线程的名字;

    System.out.println(Thread.currentThread().getName());
    Thread.currentThread.setName("线程1");
    
  6. yield(): 释放当前CPU的执行权,即线程遇到这一方法时会暂时将执行权转给其余线程;

    if(i % 20 == 0) {
        yield();
    }
    

  1. join(): 会抛异常。在 线程a 中调用 线程b 的 join(),此时 线程a 就会进入阻塞状态,直到 线程b 完全执行完之后,线程a 才结束阻塞状态;

    public class text {
        public static void main(String[] args) {
            MyThread t1 = new MyThread();
            t1.start();
            System.out.println("Hello");
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("World");
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if(i % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }
    }
    

  2. stop(): 已过时。当执行此方法时,强制结束当前线程;

  3. sleep(long millis): 会抛异常。让当前下线程**“睡眠”指定的 millis 毫秒**。在指定的 millis 毫秒。在指定的 millis 毫秒时间内,当前线程时阻塞状态;

    try {
        sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
  4. inAlive(): 判断当前线程是否存活,返回值类型为 boolean。

    System.out.println(t1.inAlive);
    
五、线程的优先级

线程中的优先级范围为1~10,其中较为特殊的三个优先级在 Java 的 Thread类 中已经内置好了,分别为 MAX_PRIORITY、MIN_PRIORITY 和 NORM_PRIORITY,分别代表优先级数值为10、1 和 5。在优先级中常用的两个方法如下:

  • 获取线程优先级:getPriority()
  • 设置线程优先级:setPriority()

说明:高优先级的线程要抢占低优先级线程CPU的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

public class ThreadPriority {
    public static void main(String[] args) {
        MyPriority p1 = new MyPriority();
        p1.setPriority(Thread.MAX_PRIORITY);	///设置优先级为最高
        p1.start();
        for (int i = 0; i < 50; i++) {
            if(i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getPriority() + ": " + i);
            }
        }
    }
}

class MyPriority extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if(i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getPriority() + ": " + i);
            }
        }
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/356990.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号