线程操作方法join与interrupt,什么是精灵线程?synchronized的使用---线程互斥与临界【线程互斥】【java养成】

Java学习打卡:第二十六天

Java养成计划(打卡第26天)


内容管理

今天继续分享多线程的问题,主要看同步与互斥。

join()

这是Thread类的一个实例方法,当线程调用另外一个线程的join方法就可以让自己暂停运行,直至另外一个线程终止,该方法为当前线程提供了一种等待机制

interrput()

这是Thread中的一个实例方法,程序可以调用interrput方法来将该线程中的中断标志位设置为true,线程代码可以测试标志位的值决定线程处理的流程,获取标志位的方法都在Thread类中

  • static bloolean interrupted() -----这是一个静态的类方法,共功能是返回当前执行线程中的标志位,这里的标志位是boolean型的,⚠ 随即该方法会将该中断标志位设置为false, 这意思为我们如果第二次调用该方法得到的返回值一定为false

  • boolean isInterrupted() ------这是一个实例方法,需要用实例来调用的方法,就是具体的对象 ,该方法同样可以返回标志位,但和上一个方法相比不会改变标志位的值

这里也简单演示一下interrupt的用法

这里的主线程就打印8个随机数,thread线程就在中断标志位不是false的情况下打印一个整数

package ThreadDemo;

public class Interrupt implements Runnable{

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+"线程");
		int num = 0;
		while(!Thread.interrupted()&&num < Integer.MAX_VALUE)
		{
			num++;
		}
		System.out.println("num =" + num);
		System.out.println("exiting");
	}
	
	public static void main(String[] args) {
        System.out.println("Thread-main线程");
		Interrupt  o1 = new Interrupt();
		Thread t = new Thread(o1);//这种方式创建线程也需要创建一个对象
		t.start();
		double r;
		for(int i = 1;i <= 8;i ++)
		{
			r = Math.random();
			System.out.println("i = "+ i + " ;r =" + r);
			if(i == 5)
			{
				t.interrupt();//中断标志位设置为true
			}
		}
		System.out.println("the main thread exit");
	}
}
//这里需要注意的地方就是线程第二中表达方式的用法,也需要创建一个对象

这里的一种运行结构,至于为什么是其中一种就不必多说,具有随机性

Thread-main线程

Thread-0线程
i = 1 ;r =0.046641839987163425
i = 2 ;r =0.04441327381437088
i = 3 ;r =0.6366254673601134
i = 4 ;r =0.9564917873989113
i = 5 ;r =0.04482439984725928
i = 6 ;r =0.9245931023303556
i = 7 ;r =0.9316857777297866
i = 8 ;r =0.5702058022453655
the main thread exit
num =107650
exiting

这种运行结果可以看出在0号线程启动之后在打印语句之后执行的就是mian线程,之后mian线程都执行完了,才轮到0号线程执行,因为这里的num循环了100000多次,耗费时间更大,这里的终止条件就是中断标志位不是false,这里使用的是第一种获取中断标志位的方式,所以会设置为false

这里的中断标志位设置就是mian线程中的interrupt语句,这个语句的作用就是设置一个为true的中断标志位

所有的阻塞方法都可以中断吗

不是,java.io里的read方法在阻止线程时就不会背interrupt方法中断,不会抛出例外,interrupt方法仅仅将中断标志位设置为true

精灵线程与程序终止

精灵线程是什么?在Java中,线程可以分为用户线程(user thread)和精灵线程(daemonthread),精灵线程又称为守护线程,main线程也是用户线程user thread ,使用Thread类的实例方法setDaemon() 可以重新设置一个线程的类型,使用Thread类的实例方法isDaemon可以返回一个线程的类型

  • final void setDaemon(boolean on) //on为true时,,线程设置为精灵线程
  • final boolean isDaemon() //线程为精灵线程,就返回true

这里用上面的Thread-0线程来查看是否为精灵线程

System.out.print(Thread.currentThread().getName()+"线程是");
if(Thread.currentThread().isDaemon())
{
	System.out.println("精灵daemon线程");
}
else
	System.out.println("不是精灵线程,为用户线程");

这里输出的就是thread 0 不是精灵线程,我们创建的就是用户线程,那如何设置为精灵线程?

这里需要注意设置精灵线程必须在线程启动之前,这里就是我们在start之前就要使用set方法来设置线程,否则就会抛出例外

t.setDaemon(true);
t.start();

这样之后就得到

Thread-0线程是精灵daemon线程

这里就疑惑了,我们设置为精灵线程有什么用?

daemon 与 user区别

这里唯一的区别就是,用户线程阻止Java虚拟机的退出,而精灵线程不会阻止, 也就是说,当没有活动的用户线程时,java虚拟机就结束了

那么什么时活动线程,在java中,一个线程的控制流从run中退出(main中对应为main方法),这个线程即告终止,在java中中,一个线程从启动开始到终止结束的这一段时间被称为活动的,不管处于就绪状态还是执行状态,就绪状态也是活动的,调用Thread类中的实例方法isAlive就可以判断是否时活动的

final boolean isAlive() //若线程时活动的就返回true

精灵线程通常看作服务者,即向用户线程提供服务,且在java虚拟机退出之前不需要进行清理操作,例如垃圾清理线程,当所有用户线程都终止了,精灵线程就没有存在的价值了

互斥与临界区

对于线程的控制比如sleep方法让线程沉睡,yiled方法让线程就绪,interruput就将中断标志设置为true,join让当前线程在某个线程执行完之后再执行,但有的线程需要更加严格的协同合作,这时这些方法就不够用了

这里就使用之前博客中的售票窗口分析,但是解决问题说的比较潦草,当时我们正常出票的时候,发现窗口 出了通一张票,这就是CPU切换导致的

这里就涉及一个概念-----临界

临界资源:一次仅允许一个线程使用的资源

临界区: 访问临界资源我的那段代码称为临界区,比如售票的过程

多线程不能同时进入临界区,即它们进入临界区就应该互斥。就是第一个线程执行售票的时候第二个线程就不能再售票了,直至第一个线程完全退出

Java中利用synchronized和锁来实现临界区互斥的要求

  • synchronized修饰实例方法,使该方法称为一个临界区,通常也称为同步方法,我们在run方法里可以直接调用其他的方法 来执行,比如可以把售票的代码给剥离出去成一个方法,直接在run方法体调用
  • 每个对象都有且仅有一把锁。可以称为对象锁,或者对象互斥锁。当没有线程进入对象的synchronized方法,对象锁是打开的
  • 线程要进入对象的synchronized方法,首先要检查对象的锁的状态。若锁是打开,线程就可以进入方法体中并关闭锁,实现临界区,若锁是关闭的,则线程进入锁等待池中,直到线程重新打开
  • 当线程执行完线程的synchronized方法代码退出时,自动打开锁,同时锁等待池的某个线程进入就绪状态

这里所有的开锁,关锁,以及线程的状态转变都是自动完成的,programmer仅仅使用synchronized设置临界区

在这里插入图片描述

该图中椭圆表示的是线程的状态,圆代表对象锁的状态,在时刻t1,线程1由执行非同步代码转而执行同步代码,同时对象锁由打开状态转为关闭状态。这里的非同步代码就是run中没有synchronized修饰的部分,有修饰的地方就是临界区,所以这里就锁等待过程

这里就举个例子,比如

  • 简单的方法就是可以直接使用synchronized就可以
private int remainder = 10;//设置为私有防止其他类的篡改
synchronized void booking(int num);
……
    
public void run()
{
    bt.booking(num);
}

这里就是将run中的代码提取成为一个方法booking,直接使用synchronized修饰,该方法就变成同步方法了,变量权限设置为私有很有必要,防止其他类的方法的改动,失去临界区的意义

  • synchronized也可以直接修饰代码块,加上一个try catch例外处理,成为一个同步语句,这就是之气博客中的处理手段,将卖票的代码给加上同步锁
synchronized(obj)//里面的对象
{
    if(remainder > 0)
    {
        System.out.println(Thread.currentThread().getName() + "窗口");
	}
}

块语句包含着对指定对象属性的访问,线程执行同步语句之前,首先要检验指定对象是否打开状态,若是打开的,就进入执行并关闭锁,否则线程进入锁等待池,直到打开

  • 一个方法可能不是所有的代码而是其中的一小段在访问临界资源,如果将整个方法指定为临界区,就会影响到整个程序的效率,所以第二种方法使用的较多
  • 一个类原不打算使用多线程环境,且采用非同步的方法访问对象属性,而现在需要将属性作为临界资源、实例方法作为临界区,但不希望改变类的定义,这个时候就定义为同步语句

这里比如就使用上面第一种将方法修饰为synchronized就效率低下,可以不在那个地方上🔒,可以在run中加锁

int remainder = 10;//设置为私有防止其他类的篡改
void booking(int num);
……
    
public void run()
{
    synchronized(bt){
         bt.booking(num);//这里只是在这里上锁,也就是只是语句上锁情况
    }
}

Java的互斥锁机制考虑到了Java的例外处理,当synchronized方法或语句方法或语句发生例外突然结束(abstructly complete)时能够自动释放锁

接下来还会深入分析同步问题(生产者producer和消费者consumer)队列,线程池的相关内容~~之后还会直接分享几个线程的题目来运用所学~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值