Java地基 (十三)-----线程(贰)

本文详细介绍了Java线程的分类、优先级、线程组、状态转换以及线程安全的操作方法,包括sleep、join、interrupt。还探讨了线程同步的重要性,讲解了synchronized、wait和notify的使用,以及如何避免死锁。此外,通过实例展示了如何在代码中应用这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 线程的分类

  • 前台线程:我们所使用的线程例如main线程
  • 后台线程:为前台线程服务的

前台转后台

//在启动线程之前,可以将其设置为后台线程,否则默认是前台线程
t.setDaemon(true);

2. 线程的优先级

1最小,10最大,优先级高的获得资源的概率增加

MIN_PRIORITY = 1;

NORM_PRIORITY = 5;

MAX_PRIORITY = 10;

设置和获得线程优先级

//设置优先级
t1.setPriority(Thread.MAX_PRIORITY);
//获得优先级
t1.getPriority()

3. 线程组

位于lang包下的java.lang.ThreadGroup

存在的作用

它可以对一批线程进行管理,对线程组进行操作,同时也会对线程组里面的这一批线程操作。

创建线程组

用户在主线程中创建的线程,属于默认线程组(名字叫"main"的线程组)

ThreadGroup group = new ThreadGroup("我的线程组")
//指定线程所属的线程组
Thread t = new Thread(group,"t线程");

4. 线程的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RfcuJ1iF-1623567810047)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210609192624390.png)]

注意,其实 BLOCKED,WAITING,TIMED_WAITING 这三种都属于线程阻塞,只是触发的条件不同,以及从阻塞状态中恢复过来的条件也不同而已。

线程在这三种情况的阻塞下,都具备相同的特点:
线程不执行代码
线程也不参与CPU时间片的争夺

获取状态方法:

t1.getState();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccpgfZJ7-1623567726945)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210609192659887.png)]

一个线程从创建到启动、到运行、到死亡,以及期间可能出现的情况都在上图中进行了描述。

5. 线程的安全以及操作方法

5.1 方法一:sleep

位于Thread中静态方法,让当前线程睡眠

//当前线程休眠10毫秒
Thread.sleep(10);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hfYd9CM3-1623567726947)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210609192945420.png)]

5.2 方法二:join

位于Thread中非静态方法,等待另一个指定的线程运行结束后,当前线程才可以继续运行 。

无参join方法:

Thread t2 = new Thread("t2线程"){
	@Override
	public void run() {
		try {
			//t2线程调用t1.join方法
			//t2线程进入阻塞状态
			//t2线程要等到t1线程运行结束,才能恢复到RUNNABLE状态
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} 
        System.out.println("t2线程结束");
	}
};

5.3 含参方法:表示阻塞多久唤醒

Thread t2 = new Thread("t2线程"){
	@Override
	public void run() {
		try {
			//t2线程调用t1.join方法
			//t2线程进入阻塞状态
			//t2线程要等到t1线程运行结束,才能恢复到RUNNABLE状态
			//2000表示,当前线程t2最多阻塞2秒钟,2秒钟之内t1线程没有结束,那么t2线程就自动恢复
			t1.join(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} 
        System.out.println("t2线程结束");
	}
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCVKc7Kl-1623567726950)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210609193523743.png)]

线程A中,调用了线程B对象的join方法,那么线程A就会进入到阻塞状态,这种阻塞有俩种情况,一种是调用了无参的join方法,那么此时线程A的状态为WAITING(无限期等待),

另一种是调用了有参的join方法,那么此时线程A的状态为TIMED_WAITING(有限期等待)线程A如果调用了sleep方法,那么线程A也会进入阻塞状态,此时线程A的状态为TIMED_WAITING

5.3 方法三:interrupt

线程类Thread中的 interrupt 方法 ,打断阻塞的状态,恢复就绪状态

public class Test {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1线程"){
			@Override
			public void run() {
				try {
					//t1线程休眠100秒
					Thread.sleep(100000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} 
                System.out.println("t1线程结束");
			}
		};
		t1.start();
		//让主线程休眠500毫秒,目的是为了给t1时间,让它调用sleep方法而进入阻塞状态
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        //打断t1由于调用sleep方法而进入的阻塞状态
		t1.interrupt();
	}
} 
//运行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.briup.sync.Test$1.run(Test.java:11)
t1线程结束

可以看出,本来t1线程调用了sleep方法进入了阻塞状态,需要100后才会恢复的,但是我们在主线程中调用了t1线程对象的打断方法 interrupt ,那么此时 Thread.sleep(100000); 这句代码就会抛出被打断的异常,同时t1线程从阻塞状态恢复到RUNNABLE状态,继续执行代码,输出了t1线程结束

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dSygB3T-1623567726951)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210609194020374.png)]

interrupt()调用本地方法 interrupt0() 改变一个标识flag的值

isInterrupted():获得返回这个“打断标识”值,并且不会对这个值进行清除(true->false)

interrupted() :内部调用isinterrupted()方法,对状态值进行擦除。

public class Test {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1线程"){
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					//判断是否有其他线程调用了自己的interrupt方法
					//调用类中的静态方法:interrupted
					System.out.println(Thread.interrupted());
				} 
                System.out.println("t1线程结束");
			}
		};
		t1.start();
    	t1.interrupt();
	}
} 
//运行结果:
true
false
false
false
false
false
false
false
false
false
t1线程结束

三个尚未理解的:

interrupt:是进行打断操作,将flag值改为true,之后该线程时刻检测该值是否变为true,如果变为true,则抛出异常,结束阻塞,改flag值为false。如果该线程为非阻塞状态,也可以使用,只是无效果。

查看标识值得俩个方法

interrupted:当由interrupt将flag修改为true后,而程序又不处在阻塞状态,获取该标识值为true,之后进行清除,将flag值改为false

isinterrupted:同interrupted,只是不会对flag进行擦除。

源码进行区分:

public class Thread{
	public void interrupt() {
	//...
	interrupt0(); // Just to set the interrupt flag
	} 
    private native void interrupt0();
	public boolean isInterrupted() {
		return isInterrupted(false);
	} 
    public static boolean interrupted() {
		return currentThread().isInterrupted(true);
	}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
	private native boolean isInterrupted(boolean ClearInterrupted);
	public static native Thread currentThread();
}

6. 关于锁:线程同步以及线程安全

6.1 锁存在的必要性

对于共享变量得访问,不同的线程之间会产生线程安全的问题

因此有了锁的概念 synchronized

分析

  • 线程同步的效果,就是一段加锁的代码,每次只能有一个拿到锁的线程,才有资格去执行,没有拿到的锁的线程,只能等拿到锁的线程把代码执行完,再把锁给释放了,它才能去拿这个锁然后再运行代码。
  • 这样以来,本来这段代码是俩线程并发访问,“争先恐后”的去执行的,现在线程同步之后,这段代码就变成了先由一个拿到锁的线程去执行,执行完了,再由另一个线程拿到锁去执行。
  • 相当于是大家每个线程不要抢,排好队一个一个去执行,那么这时候共享的变量的值,肯定不会出现线程安全问题!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJ1TnMc3-1623567726953)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210611101734351.png)]

6.2 对于synchronized

synchronized 关键字修饰非静态方法,默认使用 this 当做锁对象,并且不能自己另外指定
synchronized 关键字修饰静态方法,默认使用 当前类的Class对象 当做锁对象,并且不能自己另外指

这俩中情况的同步效果是一样的,只是锁对象不同而已

synchronized (obj){
	try {
		Thread.sleep(100000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

6.3 wait和notify

  • 前因后果:对于被加锁的时候我们使用Sleep进入睡眠也不会释放锁,因此产生了wait进入等待并且释放锁,而且进入wait状态不会自己唤醒需要notify(随机唤醒等待池的一个)去唤醒,任何对象都有三个方法。还有一个是notifyAll(唤醒全部等待池的)

  • 注意:wait:必须写在锁的代码块当中

最终状态图:

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OVXCtbkA-1623567726954)(C:\Users\86132\AppData\Roaming\Typora\typora-user-images\image-20210611102851930.png)](https://img-blog.csdnimg.cn/20210613150611659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0MjAxNTYx,size_16,color_FFFFFF,t_70)]

6.4 死锁

俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放,俩个线程就这样一直僵持下去。

如何避免死锁

  1. 避免多次锁定。尽量避免同一个线程对多个 Lock 进行锁定。
  2. 具有相同的加锁顺序。如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。
  3. 使用定时锁。程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。
  4. 死锁检测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值