Java多线程并发笔记(1.2)进程、线程知识补充

前言

前面学了Java多线程并发(1.1)线程与线程的实现、启动,然后去看了许多资料,发现还有很多知识,不能简单的跳过,学习笔记还要继续补充

目录

  1. 线程知识
  2. 线程操作
    2.1.创建线程方法
    2.2.设置线程名
    2.3.守护线程
    2.4.yield让行
    2.5.join
    2.6.终止线程方法
    2.7.等待与唤醒
  3. 总结

线程知识

  1. 线程在JVM中的存储

一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。

在这里插入图片描述

程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 。

栈:用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈祯。

堆:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的。

方法区:则用来存放 JVM 加载的类、常量及静态变量等信息,也是线程共享的 。

即JVM运行时的数据区可以分为两种:线程共享和线程独占

  1. 进程、线程的状态

进程的3个基本状态:执行、就绪(活动就绪-静止就绪)、阻塞(活动阻塞-静止阻塞)

线程的5个基本状态:创建、就绪、执行、阻塞、完成(结束)
在这里插入图片描述
在这里插入图片描述
关于锁定(Blocked)在多线程的知识中

随着计算机的不断发展,进程调度需要花费越来越多的时间和空间

引入线程主要是为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理。使OS具有更好的并发性

进程实现多处理非常耗费CPU的资源,而我们引入线程是作为调度和分派的基本单位(取代进程的部分基本功能【调度】)
在这里插入图片描述
一个Firefox是一个进程,而在Firefox上的多个页面可以看成线程

  1. 并行与并发

并行:是说在单位时间内多个任务同时在执行

并发:是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行

并行是针对进程的,并发是针对线程的
并发是造成诸多问题的关键

线程的操作方法

创建线程方法

常见的有两种创建线程的方法,继承Thread类,继承Runnable接口
详情见Java多线程并发(1.1)线程与线程的实现、启动

而在实现多线程的时候,继承重写了run()方法,而启动线程又是使用start()方法

run()和start()方法区别:

run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

start()方法是Thread类的方法:
在这里插入图片描述
在这里插入图片描述

start会启动start0()方法
而start0()方法是native修饰的
native是什么?
native详解

总结就是native是用来通知操作系统的,告诉操作系统要实现native修饰的方法,Java只能调用

设置线程名

线程的名字:主线程叫做main,其他线程是Thread-x
可以看线程的构造方法
在这里插入图片描述
nextThreadNum方法:

在这里插入图片描述

threadInitNumber没有设置值,那我们看看线程命名是怎样的?
我们可以试试
在线程方法下的main方法查看:

         System.out.println(creatThread1.getName());
         System.out.println(creatThread2.getName());

在这里插入图片描述
也就是Thread-x,后接数字排序

通过setName()设置名字
在这里插入图片描述

         System.out.println(creatThread1.getName());
         creatThread1.setName("hello");
         System.out.println(creatThread1.getName());

在这里插入图片描述

守护线程

守护线程是为其他线程服务的, 垃圾回收线程就是守护线程

守护线程有一个特点: 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行

那就需要注意:守护线程不用访问共享资源,守护线程不知道什么时候会关闭
在这里插入图片描述
在这里插入图片描述
通过on(也就是输入值)设置守护线程
isAlive()方法:当线程已经启动再设置守护线程会报错

package com.company.Thread;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;

public class OpenThread {
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    static class CreatThread1 implements Runnable{
        public void run(){
            for(int i=0;i<30;i++) {
                System.out.println(i + ":运行线程1");
            }
        }
    }
    static class CreatThread2 implements Runnable{
        public void run(){
            for(int i=0;i<300;i++)
                System.out.println(i+":运行线程2");
        }
    }
    public static void main(String[] args){

        CreatThread1 thread1=new CreatThread1();
        CreatThread2 thread2=new CreatThread2();

        //使用线程的启动方法start
        //启动线程需要实例化Thread,并传入我们的实例
        //启动线程1
        Thread myThread1=new Thread(thread1);
        myThread1.start();

        //启动线程2
        Thread myThread2=new Thread(thread2);
	    //设置线程2位守护线程
        myThread2.setDaemon(true);
        myThread2.start();
    }
}

在这里插入图片描述

因为守护线程,线程2在线程1关闭后运行到62就也关闭了

yield让行

前面学习了sleep方法,让线程睡眠
在这里插入图片描述

接下来学习yield方法让行
在这里插入图片描述
yield方法会先让别的线程执行,但是不确保真正让出

package com.company.Thread;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;

public class OpenThread {
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    static class CreatThread1 implements Runnable{
        public void run(){
            for(int i=0;i<30;i++) {
                System.out.println(i + ":运行线程1");
            }
        }
    }
    static class CreatThread2 implements Runnable{
        public void run(){
            for(int i=0;i<300;i++)
                System.out.println(i+":运行线程2");
        }
    }
    public static void main(String[] args){

        CreatThread1 thread1=new CreatThread1();
        CreatThread2 thread2=new CreatThread2();

        //使用线程的启动方法start
        //启动线程需要实例化Thread,并传入我们的实例
        //启动线程1
        Thread myThread1=new Thread(thread1);
        myThread1.start();

        //启动线程2
        Thread myThread2=new Thread(thread2);
		//让行
        myThread2.yield();
        myThread2.start();
    }
}

在这里插入图片描述

然而让行并不怎么样

在这里插入图片描述

join

线程运行完,再执行其他线程

在这里插入图片描述

package com.company.Thread;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;

public class OpenThread {
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    static class CreatThread1 implements Runnable{
        public void run(){
            for(int i=0;i<30;i++) {
                System.out.println(i + ":运行线程1");
            }
        }
    }
    static class CreatThread2 implements Runnable{
        public void run(){
            for(int i=0;i<300;i++)
                System.out.println(i+":运行线程2");
        }
    }
    public static void main(String[] args) throws InterruptedException {

        CreatThread1 thread1=new CreatThread1();
        CreatThread2 thread2=new CreatThread2();

        //使用线程的启动方法start
        //启动线程需要实例化Thread,并传入我们的实例
        //启动线程1
        Thread myThread1=new Thread(thread1);
        myThread1.start();

        //启动线程2
        Thread myThread2=new Thread(thread2);
		//join方法
        myThread2.join();
        myThread2.start();

    }
}

在这里插入图片描述

和优先级方法类似
虽然Java方法的意愿是好的,但是操作系统不允许

操作系统内部有线程规划器

终止线程方法

最初是stop方法,stop方法可以让一个线程A终止掉另一个线程B

被终止的线程B会立即释放锁,这可能会让对象处于不一致的状态。

线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法将线程B终止,就会造成数据的不一致

stop方法不安全,已经过时了
现在使用interrupt方法请求终止线程,它并没有强制终止线程(强制的话就和stop一样的)
请求终止进程就是让线程自己去终结,具体该怎么中断要看线程自身

在这里插入图片描述不能中断阻塞线程、死亡线程
源代码:
在这里插入图片描述

interrupt方法压根是不会对线程的状态造成影响的,它仅仅设置一个标志位罢了

interrupt线程中断还有另外两个方法(检查该线程是否被中断):
静态方法interrupted()–>会清除中断标志位
实例方法isInterrupted()–>不会清除中断标志位

package com.company;

public class Main {

    public static void main(String[] args) {
        Main main = new Main();

        // 创建线程并启动
        Thread t = new Thread(main.runnable);
        System.out.println("This is main ");
        t.start();

        try {

            // 在 main线程睡个3秒钟
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("In main");
            e.printStackTrace();
        }

        // 设置中断
        t.interrupt();
    }

    Runnable runnable = () -> {
        int i = 0;
        try {
            while (i < 1000) {

                // 睡个半秒钟我们再执行
                Thread.sleep(500);

                System.out.println(i++);
            }
        } catch (InterruptedException e) {


            // 判断该阻塞线程是否还在
            System.out.println(Thread.currentThread().isAlive());

            // 判断该线程的中断标志位状态
            System.out.println(Thread.currentThread().isInterrupted());

            System.out.println("In Runnable");
            e.printStackTrace();
        }
    };
}

在这里插入图片描述

sleep线程被中断,抛出异常

大概可以看出终结线程的步骤:

线程执行 - > checkAccess判断是否有权限置换中断标志 -> 查看是否阻塞 ->如果是,抛出异常,将中断标志改为false -> 如果一切顺利,调用interrupt0方法,interrupt0是native方法,控制操作系统

等待与唤醒

通过wait()方法可以让线程进入等待状态(阻塞),通过notify()或者notifyAll()方法就能唤醒

wait()是属于Object类的方法,同样的等待方法sleep是Thread类的方法
wait()会释放当前共享对象的锁,而sleep()不会
在这里插入图片描述注意:当前线程调用共享变量对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁并不会被释放

package com.company.Thread;

public class PersonThread {
    static class CreatThread1 {
        public void run() {
            System.out.println("运行线程");
            System.out.println("开始等待");
            synchronized (this){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("等待结束");
        }
    }
    public static void main(String[] args) {
        CreatThread1 creatThread1=new CreatThread1();
        new Thread(creatThread1::run).start();

    }
}

在这里插入图片描述
wait()方法执行后,没有唤醒的话一直在等待,也可以设置超时唤醒wait(time)
唤醒:notify() 通过另一个线程唤醒线程

package com.company.Thread;

public class PersonThread {
    static class CreatThread1 {


        public void run() {
            System.out.println("运行线程");
            System.out.println("开始等待");
            synchronized (this){
                try {
                    this.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("等待结束");
        }

        public void ThreadNotify(){
            synchronized (this){
                this.notify();
            }
        }
    }
    public static void main(String[] args) {
        CreatThread1 creatThread1=new CreatThread1();
        Thread thread1=new Thread(creatThread1::run);
        thread1.start();
        Thread thread2=new Thread(()->creatThread1.ThreadNotify());
        thread2.start();
    }
}

在这里插入图片描述

无论是wait()方法还是notify()方法都需要首先获取目标对象上的一个监视器,也就是wait、notify都在锁synchronized 的代码块或方法内

在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的
为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁
锁住了一个对象,就是获得对象相关联的监视器

在这里插入图片描述
当然,要唤醒必须是同一个监视器
notify唤醒对于等待同一个对象的线程是无序唤醒的,我们可以通过嵌套方法,一个一个唤醒实现有序
即编写一个唤醒方法唤醒线程1,线程1里有一个唤醒方法唤醒线程2。。。

notifyAll()就是用来唤醒正在等待状态中的所有线程的,注意:
(1)notifyAll()只会唤醒那些等待抢占指定的object’s monitor的线程,其他线程则不会被唤醒。
(2)notifyAll()只会一个一个的唤醒,而并非统一唤醒。因为在同一时间内,只有一个线程能够持有object’s monitor
(3)notifyAll()只是随机的唤醒线程,并非有序唤醒

package com.company.Thread;

public class PersonThread {
    static class CreatThread1 {


        public void run() {
            System.out.println("运行线程");
            System.out.println("开始等待");
            synchronized (this){
                try {
                    this.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("等待结束");
        }

        public void ThreadNotify(){
            synchronized (this){
                this.notifyAll();
            }
        }
    }
    static class CreatThread2{
        public void run(){
            System.out.println("对象2开始等待");
            synchronized (this){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("对象2等待结束");
        }
    }
    public static void main(String[] args) {
        CreatThread1 creatThread1=new CreatThread1();
        CreatThread2 creatThread2=new CreatThread2();
        Thread thread=new Thread(creatThread2::run);
        thread.start();
        Thread thread1=new Thread(creatThread1::run);
        thread1.start();
        Thread thread2=new Thread(()->creatThread1.ThreadNotify());
        thread2.start();

    }
}

对象creatThread1的notifyAll唤醒不了对象creatThread2的线程

总结

  1. 复习了线程自身:线程在JVM的存储,线程与进程的基础状态,并发与并行
  2. 线程的操作:创建、命名、守护线程、yield让行、join、interrupt

Thread中重要的还是那几个可以切换线程状态的方法,还有理解中断的真正含义

发布了49 篇原创文章 · 获赞 0 · 访问量 1227

猜你喜欢

转载自blog.csdn.net/key_768/article/details/104282024