线程学习(2)线程创建,等待,安全,synchronized(二)

简介: 线程学习(2)线程创建,等待,安全,synchronized(二)

线程学习(2)线程创建,等待,安全,synchronized(一)+https://developer.aliyun.com/article/1413575

5.是否是后台线程

线程可以分为两类,前台线程和后台线程,默认情况下是前台线程。后台线程又叫做"守护线程",就像一场表演的后台工作人员一样,对于后台线程来说,后台线程不结束,不影响整个进程的结束(表演完了,可后台人员还需要处理后事,他们的工作还没结束),而对于前台线程来说,一个Java程序中,如果还有前台进程没有结束,则整个进程是一定不会结束的

获取方法

isDaemon

// 源码规定   默认是前台线程
        /* Whether or not the thread is a daemon thread. */
        private boolean daemon = false;
        Thread t = new Thread("我是线程");
        boolean isDaemon = t.isDaemon();
        System.out.println(isDaemon);// 输出false

代码验证

public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();// 默认是前台线程  持续打印hello thread
    }

对于这个代码来说,主线程中没有要执行的语句,也就是说他的主线程是在一瞬间就执行完了,但是由于t是前台线程,前台线程不结束整个进程就不会结束,如果将t设置为后台线程呢

public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        // 在线程开启前将其设置为后台线程  
        t.setDaemon(true);
        t.start();
    }

可以看到什么也没有打印。因为主线程是前台线程,飞快执行完毕之后没有其他的前台线程,整个进程终止,也就是说t线程没来得及执行,整个进程就结束了。也就是说只要一个进程中的所有前台线程结束,就代表整个进程的结束

验证,先让主线程休眠3s,3s之后主线程会立即结束,尽管t线程内部还有语句没有执行,由于前台线程的结束,导致整个进程结束

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("hello thread");
            }
        });
        // 在线程开启前将其设置为后台线程
        t.setDaemon(true);
        t.start();
        System.out.println("主线程开启");
        Thread.sleep(3000);
    }

6.是否存活

 判断内核线程是否还存活

在Java中我们通过Thread类来创建出一个线程,但实际上Thread类的生命周期要比内核中的线程要长一些,也就是说线程已经不存在了,但是你创建的Thread类仍然存在,使用isAlive判定内核线程是否已经结束

isAlive()

public static void main(String[] args) throws InterruptedException {
        // 创建一个线程
        Thread t = new Thread(() -> {
            System.out.println("线程开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程结束");
        });
        // 开启线程
        t.start();
        System.out.println(t.isAlive());// 输出true
        Thread.sleep(3000);
        System.out.println(t.isAlive());// 输出false
    }

线程t内部的方法我们称之为回调方法,当回调方法执行完毕之后,就代表t这个线程的终止,但是Thread类对象的生命周期并未结束

System.out.println(t.isAlive());// 输出false
        // 开启线程
        t.start();

如果在线程开启之前打印,输出false,因为此时t线程还没有被创建

三.线程的中断

终止/打断  interrupt

在Java中要想销毁/中断一个线程的方法是比较唯一的,

就是想办法让run方法尽快执行完毕

那么如何实现呢?这里提供两种方法

1.手动设置标志位,来作为run方法结束的条件

很多线程之所以会持续很久,是因为run方法内部存在循环,结束run方法就是终止循环

// 将标志位设置为类变量
    private static boolean isQuit = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!isQuit) {
                System.out.println("Thread is working");
            }
        });
        t.start();
        // 五秒后改变标志位
        Thread.sleep(2000);
        isQuit = true;
    }

通过设置标志位,并在主线程中修改标志位,这样就实现了在5秒之后中断此线程的效果

注意:

1.isQuit不能设置为main方法中的局部变量,因为在lambda表达式中使用的变量必须是被final修饰的常量,如果设置为局部变量,就无法再次更改isQuit,导致无法结束循环。

2.将isQuit设置为"类变量",lambda表达式此时访问这个成员就不再是变量捕获了,而是内部类访问外部类这个语法了。此时就没有final的限制

上述方法虽然能够结束run方法,但是过于繁琐,且不优雅,需要人为的手动设置标志位,同时,如果在主线程中我们改变了标志位的值,但是此时线程却在sleep,那就只能等到线程再次苏醒才能终止该线程,所以说通过设置标志位的方法来终止线程还有反应不及时的问题

2.使用Thread内部自带的"标志位"

 其实在Thread类中,有自带的标志位isInterrupted,默认是false

public static void main(String[] args) throws InterruptedException {
        Thread t= new Thread(() -> {
            // 先获取当前Thread的实例  在判断其自带的标志位isInterrupted
            while (!Thread.currentThread().isInterrupted()) {
                while (true) {
                    System.out.println("Thread is working");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        // 此方法就是将自带的标志位isInterrupted设置为true
        t.interrupt();
    }

通过t.interrupt()方法将标志位设置为true来终止线程,这种方法的一个优点是即使线程内部处于"阻塞"状态(sleep),也能够强制将其唤醒,终止run方法,反应更加及时

总结:两种中断线程的方法逻辑都是一样的,即设置合适的标志位,并修改该标志位来终止run方法,从而终止整个线程,但是更加推荐第二种方法

但是上述代码的运行结果是什么呢?请看

异常被抛出且被捕获,但是t线程仍在工作,并没有发生中断,这是为什么呢?通过interrupt方法唤醒线程之后,此时sleep方法会抛出异常,同时自动清除刚才设置的标志位,相当于白白设置标志位了,为什么要这么做呢?是为了让我们有更多的操作空间,在捕获到异常之后,我们可以自由采用以下三种处理方式

try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // 1.方式1  不管不顾  让t线程继续运行
                        e.printStackTrace();
                        // 2.方式2 使用break直接中断进程
                        // 3.方式3  捕获到线程之后处理其他工作的代码
                        // 此处就存放需要解决的其他工作的代码
                    }

四.线程的等待

 一个线程等待另一个线程执行结束,再继续执行。线程等待的本质是控制线程结束的顺序

在Java中使用join来实现线程等待效果

主线程中使用join,就是主线程等待另一个线程结束再继续执行主线程的其余代码

public static void main(String[] args) throws InterruptedException {
        Thread t= new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程工作中!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        System.out.println("线程开启");
        t.join();// 让主线程等待t线程
        System.out.println("线程结束");
    }

t.join()的工作过程

  1. 如果t线程还没有结束,就让主线程等待t线程执行结束,再去执行主线程中剩余的代码,此时主线程就是一个"阻塞"状态
  2. 如果t线程已经结束了,直接返回,不存在"阻塞状态"

在哪个线程中调用join方法就是让哪个线程等待另一个线程

说明:join方法默认是"死等",即如果被等待的线程没结束,就不会执行其余代码,但这种方式存在一个问题,如果被等待的线程是死循环,那其余代码就永远无法执行,在实际的开发中,我么更推荐"有时间"的等待

此处表示主线程只等待1s,1s之后就会去执行主线程中剩余代码

补充:关于调度开销

当我们使用Thread.sleep方法时,我们通过设置一定的时间让线程处于阻塞状态,结束之后再恢复为就绪状态,由阻塞到就绪其时间一定等于sleep的时间么

long beg = System.currentTimeMillis();
        Thread.sleep(1000);// 休眠1s
        long end = System.currentTimeMillis();
        System.out.println("时间:" + (end - beg) + " ms");// 输出1003

可见由阻塞到就绪这部分的时间并不等于sleep的时间,原因在于休眠结束之后,线程并不是立马就变为就绪状态,而是需要通过调度器进行调度,而这种调度是需要时间的,这部分由于调度器调度所产生的时间就叫做调度开销

线程学习(2)线程创建,等待,安全,synchronized(三)+https://developer.aliyun.com/article/1413580

目录
相关文章
|
2月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
58 5
|
2月前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
35 0
|
1月前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
102 6
【Java学习】多线程&JUC万字超详解
|
2月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
81 5
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
20 2
|
2月前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
86 1
|
2月前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
2月前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
2月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
52 1
|
2月前
|
Java C++
【Java 并发秘籍】synchronized vs ReentrantLock:揭秘线程同步神器的对决!
【8月更文挑战第24天】本文详细对比了Java并发编程中`synchronized`关键字与`ReentrantLock`的不同之处。`synchronized`作为内置关键字,提供自动锁管理但不支持中断或公平锁;`ReentrantLock`则通过显式调用方法控制锁,具备更多高级功能如可中断、公平锁及条件变量。文章通过两个计数器类实例展示了两种机制的具体应用,帮助读者理解其差异及适用场景。掌握这两者对于提升多线程程序设计能力至关重要。
41 0