多线程重点知识归纳总结。

目录

一、线程相关概念

1、程序

2、进程

3、线程

2、多线程应用

1、创建线程的两种方式

2、代码示例

 三、Runnable 接口

1、为什么要实现接口

2、代码示例:如何使用

3、静态代理模式:模拟极简的Tread类

4、继承Tread类 和 实现Runnable接口的区别

 四、线程常用方法

1、补充-线程终止

2、常用方法

3、守护线程

 五、线程的生命周期

 1、线程的几种状态

 2、线程状态转换图

 3、代码示例:查看线程状态

六、线程同步机制

 1、基本介绍

 2、synchronized 关键字

 3、互斥锁

 4、线程死锁

 5、释放锁


一、线程相关概念

1、程序

        程序是一个指令序列。是为完成特定任务、 用某种语言编写的一组指令的集合。简单的说:就是我们写的代码

        它以某些程序设计语言编写,运行于某种目标结构体系上。打个比方,程序就如同以英语(程序设计语言)写作的文章,要让一个懂得英语的人(编译器)同时也会阅读这篇文章的人(结构体系)来阅读、理解、标记这篇文章。一般的,以英语文本为基础的计算机程序要经过编译、链接而成为人难以解读,但可轻易被计算机所解读的数字格式,然后放入运行。

2、进程

        进程是指运行中的程序, 比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。 当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
        进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程 : 有它自身的产生 存在和消亡的过程

3、线程

(1)基本介绍

        线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

        线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

        一个进程可以有很多线程,每条线程并行执行不同。

        同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

(2)单线程

        同一个时刻, 只允许执行一个线程。(单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。)

(3)多线程

        同一个时刻, 可以执行多个线程,比如 : 一个qq进程, 可以同时打开多个天窗口,一个迅需进程, 可以同时下载多个文件

(4)并发

        同一个时刻,多个任务交替执行,造成一种“貌似同时” 的错觉, 简单的说, 单核cpu实现的多任务就是井发。

(5)并行

        同一个时刻,多个任务同时执行。多核cpu可以实现并行。


2、多线程应用

 1、创建线程的两种方式

(1)继承 Thread 类,重写 run 方法

(2)实现 Runnable 接口,重写 run 方法

(3)Thread 类示意图:

2、代码示例

        ▶ 当一个类继承了 Thread 类, 该类就可以当做线程使用
        ▶ 我们会重写 run 方法,写上自己的业务代码
public class Thread01 {
    public static void main(String[] args) throws InterruptedException{//抛出异常

        //创建一个Cat对象,当线程使用
        Cat cat = new Cat();
        //启动线程
        cat.start();

        //当主线程(main)启动一个子线程(Thread-0) ,主线程不会阻塞(即停止),会继续执行
        //这时主线程和子线程是交替执行的(交替速度极快)
        //Thread.currentThread().getName() 获取线程的名称
        System.out.println("主线程继续执行" + Thread.currentThread().getName());

        for (int i = 0; i < 60; i++){

            System.out.println("主线程 i=" + i);

            //让主线程休眠
            Thread.sleep(1000);

        }
    }
}

//当一个类继承了 Thread类,该类就可以当做线程使用
//我们会重写run方法,写上自己的业务代码
class Cat extends  Thread{

    //记录次数,为了控制退出线程
    int times = 0;

    @Override
    public void run() {

        while (true) {

            //该线程每隔一秒,在控制台输出子线程
            System.out.println("子线程 :" + (++times) + "线程名=" + Thread.currentThread().getName());

            //让该线程休眠一秒
            //此处有异常,需要try-catch
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //退出线程
            if (times == 80){
                break;//当times到80时,线程就退出了
            }

        }
    }
}

3、底层


 三、Runnable 接口

1、为什么要实现接口

  • java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了。
  • java设计者们提供了另外一个方式创建线程,就是通过实现 Runnable 接口来创建线程

2、代码示例:如何使用

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

        Text text = new Text();

        //一定要注意:这里不能直接调用start()

        //需要创建Thread对象,把text对象(实现Runnable)放入到Thread
        Thread thread = new Thread(text);

        //然后再调用 start()方法
        thread.start();

    }
}

// Text类 实现 Runnable 接口
class Text implements Runnable {

    //计数
    int count = 0;

    @Override
    public void run() {//普通方法

        while (true) {

            System.out.println("hi" + (++count) + Thread.currentThread().getName());

            try {//休眠1秒,有异常进行try-catch

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }
                
            //退出线程
            if (count == 10) {
                break;
            }

        }

    }
}

3、静态代理模式:模拟极简的Tread类

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

        //创建T1对象
        T1 t1 = new T1();

        //创建ThreadProxy 对象,参数是t1对象
        ThreadProxy threadProxy = new ThreadProxy(t1);
        
        //启动线程,调用start()方法
        threadProxy.start();
    }
}


class T {

}

class T1 extends T implements Runnable {

    @Override
    public void run() {
        System.out.println("T1类");
    }

}

//静态代理模式
//模拟了一个极简的Thread类
class ThreadProxy implements Runnable {

    //属性,类型是 Runnable
    private Runnable target = null; 

    //构造器
    public ThreadProxy(Runnable target) {
        this.target = target;
    }


    //start()方法
    public void start() {
        
        //调用start0()方法
        start0();
    }

    //start0()方法
    public void start0() {
        
        //调用run方法
        run();
    }

    @Override
    public void run() {

        if (target != null) {
            target.run();  //动态绑定(运行类型T1)
        }

    }
}

运行顺序:

        ThreadProxy对象的start()方法 --> start0()方法 --> ThreadProxy对象的run() 方法 --> 创建ThreadProxy对象时,传入了t1对象,所以target 不为空 --> 运行类型是传入是t1对象,所以会调用 T1类 的 run()方法

4、继承Tread类 和 实现Runnable接口的区别

  • 从java的设计来看, 通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk文档中我们可以知道Thread类本身就实现了Runnable接口
  • 实现Runnable接口方式更加适合多个线程共享一个资源的情况 并且避免了单继承的限制,建议使用Runnable

四、线程常用方法

1、补充-线程终止

  • 线程终止就是当线程完成任务后,会自动退出。
  • 还可以通过使用变量来控制 run方法 退出的方式停止线程,即通知方式
  • 代码示例
public class Exit_ {
    public static void main(String[] args) throws InterruptedException{
        T t = new T();
        t.start();

        //通知方式

        //让主线程休眠10秒
        Thread.sleep(10 * 1000);

        //如果希望main线程去控制t 线程的终止,只需要修改loop
        //修改loop的值为false 让t退出run方法,而终止t线程
        t.setLoop(false);
    }
}

class T extends Thread{

    //计数
    int count = 0;

    //设置一个控制变量,控制线程的退出
    private boolean loop = true;

    @Override
    public void run() {

        while (loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("线程正在运行...." + (++count));

        }
    }
    
    //用来修改 loop 属性
    public void setLoop(boolean loop){
        this.loop = loop;
    }
}

2、常用方法

方法名 功能
setName 设置线程称
getName 返回该线程的名称,使之与参数name相同
start 使该线程开始执行,即启动线程,Java 虚拟机底层调用该线程的start0方法。注意:底层会创建新的线程,调用run方法,run方法就是一个简单的方法调用,不会启动新线程
run 调用线程对象 run方法
setPriority 更改线程的优先级
getPriority 获取线程的优先级
sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),(线程的静态方法)
interrupt 中断线程。注意中断线程, 是没有真正的结束线程的。所以一般用于中断正在休眠线程
yield 线程的礼让。 让出cpu, 让其他线程执行,但礼让的时间不确定 所以也不一定礼让成功
join 线程的插队。 插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务,然后再依次执行其他任务

join :线程插队代码示例

public class Method02 {
    public static void main(String[] args) throws InterruptedException{
        T2 t2 = new T2();
        t2.start();

        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程执行...." + i);

            if (i == 5){
                System.out.println("主线程让子线程先执行");
                //join --> 线程插队
                t2.join();
                System.out.println("子线程执行完,主线程继续执行");
            }
        }
    }
}
class T2 extends Thread{

    @Override
    public void run() {

        for(int i = 1;i <= 20; i++){

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子线程在执行...." + i);

        }

    }

}

解释:主线程和子线程会先交替执行,当 i = 5 时子线程会线程插队,然后只执行子线程,当子线程执行完毕,再继续执行主线程。

3、守护线程

  • 用户线程 : 也叫工作线程,当线程的任务执行完毕或通知的方式结束
  • 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  • 常见的守护线程 : 垃圾回收机制
  • 代码示例:
public class Method03 {
    public static void main(String[] args) throws InterruptedException{

        MyDaemonThread myDaemonThread = new MyDaemonThread();

        //如果我们希望主线程结束后,子线程自动结束,只需将子线程设置成守护线程即可
        myDaemonThread.setDaemon(true);

        myDaemonThread.start();



        for (int i = 1; i <= 10; i++) {
            System.out.println("主线程在执行....");
            Thread.sleep(1000);
        }
    }
}
class MyDaemonThread extends Thread{
    @Override
    public void run() {
        
        //无限循环
        for (; ;) {

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子线程在执行....");
        }

    }
}

关键点:在启动线程之前,把 setDaemon()方法设置成 true


五、线程的生命周期

 1、线程的几种状态

2、线程状态转换图

 3、代码示例:查看线程状态

public class State {
    public static void main(String[] args) throws InterruptedException{

        T t = new T();

        //查看初始状态,调用方法getState()
        System.out.println(t.getName() +  " 状态" + t.getState());
        
        //启动线程
        t.start();

        //循环查看线程的状态
        while (Thread.State.TERMINATED != t.getState()){

            System.out.println(t.getName() +  " 状态" + t.getState());

            Thread.sleep(500);

        }

        System.out.println(t.getName() +  " 状态" + t.getState());
    }
}
class T extends Thread{
    @Override
    public void run() {

        while (true){

            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            break;
        }

    }

}

六、线程同步机制

 1、基本介绍

  • 在多线程编程时,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
  • 也可以这样理解 : 线程同步, 即当有一个线程在对内存进行操作时, 其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作

2、synchronized 关键字

(1)同步代码块
        synchronized (对象){//得到对象的锁,才能操作同步代码

                //需要被同步代码

        }
(2)同步方法

        public synchronized void m1(String name){

                //需要被同步的代码

        }

3、互斥锁

(1)基本介绍

  • Java语言中,引入了对象互斤锁的概念, 来保证共享数据操作的完整性。
  • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  • 关键字synchronized 来与对象的互斥锁联系。 当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
  • 同步的局限性 : 导致程序的执行效率要降低
  • 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一个对象)
  • 同步方法 (静态的)的锁为当前类本身。

(2)注意事项

  • 同步方法如果没有使用static修饰 : 默认锁对象为this
  • 如果方法使用static修饰, 默认锁对象 :当前类.class
  • 实现的落地步骤 : ①需要先分析上锁的代码 ②选择同步代码块或同步方法  ③要求多个线程的锁对象为同一个即可!

(3)代码示例

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

        SellTicket03 sellTicket03 = new SellTicket03();

        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}

//使用Runnable方式
class SellTicket03 implements Runnable{

    //控制线程退出
    boolean loop = true;

    private static int ticketNum = 100; //让多个线程共享ticketnum

    //在同一时刻只能有一个线程来执行sell方法,加synchronized关键字
    public synchronized void sell(){
        if (ticketNum <= 0){
            System.out.println("售票结束。。。");
            loop=false;
            return;
        }

        System.out.println("剩余票数" + (--ticketNum));
    }

    @Override
    public void run() {

        while (loop){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //调用方法,实现售票
            sell();
        }

    }
}

4、线程死锁

  • 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。

5、释放锁

  • 当前线程的同步方法、 同步代码块执行结束。就会释放锁
  • 当前线程在同步代码块、 同步方法中遇到break,return。就会释放锁
  • 当前线程在同步代码块、同步方法中出现了未处理的Error 或 Exception,导致异常结束。就会释放锁
  • 当前线程在同步代码块、 同步方法中执行了线程对象的 wait()方法, 当前线程暂停,并释放锁
  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方
    法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,
    该线程不会释放锁
  • 注意:应尽量避免使用suspend()和resume()来控制线程, 方法不再推荐使用

猜你喜欢

转载自blog.csdn.net/yzh2776680982/article/details/125038344
今日推荐