java单线程基础

java单线程基础文章目录 一 概念运行程序会创建一个进程但 OS 调度的最小单元是线程 轻量级进程 普通的程序包含的线程 二 启动线程和退出线程 1 创建线程的方法 2 启动线程 3 线程中断 interrupt 方法 中断线程的两种写法 try catch 块中包含 while 循环 处理不可中断的阻塞 三 线程的状态

大家好,我是讯享网,很高兴认识大家。



文章目录

  • 一,概念运行程序会创建一个进程但OS调度的最小单元是线程(轻量级进程)普通的程序包含的线程:
  • 二,启动线程和退出线程
    • 1,创建线程的方法
    • 2,启动线程
    • 3、线程中断
      • interrupt方法
      • 中断线程的两种写法
        • try/catch块中包含while循环
      • 处理不可中断的阻塞
  • 三、线程的状态
  • 四、常用方法深入理解
      • 1、run()
      • 2、start()
      • 3、sleep()
      • 4、yield()
      • 5、wait()
      • 6、notify()
      • 7、notiyfAll()

一,概念运行程序会创建一个进程但OS调度的最小单元是线程(轻量级进程)普通的程序包含的线程:

  1. 监听Ctrl-Break // 监听中断信号
  2. 附加监听器// 获取内存转储,线程转储
  3. 信号调度程序// 将信号分给jvm的线程
  4. 终结者// 调用对象的终结者方法
  5. 参考处理程序// 清除参考
  6. 主// 程序的主入口

可以用以下代码打印出以上线程:

 
讯享网 

在这里插入图片描述

从上面可知:即使我们运行一个什么都不做的主方法,也会有以上6个线程,另外关于垃圾回收的GC线程,如果程序一直不用启动垃圾回收机制,那么GC线程是不会启动的。

为什么要用多线程
1.充分利用多处理核心;
2.更快的响应时间(用户订单的场景,发送邮件等部分可由其他线程执行)

二,启动线程和退出线程

1,创建线程的方法

方法一,将类声明为Thread的子类。该子类应重写Thread类的运行方法。接下来可以分配并启动该子类的实例。

讯享网

然后,下列代码会创建并启动一个线程: PrimeThread p = new PrimeThread(143); p.start();

方法二,声明实现Runnable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。

 

然后,下列代码会创建并启动一个线程:

讯享网

每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。
方法三:通过线程池创建

 
 

例如:

 

方法四:通过Future Callable 和FutureTask

 

FutureTask实现RunnableFuture接口,而RunnableFuture继承自Runnable, Future,所以可以把FutureTask传给Thread创建线程,本质上还是方法二。
在这里插入图片描述
在这里插入图片描述
FutureTask继承了Future,可以获取线程的执行结果,所以还可以改成如下:

 

2,启动线程

调用线程的start()方法即可启动线程。

 

线程完成:

  1. run()方法执行完成;
  2. 抛出一个未处理的异常导致线程的提前结束

补充:不要在构造函数启动线程,有发生因为指令重排导致对象逸出的风险,详情自行百度,这里不细说。

3、线程中断

interrupt方法

不安全的取消:
单独使用一个取消标志位.Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁或者数据不一致。

使用线程的中断 :
interrupt() 中断线程,本质是将线程内部的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定。
isInterrupted() java单线程基础 线程检查自己的中断标志位。
静态方法Thread.interrupted() 将中断标志位复位为false。

如果我们不使用线程内部的中断标志位,而是自己起一个变量用于标志中断,那么遇到wait()、sleep()等方法时,我们的标志位就不起作用了,这是不靠谱的做法,如:

 

运行结果:

 

线程一直停留在wait()方法,没有终止。

由上面的中断机制可知Java里是没有抢占式任务,只有协作式任务。当线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。一旦查到中断标志位是中断状态时,立刻中断线程,并对sleep,wait等方法有效。

下面介绍两种中断线程的写法

中断线程的两种写法

先上代码:

 
try/catch块中包含while循环

在TryWhileWhenBlock的cancel方法中设置线程标志变量 并调用了interrupt()方法,将线程设置为中断
,设置中断之后,会停止wait方法并抛出InterruptedException异常,并且抛出异常后,中断标志位会改成false,抛出异常已经退出while循环,程序结束。
运行结果如下:
在这里插入图片描述
把main方法改成如下:

 

在WhileTryWhenBlock的cancel方法中调用了interrupt()方法,将线程的中断标志位设为true,设置中断之后,会停止wait方法并抛出InterruptedException异常,并且抛出异常后,中断标志位会改成false,我们在异常处理的代码块中调用 重新设置中断位为true,重新回到while条件,不满足条件,结束循环。
运行结果如下:
在这里插入图片描述

处理不可中断的阻塞

如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?

覆盖线程的interrupt方法,在处理套接字异常时,再用super.interrupt()自行中断线程。
示例代码:

 

三、线程的状态

新建(NEW): 线程被创建,但是没有调用start方法。
可运行(RUNNABLE): 可运行线程的线程状态,由cpu决定是不是正在运行,有人把正在运行的线程再细分为运行状态(running)
被阻塞(BLOCKING): 受阻塞并且正在等待监视器锁的某一线程的线程状态。
等待(WAITING ): 某一等待线程的线程状态。
计时等待(TIMED_WAITING): 具有指定等待时间的某一等待线程的线程状态。
被终止(TERMINATED ): 已终止线程的线程状态。

在这里插入图片描述

四、常用方法深入理解

1、run()

run就是一个普通的方法,跟其他类的实例方法没有任何区别,只是线程启动后会调用到该方法。

2、start()

启动线程调用的方法,然后Java 虚拟机调用该线程的 run 方法。

结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。

3、sleep()

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

 

输出结果:

 

其中输出 之后停了4秒,再输出下面的内容。这是因为ThreadSleep线程里面把sleep方法放到同步代码块里面了,sleep期间其他线程不能获得同一对象锁,如果把sleep方法放到同步代码块外面,那么对其他线程的影响就会小得多。

4、yield()

当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中,不会释放锁。

5、wait()

调用以前,当前线程必须要持有锁,调用wait方法后,当前线程会处于等待状态,加入对象锁的等待集合中,同时,线程会释放当前持有的锁,其他线程可以在这时获取该锁,其他线程获取到该锁后,wait的线程会进入blocked状态,直到被notify() 方法或 notifyAll() 方法唤醒。

java.lang.Object#wait(long)方法表示等待某一时间内是否有线程对其唤醒,如果超时则自动唤醒。

注意1: 虽然会wait自动解锁,但是对顺序有要求, 如果在notify被调用之后,才开始wait方法 的调用,线程会永远处于WAITING状态。
注意2: 这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出 IllegalMonitorStateException异常。

注意3: 从wait方法返回前即被notify、notifyAll唤醒后,线程必须重新去获取监视器锁,成功获取锁后才能执行。

关于避免假唤醒的问题参考这篇文章:并发编程之 wait()为什么要处于while循环中?

6、notify()

唤醒在此对象监视器上等待的单个线程,如果改对象有多个线程在等待,那么唤醒哪一个完全由cpu决定(谨慎使用)

此方法必须在同步方法或同步块即synchronized上下文中被调用,即当前线程持有监视器锁。

执行方法后,当前线程不会立即释放当前拥有的监视器锁,必须等待此方法的方法或同步块即synchronized上下文执行完,退出同步,当前线程才会释放锁,此时wait状态的线程才可以去竞争获取监视器锁。

7、notiyfAll()

唤醒在此对象监视器上等待的所有线程。(推荐使用)

相关原理:在Java语言中,每个对象都有一个与之关联的监视器,它实际上是对象头(Object Header)中的一部分。当一个线程调用了某个对象的wait方法后,该线程会释放对象的锁并进入等待状态,并将自己加入到该对象的等待队列中。当其他线程调用了该对象的notify方法后,JVM会从等待队列中选择一个线程将其唤醒。

下面是关于wait和notifyAll的实例:

 
 

运行结果

 

注意:

1、wait、notify、notifyAll只能在synchronized关键字中使用,且调用wait、notify、notifyAll的对象与锁对象相同,否则会抛出IllegalMonitorStateException异常。

2、wait() 方法调用后,会破坏原子性。

补充: 调用完notify或者notifyAll后,线程并不会马上从阻塞中退出,抢到锁后才会继续执行。

 

输出结果(相同的输出结果只保留两条,多于的用。。。表示):


                    
小讯
上一篇 2024-12-27 11:20
下一篇 2024-12-28 10:08

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/4435.html