Java多线程Part1:线程(Thread)的介绍及实现

一、 并发与并行

1、并发

并发:指两个或多个事件在同一个时间段内发生
它们会交替执行
在这里插入图片描述
在操作系统中安装了多个程序 并发指的是在一段时间内宏观上有多个程序同时运行
在单 CPU 系统中 每一时刻只能有一道程序执行 即微观上这些程序是分时的交替运行
只不过是给人的感觉是同时运行 那是因为分时交替运行的时间是非常短的

2、并行

并行:指两个或多个事件在同一时刻发生(同时发生)
它们会同时执行
在这里插入图片描述
而在多个 CPU 系统中 则这些可以并发执行的程序便可以分配到多个处理器(CPU)上 从而实现多任务并行执行
即利用每个处理器来处理一个可以并发执行的程序 这样多个程序便可以同时执行
目前电脑市场上说的多核CPU便是多核处理器 核越多 并行处理的程序越多 这能大大的提高电脑运行的效率

二、线程与进程

1、进程

进程是指一个进入到内存中运行的应用程序 每个进程都有一个独立的内存空间 一个应用程序可以同时运行多个进程
进程也是程序的一次执行过程 是系统运行程序的基本单位
系统运行一个程序即是一个进程从创建 运行到消亡的过程

2、线程

线程是进程中的一个执行单元 负责当前进程中程序的执行 一个进程中至少有一个线程
一个进程中是可以有多个线程的 这个应用程序也可以称之为多线程程序
即 一个程序运行后至少有一个进程 一个进程中可以包含多个线程
在这里插入图片描述

三、线程调度

1、分时调度

所有线程轮流使用 CPU 的使用权 平均分配每个线程占用 CPU 的时间

2、抢占式调度(Java使用此方式)

优先让优先级高的线程使用CPU
如果线程的优先级相同 那么会随机选择一个(线程的随机性)
若优先级越高 则抢夺cpu的执行几率越大

大部分操作系统都支持多进程并发运行 现在的操作系统几乎都支持同时运行多个程序
例:一边使用编辑器 一边使用录屏软件 同时还开着画图板 dos窗口等软件
此时 这些程序是在同时运行 但是感觉这些软件好像在同一时刻运行着
实际上 CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换
对于CPU的一个核而言 某个时刻只能执行一个线程 而 CPU的在多个线程间切换速度相对我们的感觉要快 看上去就是在同一时刻运行
其实 多线程程序并不能提高程序的运行速度 但能够提高程序运行效率 让CPU的使用率更高
在这里插入图片描述

四、创建线程类

1、单线程

在main方法中依次执行

public class Person {
    private String name;

    public void run()
    {
        // 定义循环 执行20次
        for (int i=0;i<20;i++)
        {
            System.out.println(name+"第"+i+"次");
        }
    }
    //省略构造方法和get set方法
}
public static void main(String[] args) {
        Person person1=new Person("一号机器人");
        person1.run();

        Person person2=new Person("二号机器人");
        person2.run();
}

JVM(Java虚拟机)执行main方法 main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字 叫main线程(主线程)

单线程程序的弊端:一旦某个位置出现异常 则下面的语句都不会被执行

2、多线程

①、创建多线程程序的第一种方式

Java使用 java.lang.Thread 类代表线程 所有的线程对象都必须是Thread类或其子类的实例
每个线程的作用是完成一定的任务 实际上就是执行一段程序流 即一段顺序执行的代码
Java使用线程执行体来代表这段程序流

通过继承Thread类来创建并启动多线程
步骤:
1. 定义Thread类的子类,并重写该类的run()方法
该run()方法的方法体就代表了线程需要完成的任务 因此把run()方法称为线程执行体
2. 创建Thread子类的实例,即创建了线程对象
3. 调用线程对象的start()方法来启动该线程

线程类:

public class MyThread extends Thread {
    @Override
    public void run() {
        // 设置线程任务
        for (int i=0;i<20;i++)
        {
            System.out.println("run()第"+i+"次");
        }
    }
}

主类:

public static void main(String[] args) {
       MyThread myThread=new MyThread();
       myThread.start();
       for (int i=0;i<20;i++)
       {
           System.out.println("main()第"+i+"次");
       }
}

②、创建多线程程序的第二种方式

实现 java.lang.Runnable 接口也是非常常见的一种方式
只需重写run()方法即可
步骤:
1. 定义Runnable接口的实现类并重写该接口的run()方法 该run()方法的方法体同样是该线程的线程执行体
2. 创建Runnable实现类的实例 并以此实例作为Thread的target来创建Thread对象 该Thread对象才是真正的线程对象
3. 调用线程对象的start()方法来启动线程
线程类:

public class MyThreadRunnable implements Runnable {
    @Override
    public void run() {
        for (int i=0;i<20;i++)
        {
            System.out.println(Thread.currentThread().getName()+" === "+i);
        }
    }
}

主类:

public static void main(String[] args) {
        MyThreadRunnable myThread=new MyThreadRunnable();
        Thread thread=new Thread(myThread);
        thread.start();

        for (int i=0;i<20;i++)
        {
            System.out.println(Thread.currentThread().getName()+" === "+i);
        }
}

五、随机执行流程分析

JVM执行main方法 会找OS(操作系统)开辟一条main方法通向CPU的路径
这个路径叫做main线程(主线程) CPU通过这个线程可以执行main方法

然后又new了一个MyThread(自定义的线程类) 又会开辟一条路径 用于执行run方法 一旦调用start()方法就会执行run方法()

此时 对于CPU而言 就有了两条执行的路径 CPU就有了选择的权利
CPU喜欢谁 就会执行哪条路径 控制不了CPU
因此就有了程序的随机执行(一会儿执行这个 一会儿执行那个)
两个线程:一个main线程 一个新线程 一起抢夺CPU的执行权(即CPU的执行时间)谁抢到了就让谁执行对应的代码

六、多线程程序的内存

每当调用了Thread的start()方法 都会new一个新的栈内存空间
从而增加一个线程用于执行对应的Thread里的run()方法

CPU从而就有了选择的权利 可以执行main方法也可以执行新增的线程里的run()方法

多线程的好处:多个线程之间互不影响 因为在不同的栈空间中

七、线程的名称:线程也是有名字的

1、获取线程的名称

方法一:使用Thread类中的getName()方法
String getName() 返回该线程的名称

主线程:main
新线程:Thread-0 Thread-0 …

线程类:

@Override
public void run() {
    // 获取线程名称
    String name = getName();
    System.out.println(name);
}

主类:

public static void main(String[] args) {
       MyThread2 myThread2=new MyThread2();
       myThread2.start(); // 返回值:Thread-0

       new MyThread2().start(); // 返回值:Thread-1

       //获取主线程名称:main
       System.out.println(Thread.currentThread().getName());
   }

方法二:先获取到当前正在执行的线程 再使用线程中的getName()方法获取线程的名称
Thread currentThread() 返回当前正在执行的线程对象

currentThread():获取整个线程
线程类:

@Override
public void run() {
    Thread thread = Thread.currentThread();
    System.out.println(thread);
}

主类:

public static void main(String[] args) {
        MyThread2 myThread2=new MyThread2();
        myThread2.start(); // 返回值:Thread[Thread-0,5,main]

        new MyThread2().start(); // 返回值:Thread[Thread-1,5,main]

        //获取主线程名称:Thread[main,5,main]
        System.out.println(Thread.currentThread());
}

currentThread().getName():从线程对象中获取名称
线程类:

@Override
public void run() {
    // 链式编程
    System.out.println(Thread.currentThread().getName());
}

主类:

public static void main(String[] args) {
        MyThread2 myThread2=new MyThread2();
        myThread2.start(); // 返回值:Thread-0

        new MyThread2().start(); // 返回值:Thread-1

        //获取主线程名称:main
        System.out.println(Thread.currentThread().getName());
}

2、设置线程的名称

方法一:使用Thread类中的setName()方法
线程类:

@Override
public void run() {
    System.out.println(Thread.currentThread().getName());
}

主类:

public static void main(String[] args) {
        MyThreadSetName myThread=new MyThreadSetName();
        myThread.setName("机器人1");
        myThread.start();
}

输出结果:机器人1

方法二:创建一个带参数的构造方法 参数传递线程的名称 调用父类的带参构造方法
线程类:

public MyThreadSetName() {

}

public MyThreadSetName(String name) {
    // 将线程名称传递给父类 让父类给子线程起一个名字
    super(name);
}

@Override
public void run() {
    System.out.println(Thread.currentThread().getName());
}

主类:

public static void main(String[] args) {
        MyThreadSetName myThread=new MyThreadSetName("机器人2");
        myThread.start();
}

输出结果:机器人2

八、线程的睡眠(sleep)

public static void sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
下面以一个小案例 模拟秒表来演示:

public static void main(String[] args) {
   // 模拟秒表
    for (int i=0;i<=60;i++)
    {
        System.out.println(i);

        // 使用Thread类的sleep()方法让程序睡眠1秒钟
        try {
            // sleep()方法可能会产生异常 需要进行处理
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

九、实现Runnable接口创建多线程程序的好处:

①、避免了单继承的局限性

局限性:一个类只能继承一个类 就像一个人只能有一个亲爹(误
一个类若继承了Thread类 就不能再继承其它类了
但若实现了Runnable接口 还能继承其它的类 还能实现其它的接口

②、增强了程序的扩展性 降低了程序的耦合性(解耦)

实现了Runnable接口的方式将设置线程任务和开启新线程这两件事进行了分离
实现类中重写了run()方法用于设置线程任务
而创建Thread类对象 调用start()方法用于开启新线程
只管创建 与里面传的是什么 做的是什么任务无关

十、匿名内部类方式创建线程

匿名:没有名字
内部类:写在其它类内部的类

作用:简化代码
将子类继承父类 重写父类的代码 创建子类对象这三步三合一完成
将实现类去实现类的接口 重写接口中的方法 创建实现类对象三合一完成

格式:

new 父类或接口()
{
    重复父类或接口中的方法
}

1、线程继承父类Thread的方式

直接在主类中写

new Thread()
{
     // 重写run方法 设置线程任务
     @Override
     public void run() {
         for (int i=0;i<20;i++)
         {
             System.out.println(Thread.currentThread().getName()+" ===== "+i);
         }
     }
 }.start();

2、线程实现Runnable接口的方式

用Runnable接口去接收该接口的实现类对象 体现了多态的思想
同样 直接在主类中写 无须专门写一个线程类

Runnable runnable = new Runnable() {
  // 重写run方法 设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " ===== " + i);
        }
    }
};
new Thread(runnable).start();

再次简化 不额外创建一个Runnable变量了

Runnable直接创建在Thread的参数内

new Thread(new Runnable() {
   // 重写run方法 设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " ===== " + i);
        }
    }
}).start();

发布了56 篇原创文章 · 获赞 0 · 访问量 1186

猜你喜欢

转载自blog.csdn.net/Piconjo/article/details/104601988