Java多线程基础一

什么是线程?
从OS角度看,线程是调度的基本单位
从应用开发者的角度看,线程是一个分担任务的角色

为什么要引入线程?
因为进程的频繁创建,销毁,本身就比较消耗资源。因此引入线程,创建线程,并没有申请资源,销毁线程,也没有去申请资源。线程是在进程内部创建的,共用之前的资源。

从操作系统的角度看线程和进程
在操作系统内核的角度,不分“线程还是进程”,只认PCB。

当创建一个进程出来,其实就是创建了一个PCB出来,同时这个PCB也可以视为当前进程中包含了一个线程了(一个进程中至少得有一个线程)。

进程和线程之间的区别和联系【经典面试题】

  1. 进程是包含线程的,一个进程可以有一个线程,也可以有多个线程。

  2. 每个进程都有独立的内存空间(虚拟地址空间),同一个进程的多个线程之间,共用这个虚拟地址空间。
    在这里插入图片描述

  3. 进程是操作系统分配资源的基本单位,线程是操作系统调度执行的基本单位(操作系统调度的最小任务单位是线程)。

线程和代码的关系
一个线程就是代码的一个执行流(按照一定的顺序,来执行一组指令)

使用Java来操作线程,在Java中,使用Thread这个类的对象来表示一个操作系统中的线程。

PCB是在操作系统内核中描述线程
Thread来是在Java代码中描述线程

Thread类中start和run的区别

start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行

在直接调用run方法的时候,程序并不会创建新线程,run方法内部的代码将在当前线程上运行。

从调用次数来看:start()不能被重复调用,run()可以

start()中的run代码可以不执行完就继续执行下面的代码,即进行了线程切换。直接调用run方法必须等待其代码全部执行完才能继续执行下面的代码。

线程创建的五种方式

  1. 通过继承Thread类,重写run方法
//Thread是Java标准库中描述的一个关于线程的类
//常用的方式就是自己定义一个类继承Thread
//重写Thread中的run方法,run方法就表示线程要执行的具体任务(代码)
class MyThread extends Thread{
    @Override
    public void run() {
        //super.run();
        System.out.println("创建线程,开始执行");
    }
}

public class Thread_TestDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        //start方法,就会在操作系统中真的创建一个线程出来(内核中搞个PCB,加入到双向链表中)
        //这个新的线程,就会执行run中所描述的代码
        t.start();
    }
}
  1. 创建Thread实例,传入一个Runnable实例。实现Runnable接口,重写run方法。
class MyRunable implements Runnable{
    @Override
    public void run() {
        while (true){
            try {
                System.out.println("hello thread");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunable());
        t.start();
    }
}
  1. 继承Thread,重写run,使用匿名内部类的方式
public class ThreadDemo5 {
    public static void main(String[] args) {
        //匿名内部类
        //相当于是创建了一个匿名的类,这个类继承了Thread
        //此处咱们new的实例,其实是new了这个新的子类的实例
        Thread t = new Thread(){
            @Override
            public void run() {
                while(true){
                    try {
                        System.out.println("hello thread");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
}
  1. 实现Runnable,重写run,使用匿名内部类
package java100_0926;
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        System.out.println("hello thread");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
}
  1. 使用lambda表达式来表示要执行的任务
package java100_0926;

public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (true){
                try {
                    System.out.println("hello thread");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

lambda是直接执行任务,不需要重写run方法。

以上这些创建线程的方式,本质都相同。都是借助Thread类,在内核中创建新的PCB,加入到内核的双向链表中。

区别是,指定线程要执行的任务的方式不一样,此处的区别,其实都只是单纯的java语法层面的区别。

public static void main(String[] args) {
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("1");
        }
    });
    t.start();
    System.out.println("2");
}

本质上main线程和 t 子线程是同时并发并行的执行,打印的顺序是随机的。

但子线程需要申请系统运行及时间片轮转调度,而main线程一直处于运行态,从概率上看main先执行的几率非常大。所以是2,1

可视化查看新创建的线程的方法
使用在JDK中内置的jconsole工具
在这里插入图片描述

Thread类的具体用法(类中的属性和方法)
通过构造方法给线程命名

		Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        System.out.println("MyThread");
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"myThread");
        t.start();
属性获取方法备注
IDgetId()
名称getName()给线程命名,方便调试
状态getState()存在的意义是辅助进行线程调度
优先级getPriority()可以对线程设定优先级,设定优先级的方法是:Thread.setPriority(int n) // 1~10, 默认值5。优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
是否后台线程isDaemon()创建的一个线程,默认不是后台线程,是否是后台线程,影响了JVM进行是否能够退出
是否存活isAlive()
是否被中断isInterrupted()判断内核中的PCB是否被销毁
public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        System.out.println(Thread.currentThread().getName());
                        Thread.sleep(1000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"myThread");
        t.start();
        System.out.println(t.getId());
        System.out.println(t.getName());
        System.out.println(t.getPriority());
        System.out.println(t.getState());
        System.out.println(t.isAlive());
        System.out.println(t.isDaemon());
        System.out.println(t.isInterrupted());
    }

线程中断-interurpt()

为了实现能够控制线程,按照需要随时结束,实际开发中,有两种办法

  • 简单粗暴的办法,使用一个boolean变量来作为循环结束标记
  • 使用Thread.interrupted()或者Thread.currentThread().isInterrupted()代替自定义标志位。
    获取线程内置的标记位:线程的isInterrupted()判定当前线程是不是应该要结束循环
    修改线程内置的标记位:Thread.interrupt()来修改这个标记位
public static void main(String[] args){
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()){
                    System.out.println("Thread run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        //e.printStackTrace();
                        // 在这里加上break保证循环能够结束
                        break;
                    }
                }
            }
        },"MyThread");
        t.start();
		// 留出三秒时间给 t 线程执行 方便观察效果
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }

这里的interrupt方法可能有两种行为:

  1. 如果当前线程正在运行,此时就会修改Thread.currentThread().isInterurpted()标记位true
  2. 如果当前线程正在sleep/wait/等待锁 此时会触发InterruptedException

isInterrupted() 这个是Thread的实例
interrupted()这个是Thread的类方法

使用这两者的区别(后续标记位的不同)

  • 当调用静态的interrupted()来判定标记位的时候,就会返回true,同时就会把标记位再改回false。下次再调用interrupted()就会返回false
  • 如果是调用非静态的isInterrupted()来判断标记位,也会返回ture。同时不会对标记位就行修改,后面在调用isInterrupted()的时候就仍然返回true

线程等待-join()

通过对另一个线程对象调用join()方法可以等待其执行结束;

当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。但是,有的时候我们希望线程执行的顺序是可控的。线程等待就是一种办法。

死等线程 t 运行结束再执行主线程

public static void main(String[] args){
        Thread t = new Thread(new Runnable() {
            int count = 0;
            @Override
            public void run() {
                while (count<10000000){
                    count++;
                    System.out.println("thread run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"MyThread");

        t.start();
        try {

            t.join();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程结束");
    }

获取当前线程引用

Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId());
                System.out.println(this.getId());
            }
        };
        t.start();

this和Thread.currentThread没有区别的前提是使用继承Thread,重写run的方式创建线程。
如果当前是通过Runnable或者lambda的方式,this就不可以。

Java线程对象Thread类的状态包括

  • New:新创建的线程,尚未执行;
  • Runnable:运行中的线程,正在执行run()方法的代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起
  • Waiting:运行的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
  • Terminated:线程已终止,因为run()方法执行完毕;

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值