多线程(一)

认识线程

线程的基本概念

1.进程与线程的关系

首先我们要明确,线程是轻量级进程(创建销毁的开销更小)

进程是包含线程的(一个或多个),我们在Windows任务管理器看不到进程内部的线程,需要借助一些其他的调试工具(VS的调试器/Windbg...)

线程是CPU上调度执行的基本单位,进程是操作系统资源(包括CPU,内存,磁盘资源(文件描述符表))分配的基本单位,进程内部管辖的多个线程之间会共享上述的内存资源、硬盘资源、网络带宽

我们在销毁线程的时候是不会释放资源的

一个进程内部的线程之间,会容易相互影响的

线程在CPU上执行的一系列过程和"进程调度"是一样的

2.JAVA中如何使用线程

线程是操作系统提供的概念,操作系统提供了一些操作线程的API(应用程序编程接口)(Application Programming Interface)

这里注意原生api是C语言的,不同操作系统的api是不一样的

JAVA对其进行了封装,也就是Thread类,这也意味着我们的JAVA代码在不同操作系统上都是可以正常使用的

创建第一个线程

1.继承Thread,重写run

创建线程要使用Thread类,Thread是属于java.Lang的,默认import

Thread本身有一个run方法(相当于线程的入口)

我们要写一个MyThread 来继承Thread 重写run来创建线程

t.start()相当于我们创建一个新的线程,通俗来讲就是多了个执行流来干活

我们可以利用Thread.sleep( 单位为ms)来让线程休眠,也就是让当前线程暂时放弃CPU,休息一会后再执行

注意在使用这一方法的时候,会有异常

这时我们的处理方法只能是Try-Catch,无法throws(父类无)

复制代码
class MyThread extends Thread{
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello thread");
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new MyThread();
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

2.实现RUNNABLE,重写run

这里的本质是还是用Thread来创建线程,只不过是用了RUNNABLE接口(而不是直接重写Thread run 方法),从而会达到解耦合的目的

耦合:两个代码关联关系越大,耦合就越大

解耦合:让执行任务方式和线程这个概念的关联性降低 这样的话后期变更代码会变得更加简单

我们在写代码的时候追求高内聚低耦合

复制代码
class MyRunnable implements Runnable{

    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello thread");
        }
    }
}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=new MyRunnable();
       Thread t=new Thread(runnable);
       t.start();
       while(true){
           System.out.println("hello main");
           Thread.sleep(1000);
       }
    }
}

3.使用匿名内部类来创建线程

这看似简单的一行代码实际上看来三件事:1.创建了一个Thread子类,匿名内部类

2.{}里面可以编写子类的定义代码,子类有哪些属性,要有哪些方法重写父类的方法

3.创建了一个匿名内部类的实例,并且将其赋值给t;

复制代码
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
               while(true){
                   System.out.println("hello t");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            }
        };
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

这样的我们就可以少定义一些类了,如果某个代码是一次性的,就可以使用匿名内部类的方法

4.使用匿名内部类并实现RUNNABLE来创建线程

使用RUNNABLE,任务和线程是分开的,降低了耦合

复制代码
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("hello t");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        Thread t=new Thread(runnable);
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

5.针对三和四进行改进,引用lambda函数(这也是我们推荐的做法)

lambda函数本质是一个匿名函数,最主要的用途是作为"回调函数"

ps:回调函数

1.c语言中函数指针的应用场景就有回调函数 2.JAVA数据结构中,优先级队列指定排序方式

JAVA中方法必须要寄托于类存在,"函数式接口"()->{}

其实这一方法的本质是创建了一个匿名的函数式接口的子类,并且创建出了对应的实例,并且重写了里面的方法(编译器做的事)

复制代码
public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
           while(true) {
               System.out.println("hello t");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

Thread类的其他属性及常见方法

Thread的构造方法

|-----------------------------------------------|---------------------------------------|
| 方法 | 说明 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 可以给线程起名字,线程的名字不会影响线程的执行,名字的意义是方便程序员调试 |
| Thread(Runnable target,String name) | 使用Runnable对象创建线程对象并命名 |
| (了解)Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |

我们可以在jconsole上看到线程,可以通过给线程命名来更好的调试

Thread的几个常见属性

1.

|-----|------------------------------------|
| 属性 | 获取方法 |
| ID | getID() JAVA中给线程分配ID,标识线程的身份,类似pid |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |

2.isDaemon()

isDaemon() 是否是守护线程

守护线程这里==后台线程

那么什么是后台线程,什么是前台线程呢?

main线程虽然结束了,但t1、t2仍然在运行,我们可以认为t1、t2是前台线程,也就是说线程的存在能影响进程是否继续存在,这样的线程叫做"前台线程";

像JVM自带的线程就属于"后台线程",也就是说不会影响进程的存在的线程就是"后台线程"

而isDaemon()就是用来判断是不是后台线程的方法

我们可以通过setDaemon来修改线程的属性

复制代码
public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(true){
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
        }
        System.out.println("main 结束");
    }
}

3.isAlive()

是否存活

这里我们要明白,前台线程和后台线程都是有多个的,一个前台线程的结束并不一定会影响是否存活,只有当前台线程全部结束时,进程才会结束

复制代码
public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello t");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
        while(true){
            System.out.println(t.isAlive());
            Thread.sleep(1000);
        }
    }
}

4.isInterrupted()

是否被打断

如何开始一个线程

线程.start()

复制代码
public class Demo9 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
           while(true) {
               System.out.println("hello t");
           }
        });
        t.start();
    }
}

如何中断一个线程?

目前常见的两种方法:1.通过共享的标记来进行统治 2.通过调用isInterrupted()方法来通知

实例一 :使用自定义的变量来作为标志位

复制代码
public class Demo10 {
    private static boolean isFinished=false;
    public static void main(String[] args) throws InterruptedException {
       //boolean isFinished=false;
        Thread t=new Thread(()->{
            while(!isFinished){
                System.out.println("hello,thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread 结束");
        });
               t.start();
               Thread.sleep(3000);
               isFinished=true;
    }
}

实例二:使用Thread对象的isInterrupted()方法来通知线程结束

复制代码
public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //throw new RuntimeException(e);
                    //线程掀桌了
                    break;
                }
            }
            System.out.println("t线程结束");
        });
        t.start();
        Thread.sleep(3000);
        System.out.println("main线程尝试终止线程");
        t.interrupt();
    }
}

如果没有break可以结束吗,理论上是可以的,但实际上这段代码把sleep唤醒了,这种情况下,就把isInterrupted到标志位设置回false了

这样的设定让决定权交给被终止的线程本身

5.等待一个线程--join()

多个线程之间的调度是并发执行的,随机调度的,但这并不是我们想要看到的,join能够要求多个线程之间结束的先后顺序

复制代码
public class Demo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(true){
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        t.join();
        Thread.sleep(2000);
        System.out.println("主线程结束");
    }
}

在主线程中调用t.join就是让主线程等到t线程先结束,也就是说main线程会出现"阻塞等待"

只要t不结束,主线程的join就会一直等待下去,这样的做法并不合理,更加合理的做法应该是给join设置一个时间,这样的话就不会盲目的等待

6.线程调用

public static Thread currentThread()

哪个线程调用这个方法就会返回哪个线程(有点类似this)

7.休眠当前线程

sleep,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间

sleep(0) 意味着让当前的线程立即放弃CPU资源,等待操作系统重新调度

相关推荐
Yue丶越2 小时前
【C语言】深入理解指针(二)
c语言·开发语言·数据结构·算法·排序算法
m0_748248022 小时前
C++中的位运算符:与、或、异或详解
java·c++·算法
介一安全2 小时前
从 0 到 1 玩转 2025 最新 WebGoat 靶场:环境搭建 + 全关卡漏洞解析(超级详细)
java·web安全·网络安全·靶场
web安全工具库2 小时前
Linux进程的:深入理解子进程回收与僵尸进程
java·linux·数据库
沐浴露z2 小时前
详解【限流算法】:令牌桶、漏桶、计算器算法及Java实现
java·算法·限流算法
兜有米啦2 小时前
python练习题3
开发语言·python
chxii2 小时前
Spring Boot 响应给客户端的常见返回类型
java·spring boot·后端
Wzx1980123 小时前
go基础语法练习
开发语言·后端·golang
老友@3 小时前
一次由 PageHelper 分页污染引发的 Bug 排查实录
java·数据库·bug·mybatis·pagehelper·分页污染