首页 > 技术文章 > 线程介绍,常用方法,生命周期,线程守护,线程同步机制,互斥锁,线程死锁,线程池的相关概念

herebug 2021-07-31 17:58 原文

一、引入概念

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

进程:

1、进程是指运行中的程序

2、进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身产生,存在和消亡的过程。

线程:

1.线程由进程创建的,是进程的一个实体

2.一个进程可以拥有多个线程,

单线程和多线程:

单线程:同一个时刻,只允许执行一个线程

多线程:同一时刻,可以执行多个线程

并发和并行:

 

 

 二、线程的继承实现图;(自定义线程可以继承Thread也可以直接实现Rubable)

(1)继承Thread实现一个简单的线程

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

        cat cat = new cat();
      cat.run();//run只是在cat中的一个方法,当调用时,并不会重新开启一个线程。会在main中按照顺序执行
      cat.start();//启动线程,启动之后run方法开始运行,并不会对main方法造成任何影响。
} }
class cat extends Thread{ @Override public void run() { int runtime = 0; while (runtime<8) { try { System.out.println("喵喵,我是小猫咪!"); runtime++; Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }

线程的结构

 

可以在程序运行时(进程开始的时候:在终端利用Jconsole查看当前进程中的线程)

 

 

 注意:

在进程中,如果主线程停止了,子线程还没结束,并不会结束进程。

(2)实现一个Runnabled的接口,实现一个多线程的类

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

        dog dog = new dog();
        //dog.start();
        
         Thread thread = new Thread(dog);
         thread.start();
    }
}
class dog implements  Runnable{

    @Override
    public void run() {

        int timesize = 0;

        while (timesize<10) {
            try {
                System.out.println("hi! here is a dog!!!");
                timesize++;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:由于没有继承Thread类,所以在main中无法调用start方法启动线程,Runnable中只有一个run方法,所以需要重新定义一个新的线程,再将实现类dog放进去;

(3)线程的的基本使用

<1>线程如何理解:

 

 

 

 

(4)线程终止

基本说明:

1.当线程完成任务后,会自动退出。
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

(利用一个loop来通知线程结束)

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

        dog dog = new dog();
        //dog.start();

         Thread thread = new Thread(dog);
         thread.start();

         Thread.sleep(10*1000);

         dog.setLoop(false);
    }
}
class dog implements  Runnable{

    private boolean loop = true;

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {

        int timesize = 0;


        while (loop) {
            try {
                System.out.println("hi! here is a dog!!!");
                timesize++;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(5)线程常用方法

1).setName //设置线程名称,使之与参数name相同

2).getName//返回该线程的名称
3). start//使该线程开始执行;Java虚拟机底层调用该线程的startO方法

4). run 1/调用线程对象run 方法;
5).setPriority //更改线程的优先级

6). getPriority //获取线程的优先级
7).sleep1/在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8). interrupt 1/中断线程

9)yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功

10).join: 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

 代码实例:

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

        T t = new T();
        t.start();//启动线程
        for (int i = 0; i <10; i++) {//进行主线程的代码运行
            Thread.sleep(1000);
            System.out.println("here is main!!"+i);
            if (i == 3){
                System.out.println("开始插队!!");
                t.join();//让T插队,插队之后t线程将运行完毕退出再进入主程序
                System.out.println("插队完成!!");
            }
        }

    }
}
class T extends Thread{
    @Override
    public void run() {

        int i = 0;
        while (i<10){
            try {
            for (int j = 0; j < 10; j++) {
                Thread.sleep(1000);
                System.out.println("hello test let`s go!!"+ j);
                i++;
            }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 11)用户线程和守护线程

1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

3.常见的守护线程:垃圾回收机制

public class ThreadMothed01 {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        Thread thread = new Thread(t1);
        thread.setDaemon(true);//设置线程为守护线程
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("test deamon!!!");
            Thread.sleep(1000);
        }
    }
}

class T1 implements Runnable{
    @Override
    public void run() {

        int i = 0;
//        while (i < 10) {
        while (true) {//测试守护线程
            for (int j = 0; j < 10; j++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("test!!!" + j);
                i++;
            }
        }
    }
}

 

二、线程的生命周期

1、在JDK中Thread.State枚举表示了线程的几种状态

 

 

线程状态图

 

 三、线程同步机制(Synchronized)

线程同步机制:

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

同步具体方法:

 

 四、互斥锁

基本介绍:

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

6.同步方法(静态的)的锁为当前类本身。

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

//        Tickets tickets = new Tickets();
//        Tickets tickets1 = new Tickets();
//        Tickets tickets2 = new Tickets();
//        tickets.start();

        

        //定义三个新线程,开始进行线程同步测试,线程处理的对象需要是同一个对象
        //不然不会体现出线程同步,线程锁的作用
        Tickets tickets = new Tickets();
         new Thread(tickets).start();
         new Thread(tickets).start();
         new Thread(tickets).start();

    }
}
class Tickets extends Thread{
//class Tickets implements Runnable{
    private int ticket = 100;
    private boolean loop = true;
    @Override
    public  void run() {
        while (loop){
            loop=sell();
        }
    }
    private  synchronized boolean sell(){
        if (ticket<=0){
            System.out.println("票已全部售出。。。");
            return false;
        }
        if (ticket>0){
            System.out.println(Thread.currentThread().getName() + "-售出一张票-剩余的票数"+(--ticket));
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return  true;
    }
}

 

注意事项和细节:

1.同步方法如果没有使用static修饰:默认锁对象为this

2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
需要先分析上锁的代码

选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!

 五、线程死锁

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

(1)使用继承方式Thread 体现死锁

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

        Lock lock = new Lock(true);//传参true获取o1对象
        lock.setName("true");//设置线程名称
        Lock lock1 = new Lock(false);//传参true获取o2对象
        lock1.setName("false");
        //开始新的进程
        lock.start();
        lock1.start();
    }
}

class Lock extends Thread {
    static Object o1 = new Object();
    static Object o2 = new Object();
    private boolean loop;

    public Lock(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        if (loop){
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"进入正确的o1,等待o2");
                synchronized (o2){
                    System.out.println("拿到o2,o1执行完成");
                }
            }
        }else{
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"进入错误的o1,等待o2");
                synchronized (o1){
                    System.out.println("拿到o2,o1执行完成");
                }
            }
        }

    }
}

使用实现Runnable 的方式体现死锁

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

        Lock lock = new Lock(true);
        Thread thread = new Thread(lock);
        thread.setName("A");
        Lock lock1 = new Lock(false);
        Thread thread1 = new Thread(lock1);
        thread1.setName("B");
        thread.start();
        thread1.start();

    }
}

class Lock implements Runnable {
    static Object o1 = new Object();
    static Object o2 = new Object();
    private boolean loop;

    public Lock(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        if (loop){
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"进入正确的o1,等待o2");
                synchronized (o2){
                    System.out.println("拿到o2,o1执行完成");
                }
            }
        }else{
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"进入错误的o1,等待o2");
                synchronized (o1){
                    System.out.println("拿到o2,o1执行完成");
                }
            }
        }

    }
}

六、释放锁

释放锁的情况:

1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return.
案例:没有正常的完事,经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释
放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

不会释放锁的情况:

1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方
法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,
该线程不会释放锁。
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

 七、线程池

1. 线程池的概念:

          线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

2. 线程池的工作机制

         2.1 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。

         2.1 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

3. 使用线程池的原因:

        多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。

在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor

而我们创建时,一般使用它的子类:

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

 

这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:

 

 

 

由图中,我们可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了知乎,拒绝执行某些任务。

线程池的执行流程又是怎样的呢?

 

有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

handler的拒绝策略:

有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

第二种DisCardPolicy:不执行新任务,也不抛出异常

第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

第四种CallerRunsPolicy:直接调用execute来执行当前任务

四种常见的线程池:

CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
线程池原文链接:https://blog.csdn.net/weixin_40271838/article/details/79998327

(收藏中有四种线程和自定义线程的博客)

对四种创建线程池和自定义线程池的测试代码:

package com.zjl.test.threaduse;

import org.junit.jupiter.api.Test;

import java.util.concurrent.*;

public class ThreadPoolTest {
    //            Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,
//            就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
    //线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程
    @Test
    public void ThreadPool1() throws Exception {
        //创建一个可缓存线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            Thread.sleep(500);
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    //输出当前的执行线程的名称
                    System.out.println("当前运行的线程名称为:" + Thread.currentThread().getName());
                }
            });
        }
    }
//测试结果:

//当前运行的线程名称为:pool-1-thread-1
//当前运行的线程名称为:pool-1-thread-1
//当前运行的线程名称为:pool-1-thread-1

// Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
    @Test
    public void ThreadPool2() throws InterruptedException {
        //创建一各可重用个数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            Thread.sleep(500);
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    //输出当前正在执行的线程的名称
                    System.out.println("当前执行的进程是:" + Thread.currentThread().getName());
                }
            });
        }
    }
//测试结果:

当前执行的进程是:pool-1-thread-1
当前执行的进程是:pool-1-thread-2
当前执行的进程是:pool-1-thread-3
当前执行的进程是:pool-1-thread-4
当前执行的进程是:pool-1-thread-5
当前执行的进程是:pool-1-thread-1
当前执行的进程是:pool-1-thread-2
当前执行的进程是:pool-1-thread-3

//定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
    // Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
    @Test
    public void ThreadPool3() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

        while (true) {
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("当前schedule线程执行:" + Thread.currentThread().getName());
                }
            }, 1, TimeUnit.SECONDS);//表示当前线程的执行为,延迟一秒后执行

            Thread.sleep(1000);


            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("当前scheduleAtFixedRate线程执行:" + Thread.currentThread().getName());
                }
            }, 1, 2, TimeUnit.SECONDS);//表示当前进程是在开始时间延时一秒后开始,然后每隔2秒执行一次
        }
    }
//测试结果

当前schedule线程执行:pool-1-thread-1
当前schedule线程执行:pool-1-thread-2
当前scheduleAtFixedRate线程执行:pool-1-thread-1
当前scheduleAtFixedRate线程执行:pool-1-thread-3
当前schedule线程执行:pool-1-thread-2
当前scheduleAtFixedRate线程执行:pool-1-thread-1
当前scheduleAtFixedRate线程执行:pool-1-thread-4
当前schedule线程执行:pool-1-thread-5
当前scheduleAtFixedRate线程执行:pool-1-thread-3
当前scheduleAtFixedRate线程执行:pool-1-thread-2
当前schedule线程执行:pool-1-thread-1

//Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
    // 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    @Test
    public void ThreadPool4() throws InterruptedException {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            Thread.sleep(500);
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("当前线程:"+Thread.currentThread().getName()+"执行的是"+index);
                }
            });
        }
    }
//测试结果:

当前线程:pool-1-thread-1执行的是0
当前线程:pool-1-thread-1执行的是1
当前线程:pool-1-thread-1执行的是2
当前线程:pool-1-thread-1执行的是3
当前线程:pool-1-thread-1执行的是4
当前线程:pool-1-thread-1执行的是5
当前线程:pool-1-thread-1执行的是6
当前线程:pool-1-thread-1执行的是7

//自定义线程池测试
    @Test
    public void ThreadPool5(){
        //定义一个数组型缓冲队列(在执行任务之前用于保存任务的队列。 这个队列只会保存execute方法提交的Runnable任务。 )
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<Runnable>(10);
        //自定义一个缓冲池,将数组型缓冲队列放入其中
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 10, TimeUnit.SECONDS, arrayBlockingQueue);
        //定义新的线程
        TestThreadpool5 t1 = new TestThreadpool5();
        TestThreadpool5 t2 = new TestThreadpool5();
        TestThreadpool5 t3 = new TestThreadpool5();
        TestThreadpool5 t4 = new TestThreadpool5();
        TestThreadpool5 t5 = new TestThreadpool5();
        //执行线程(如果要缓冲队列保存任务的队列,必须使用execute提交线程任务)
        threadPoolExecutor.execute(t1);
        threadPoolExecutor.execute(t2);
        threadPoolExecutor.execute(t3);
        threadPoolExecutor.execute(t4);
        threadPoolExecutor.execute(t5);
        //关闭线程池
        threadPoolExecutor.shutdown();
    }


}
//测试结果:

当前自定义线程池线程的名称:pool-1-thread-1
当前自定义线程池线程的名称:pool-1-thread-2
当前自定义线程池线程的名称:pool-1-thread-2
当前自定义线程池线程的名称:pool-1-thread-3
当前自定义线程池线程的名称:pool-1-thread-1

class TestThreadpool5 implements Runnable {
    @Override
    public void run() {
        System.out.println("当前自定义线程池线程的名称:" + Thread.currentThread().getName());
    }
}

 

推荐阅读