多线程-线程的创建和使用

一、程序、进程、线程的基本概念

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如: 运行中的QQ,运行中的MP3播放器
        程序是静态的,进程是动态的
        进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

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

多线程-线程的创建和使用_第1张图片

1.1 使用多线程的优点

背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

 多线程程序的优点:

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

1.2 何时需要多线程?

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

二、线程的创建和使用

Java 语言的 JVM 允许程序运行多个线程,它通过 java.lang.Thread 类来体现。
Thread类的特性
  1. 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
  2. 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

  • Thread()创建新的Thread对象
  • Thread(String threadname)创建线程并指定线程实例名
  • Thread(Runnable target)指定创建线程的目标对象,它实现了Runnable口中的run方法
  • Thread(Runnable target, String name)创建新的Thread对象
  • 多线程-线程的创建和使用_第2张图片

 

API中创建线程的两种方式:继承Thread类,实现Runnable接口的方式。

2.1 继承Thread类

使用方式:

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run方法。
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:启动线程,调用run方法。

我们以“遍历100以内的所有偶数”来举例

// 1.创建一个继承于Thread的子类
class MyThread extends Thread{

    //2.重写Run()方法 --> 此线程执行的操作声明在run()方法中
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class ThreadTest {

    public static void main(String[] args) {

        //3. 创建Thread子类的对象
        MyThread myThread = new MyThread();

        //4.通过此对象调用start()“:①启动当前线程 ②调用当前线程的run();
        myThread.start();

        //问题一:我们不能够直接调用run()的方式启动线程
        //myThread.run();

        //问题二:在启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行
        //myThread.start();

        //我们需要重新创建一个线程的对象
        MyThread myThread2 = new MyThread();
        myThread2.start();

        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
        System.out.println(Thread.currentThread().getName() + ":" + "hello");

    }

}
Thread.currentThread().getName()这个代码意思是获取当前线程的名字,我们从运行结果上来看,我们创建的线程和主线程存在交替执行的情况,是因为主线程和我们创建的线程在相互抢占CPU资源。

这里有几点需要注意的:

  • 我们不能够直接通过类对象调用run()方法,如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  • 想要启动多线程,必须调用start方法。
  • 当我们想再启动一个线程时,不能通过原对象去class.start(),而需要再new一个对象。一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

多线程-线程的创建和使用_第3张图片

2.2 实现Runnable接口

  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象。
  4. Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

这里看起来可能会有些抽象,下面用代码来展示一下,同样是使用遍历100以内偶数这个例子。

 

package com.demo.thread;

/*
    创建多线程的方式二:实现Runnable接口
    1. 创建一个实现Runnable接口的类
    2. 实现类去实现Runnable中的抽象方法: run();
    3. 创建实现类的对象
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    5. 通过Thread类的对象调用start()
 */


class MThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2==0){
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest2 {

    public static void main(String[] args) {

        MThread mThread = new MThread();

        Thread thread = new Thread(mThread);

        thread.start();
        
    }

}

我们可以看下Runnable接口的源码

多线程-线程的创建和使用_第4张图片

从运行结果上来看,这两者没什么区别

 多线程-线程的创建和使用_第5张图片

那么他们两者的区别是什么呢?

public class Thread extends Object implements Runnable
区别
  • 继承Thread:线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线
  • 程来处理同一份资源。

下面我们来做一个练习。

创建两个分线程,让其中一个线程输出1-100之间的偶数,另一个线程输出1-100之间的奇数
这里我主要讲解两种方式:第一种是通过继承Thread类来实现;第二种创建Thread类的匿名子类的方式。
class MyThread1 extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

class MyThread2 extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 1){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}



public class ThreadDemo {
    public static void main(String[] args) {

        MyThread1 m1 = new MyThread1();
        MyThread2 m2 = new MyThread2();

        m1.start();
        m2.start();

    }

}
public class ThreadDemo {
    public static void main(String[] args) {

        //创建Thread类的匿名子类的方式
        new Thread(){

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();

        new Thread(){

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 1){
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();

    }

}

2.3 Thread类的有关方法

测试Thread中的常用方法:
1. start(): 启动当前线程;调用当前的run()
2. run(): 通常需要重新Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread(): 静态方法,返回执行当前代码的线程
4. getName(): 获取当前线程的名字
5. setName(): 设置当前线程的名字
6. yield(): 释放当前cpu执行权
7. join(): 在线程a中调用线程b的join,此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
8. isAlive(): 判断当前线程是否还存活

前三种在前面已经有了一定的了解,下面我将简单介绍一下后面几种的用法。

getname()和setname()都比较简单,直接通过对象调用即可。

多线程-线程的创建和使用_第6张图片

那么yield()方法又是做什么的呢,yield又叫线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法。

多线程-线程的创建和使用_第7张图片

大家可以在上面的代码中插入看看效果。

join方法就比较好理解了,我们在主线程中插入join方法。

 多线程-线程的创建和使用_第8张图片

 然后我们再来看运行结果

多线程-线程的创建和使用_第9张图片

多线程-线程的创建和使用_第10张图片

 

 当出现i == 20时,主线程进入了阻塞状态,待Thread-1执行完毕之后,主线程才继续执行。

2.4 线程的优先级

线程的优先级:
1.
   MAX_PRIORITY:10
   MIN_PRIORITY: 1
   NORM_PRIORITY: 5
2.如何获取和设置当前线程的优先级
getPriority();
setPriority(int p);
高优先级要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。
并不意味着只有当高优先级的线程执行完以后,低优先级线程才执行。
class HelloThread extends Thread{

    public HelloThread(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){

//                try {
//                    sleep(10);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                System.out.println(Thread.currentThread().getName() + ":" + i + "***" + Thread.currentThread().getPriority());
            }

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

}

public class ThreadMethodTest {

    public static void main(String[] args) throws InterruptedException {
        HelloThread h1 = new HelloThread("Thread-1");

//        h1.setName("线程一");
        h1.setPriority(Thread.MAX_PRIORITY);
        h1.start();


        //给主线程命名
        Thread.currentThread().setName("主线程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i + "***" + Thread.currentThread().getPriority());
            }

//            if (i == 20){
//                try {
//                    h1.join();
//                }catch (InterruptedException e){
//                    e.printStackTrace();
//                }
//            }
        }
        System.out.println(h1.isAlive());
    }

}

多线程-线程的创建和使用_第11张图片

我们把分线程的优先级设置为最高,从运行结果来看,并不是等分线程执行完再执行主线程。这也说明了从概率讲高优先级的线程高概率被执行。

 

你可能感兴趣的:(Java基础学习,java,jvm,开发语言)