Java线程入门(1)

Java线程入门(1)

  • 基础的概念
    • 程序 线程 进程 基础理解
    • 线程分类
    • 其他概念
  • 为什么要有多线程
  • 多线程的创建
    • 继承Thread 类
    • 实现Runnable接口
    • Callable 接口
  • start 和 run
    • 龟兔赛跑学习多线程

基础的概念

程序 线程 进程 基础理解

程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。 大白话就可以理解成:程序写出来的时候是静态的,比如我们写的一些代码其实就是程序,执行代码了以后就是进程,进程执行的时候其实就是去执行它里面的线程。
进程:(process): 它是系统资源分配的最小单位。进程是执行程序的一次执行过程,它是一个动态的概念。
线程(Thread) : 系统调度的最小单位。 CPU调度和执行的单位
联系和区别:

  • 进程是系统进行系统资源分配的最小单位,线程是程序执行的最小单位。
  • 内存分配进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。 系统在运行的时候会为每个进程分配不同的内存空间。而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
  • 包含关系 每个进程至少有一个线程存在,即主线程。(是系统级别的主线程即c 语言的主线程,不是java 的主线程main, java 级别的主线程: 是自己写的入口函数main 方法)
  • 开销 线程的创建、切换及终止效率更高。 线程创建、切换、终止也是比较耗时,这里只是相对进程来说,比起进程来说更好 。 每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销。线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
  • 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。java 提供的方式是共享内存来进行共享资源和通信

线程分类

线程分为用户线程和守护线程 。用户线程就是非守护线程 ,守护线程是后台线程。在程序运行时,即使没有自己创建线程,后台也会有多个线程
守护线程:如主线程(是系统级别的主线程),gc线程
用户线程:main 函数是自己写的所以称为用户线程,又可以称之为java级别的主线程,为系统的入口,用于执行整个程序。 (由于是自己写的,所以可以没有这个线程)
默认启动一个线程的时候是非守护线程,即用户线程。(在这里需要说明,守护线程顾名思义是用来守护的,那它守护的自然就是用户线程。当所有的非守护线程结束时,程序也就终止了,同时结束进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。)

其他概念

  • 很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,CPU只能一次执行一条指令,因为切换的很快,所以就有同时执行的错觉。
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

为什么要有多线程

学习多线程,不知道各位小伙伴有没有想过为什么要有多线程,它存在的意义是什么?学习还是要知道学什么,为什么学?
多线程可以运到很多场景里面理解,最简单也是最常见的案列就是去窗口排队买票,每一个要买票的人就是一个线程,一个窗口对应着一个服务资源,就是进程。
对于以下场景可以考虑使用多线程

  • 并发并行的执行提高性能运行效率 工作量比较大,执行时间比较长的任务
  • 阻塞代码,让后续代码能够执行多个客户端请求服务器,其实每一个客户端的请求就是一个线程。同时处理多个任务,为了让阻塞代码不影响后续代码的执行,解决办法就是让阻塞代码、后续代码在多个线程执行。
    如果我们想执行很多的任务,那我们为什么不采用多进程的方式,其实一个进程里面的多线程和多进程最大的区别就是是这些执行流之间是否有资源的共享多线程是可以资源共享

多线程的终极目标:提高程序的性能和效率
但是多线程为了提高性能还需要考虑其他因素
1.性能 2. 安全
本身多线程是可以提高性能的,但是在考虑性能的时候,还是要考虑安全问题。简单说就是两者都要兼顾一下的。

多线程的创建

主要有三种创建方式,对于初学者来说,主要使用前两种。

继承Thread 类

继承Thread 类 、重写run 方法()、调用start 方法开启线程
线程开启start不一定立即执行,由cpu调度执行

public class TestThread1 extends Thread{
     
    @Override
    public void run() {
     
        //run方法线程体
        for (int i = 0; i < 20; i++) {
     
            System.out.println("我在看代码---" + i);
        }
    }
    public static void main(String[] args){
     
        //main线程,主线程
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start()方法开启线程
        testThread1.start();//每一次的执行结果都不一样,由CPU安排
        for (int i = 0; i < 2000; i++) {
     
            System.out.println("我在学习多线程--" + i);
        }
    }
}

实现Runnable接口

而本质上Thread 类是继承了Runnable接口的,不建议使用继承Thread类,由于单继承具有局限性,更推荐使用实现Runnable接口的方式,这种方法更加灵活方便,且可使同一个对象被多个线程使用。

public class Test2 implements  Runnable {
     

    @Override
    public void run() {
     
        for (int i = 0; i < 20; i++) {
     
            System.out.println("我在学习多线程"+i);
        }

    }

    public static void main(String[] args) {
     
        //创建runnable 接口的实现类对象
        Test2 test2 = new Test2();
        //创建线程对象--代理
        Thread t1 = new Thread(test2);
        t1.start();//
        //上面的代码和与下面等效
        new Thread(new Test2()).start();
        for (int i = 0; i < 100; i++) {
     
            System.out.println("我在写代码"+i);
        }
    }
}

Callable 接口

简单了解名词概念即可
通过实现callable 接口,重写call 方法,该方法与run 方法的区别就是方法有返回值可以获取到,同时方法会抛出异常
创建线程启动call 方法的步骤为:

  1. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(共有的线程个数);
  2. 提交执行:Future result1 = ser.submit(提交哪一个线程);
  3. 获取结果:boolean r1 = result1.get() 这里的get 获取到的就是call 方法的返回值。
  4. 关闭服务:ser.shutdownNow();

start 和 run

最最最核心的两个方法就是start 和 run
start() : 会向系统申请启动某个线程,如果该线程处于运行状态,会自动执行run 方法。但是start 只是告诉cpu 我准备好了,可以执行我了,但是cpu 执行不执行,完全看cpu心情。
run(): 通过thread或者Runnable 接口,定义要执行的任务代码 即为-- 线程运行态执行的代码
而如果不通过通过thread或者Runnable 接口,不调用start()方法,直接调用run 方法就是相当于调用普通的方法。

龟兔赛跑学习多线程

主要是结合使用创建线程 启动线程 以及sleep 方法的使用。

//模拟龟兔赛跑
public class RaceRabbitSleep  implements  Runnable{
     
    private static String winner; // static 保证唯一性 
    //静态变量不属于对象

    @Override
    public void run() {
     
        for (int i = 1; i <= 100; i++) {
     
            //模拟兔子休息
            if(Thread.currentThread().getName().equals("兔子") && i%10 ==0){
     
                // 但是我又不想让它每跑一步休息一次 跑十步休息一次
                try {
     
                    Thread.sleep(200);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameOver(i); // 拿兔子线程来说,兔子为为什么会结束程序,我获取的i是自己的i又不是乌龟的,我的i
            //在这应该是10 来着 ,flag 应该是flase
            if(flag == true){
     
                System.out.println(Thread.currentThread().getName()+"跑了---"+i+"步");
                break;  // 比赛结束,程序结束
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
        //判断是否完成比赛
    }

    //判断是否完成比赛
    private boolean gameOver(int step){
     
        //判断是否有winner?
        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) {
     
        //赛道只有一个赛道
        RaceRabbitSleep race = new RaceRabbitSleep();

        //线程启动-- Runnable
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
 }

入门1的内容大概就是这些,下一篇会涉及到Thread 常用的API 以及线程状态的问题。

你可能感兴趣的:(多线程,java,多线程)