学习JavaEE的日子 Day29 yield,join,线程的中断,守护线程,线程局部变量共享,线程生命周期

本文详细介绍了Java多线程中的关键概念,包括线程礼让(Thread.yield)、线程合并(join)、线程中断、守护线程的使用以及线程局部变量和生命周期。通过示例展示了如何控制线程行为,如让步、合并线程执行和中断机制。
摘要由CSDN通过智能技术生成

Day29

多线程

12. 线程的礼让

Thread.yield();

理解:此方法为静态方法,此方法写在哪个线程中,哪个线程就礼让

注意:所谓的礼让是指当前线程退出CPU资源,并转到就绪状态,接着再抢

需求:创建两个线程A,B,分别各打印1-100的数字,其中B一个线程,每打印一次,就礼让一次,观察实验结果

sleep,yield都是Thread的方法

public class Test01 {
	public static void main(String[] args) {
		
		A a = new A();
		B b = new B();
		
		a.start();
		b.start();
	}
}
public class A extends Thread{

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println("A:" + i);
		}
	}
}
public class B extends Thread{

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println("B:" + i);
			
			//礼让:让当前线程退出CPU资源,当前线程退出后立刻转入抢资源的状态,可能又会抢到CPU资源
			Thread.yield();
		}
	}
}

13. 线程的合并

t.join(); 合并方法

A.join:在API中的解释是:堵塞当前线程B,直到A执行完毕并死掉,再执行=>A先执行完,再执行B;

需求:主线程和子线程各打印200次,从1开始每次增加1,当主线程打印到10之后,让子线程先打印完再打印主线程

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		
		MyThread t = new MyThread();
		t.start();
		
		for (int i = 1; i <=200; i++) {
			System.out.println("主线程:" + i);
			if(i == 10){
				//让t线程加入到当前线程
				t.join();
			}
		}
		
	}
}
public class MyThread extends Thread{

	@Override
	public void run() {
		for (int i = 1; i <=200; i++) {
			System.out.println("子线程:" + i);
		}
	}
}

14.线程的中断

14.1 线程的中断1

面试题:下列代码的子线程开启后,是否会在3000毫秒就被销毁?
答:不一定,因为3000毫秒后主线程才休眠结束,这时会抢CPU资源
如果立刻抢到,那么子线程就是3000毫秒后销毁
如果没有抢到CPU资源,那么子线程会继续运行,直到主线程抢到CPU资源

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		
		
		MyThread t = new MyThread();//子线程
		t.start();
        
		//主线程休眠3秒(Main方法就是主线程)
		Thread.sleep(3000);
		//过时了
		t.stop();//立刻停止(缺点:可能会导致功能缺失)	
		
	}
	
}
public class MyThread extends Thread{

	@Override
	public void run() {
		while(true){ //死循环
			System.out.println("111");
			System.out.println("222");
			System.out.println("333");
			System.out.println("444");
		}
	}
}
14.2 线程的中断2

另外一种写法:

该方法一定会执行到444结束,不像上面一种立马停止,可能在222或者333结束

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		
		MyThread t = new MyThread();
		t.start();
		
		Thread.sleep(3000);
		
		t.setFlag(false);
		
	}
	
}
public class MyThread extends Thread{

	private boolean flag = true;
	
	public void setFlag(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		while(flag){
			System.out.println("111");
			System.out.println("222");
			System.out.println("333");
			System.out.println("444");
		}
	}
}

14.3 线程的中断3

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		
		MyThread t = new MyThread();
		t.start();
		
		Thread.sleep(3000);
		
		//改变线程状态
		t.interrupt();
	}
	
}

public class MyThread extends Thread{


	@Override
	public void run() {
		
		//获取线程状态(是否消亡)
//		System.out.println(Thread.currentThread().isInterrupted());
		
		while(!Thread.currentThread().isInterrupted()){
			
			System.out.println("111");
			System.out.println("222");
			System.out.println("333");
			System.out.println("444");
		}
	}
}

15.守护线程/后台线程

守护线程 默默守护着前台线程,当所有的前台线程都消亡后,守护线程会自动消亡

注意:垃圾回收器就是守护线程

t.setDaemon(true);

public class Test01 {
	public static void main(String[] args) throws InterruptedException {
		
		MyThread t = new MyThread();
		t.setDaemon(true);//将当前线程设置为守护线程
		t.start();
		
		for (int i = 1; i <= 5; i++) {
			System.out.println("主线程:" + i);
			Thread.sleep(1000);
		}
		
	}
}

public class MyThread extends Thread{

	@Override
	public void run() {
		while(true){
			System.out.println("后台线程默默守护着前台线程");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

十六、线程局部变量(实现线程范围内的共享变量)-- ThreadLocal

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

16.1 共享单个数据
public class Test01 {
	public static final ConcurrentHashMap<Thread, Integer> map = new ConcurrentHashMap<>();
	
	public static void main(String[] args) {
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				int i = 10;
				
				//存数据
				map.put(Thread.currentThread(), i);
				
				A a = new A();
				B b = new B();
				a.println();//10
				b.println();//10
			}
		},"线程1").start();
		
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				int i = 20;
				
				//存数据
				map.put(Thread.currentThread(), i);
				
				A a = new A();
				B b = new B();
				a.println();//20
				b.println();//20
				
			}
		}, "线程2").start();
		
	}
}

public class A {

	public void println(){
		Thread t = Thread.currentThread();
		Integer value = Test01.map.get(t);
		System.out.println(t.getName() + "里的A类对象获取了数据:" + value);
	}
}

public class B {

	public void println(){
		Thread t = Thread.currentThread();
		Integer value = Test01.map.get(t);
		System.out.println(t.getName() + "里的B类对象获取了数据:" + value);
	}
}

16.2 共享多个数据-- ThreadLocal

存数据
local.set(data)底层原理:
1.获取当前线程对象
2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
3.map.put(this,t) * 获取数据

local.get()底层原理:
1.获取当前线程对象
2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
3.map.getEntry(this) -> Entry对象
4.entry.getValue()

public class Test01 {
	/**
	 * 知识点:线程局部变量共享的问题 -- 使用ThreadLocal解决该需求
	 * 
	 * 	ThreadLocal如何去存储数据的:
	 * 		public void set(T value) {
	 * 			//获取当前线程对象
		        Thread t = Thread.currentThread();
		        //通过当前线程对象获取Map
		        ThreadLocalMap map = getMap(t);
		        if (map != null)//如果map不等于null,就直接存储
		        
		            map.set(this, value);//key-当前ThreadLocal的对象,value-需要共享的值
		            
		        else//如果map等于null,就创建map,并存储数据
		            createMap(t, value);
	    	}
	    	
	    	public T get() {
	    		//获取当前线程对象
		        Thread t = Thread.currentThread();
		        //通过当前线程对象获取Map
		        ThreadLocalMap map = getMap(t);
		        if (map != null) {
		        	//获取当前线程中当前ThreadLocal的映射关系对象(Entry<ThreadLocal,E>)
		            ThreadLocalMap.Entry e = map.getEntry(this);
		            if (e != null) {
		                @SuppressWarnings("unchecked")
		                T result = (T)e.value;//获取到共享的数据
		                return result;
		            }
		        }
		        return setInitialValue();
		    }
	 */
	public static void main(String[] args) {
		
		//线程1
		new Thread(new Runnable() {
			@Override
			public void run() {
				//共享的数据
				DataPackage dataPackage = DataPackage.getInstance("用良心做教育", 10);
				
				//获取容器,并存储数据
				ThreadLocal<DataPackage> local = Container.getLocal();
				local.set(dataPackage);
				
				//创建对象
				A a = new A();
				B b = new B();
				
				//获取共享数据
				a.println();
				b.println();
				
			}
		},"线程1").start();
		
		//线程2
		new Thread(new Runnable() {
			@Override
			public void run() {
				//共享的数据
				DataPackage dataPackage = DataPackage.getInstance("做真实的自己", 20);
				dataPackage = DataPackage.getInstance("拼搏到无能为力", 30);
				
				//获取容器,并存储数据
				ThreadLocal<DataPackage> local = Container.getLocal();
				local.set(dataPackage);
				
				//创建对象
				A a = new A();
				B b = new B();
				
				//获取共享数据
				a.println();
				b.println();
			}
		},"线程2").start();
				
	}
}

数据包类

//数据包类
public class DataPackage {

	private String str;
	private int num;
	
    //保证每个线程里只有一个Data包对象
	public static DataPackage getInstance(String str, int num){
		//获取当前线程共享的DataPackage对象
		ThreadLocal<DataPackage> local = Container.getLocal();
		DataPackage dataPackage = local.get();
		
		if(dataPackage == null){
			dataPackage = new DataPackage(str, num);
			local.set(dataPackage);
		}else{
			dataPackage.setStr(str);
			dataPackage.setNum(num);
		}
		return dataPackage;
	}
	
	//有参构造,无参构造,get,set方法省略
	
}


public class A {
	public void println(){
        /**
		 * 获取数据
		 * local.get()底层原理:
		 * 		1.获取当前线程对象
		 * 		2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
		 * 		3.map.getEntry(this) -> Entry对象
		 * 		4.entry.getValue()
		 */
        
		
		//获取容器
		ThreadLocal<DataPackage> local = Container.getLocal();
		
		//通过当前线程获取对应的共享数据
		Thread t = Thread.currentThread();
		DataPackage data = local.get();
		System.out.println(t.getName() + "里的A类对象获取了数据:" + data);
	}


public class B {

	public void println(){
		
		//获取容器
		ThreadLocal<DataPackage> local = Container.getLocal();
		
		//通过当前线程获取对应的共享数据
		Thread t = Thread.currentThread();
		DataPackage data = local.get();
		System.out.println(t.getName() + "里的B类对象获取了数据:" + data);
	}
}

//容器类
public class Container {

	private static final ThreadLocal<DataPackage> local;
	
	static{
		local = new ThreadLocal<>();
	}

	public static ThreadLocal<DataPackage> getLocal() {
		return local;
	}
}

什么是 ThreadLocal?

变量值的共享可以使用public static的形式,所有线程都使用同一个变量,如果想实现每一个线程都有自己的共享变量该如何实现呢?JDK中的ThreadLocal类正是为了解决这样的问题。

ThreadLocal为每个线程提供了一个独立的变量副本,避免了多线程间的同步问题。

ThreadLocal类并不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量。ThreadLocal 是 Java 提供的一种线程局部变量,它为每个使用该变量的线程都提供了一个独立的副本,从而每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。

可以将变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立值,不会出现一个线程读取变量时被另一个线程修改的现象。

内部实现机制:
ThreadLocal内部维护了一个ThreadLocalMap。每个线程通过访问自己的ThreadLocalMap来访问ThreadLocal变量的副本。ThreadLocalMap的键 是ThreadLocal对象本身,值是该ThreadLocal对象所持有的副本数据。(key-当前ThreadLocal的对象,value-需要共享的值)

说说ThreadLocal底层实现?
ThreadLocal底层是通过ThreadLocalMap实现,key -> ThreadLocal; value-> 需要存储的值。

注意事项
内存泄漏问题:由于 ThreadLocalMap 使用的是 ThreadLocal 的弱引用,但值是强引用,如果线程池长时间不释放线程,可能会导致内存泄漏。因此,使用 ThreadLocal 时应在不再需要时调用 remove() 方法清理变量。

适用场景:ThreadLocal 适用于每个线程需要独立的实例或数据的场景,不适用于需要线程间共享数据的场景。

性能问题:对于频繁创建和销毁线程的场景,ThreadLocal 的创建和销毁开销可能较大,因此更适合于线程池等长生命周期的线程管理场景。

17.线程的生命周期

1、新建状态

i. 在程序中创建了一个线程对象后,新的线程对象便处于新建状态,此时,它已经有了内存空间和其它资源,但还处于不可运行状态。新建一个线程对象可采用线程构造方法来实现。

ii. 例如:Thread thread=new Thread();

2、 就绪状态

i. 新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待CPU调用,这表明它已经具备了运行条件。

3、运行状态

i. 当就绪状态的线程被调用并获得CPU资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。

4、 阻塞状态

i. 一个正在执行的线程在某些特殊情况下,如join()、sleep()、wait()等方法,线程都将进入阻塞状态。阻塞时,线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。

5、死亡状态

i. 线程调用stop()方法时或run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

在这里插入图片描述

总结

1.线程的礼让 – yield

2.线程的合并 – join

3.线程的中断

4.守护线程

5.线程局部变量共享 – 重要

简答题

1.什么是多线程,多线程的优劣?

多线程:在一个程序中可以同时运行多个不同的线程来执行不同的任务。

多线程的好处:

可以提高 CPU 的利用率。

多线程的劣势:

线程也是程序,所以线要消耗内存,线程越多占用内存也越多;

多线程需要协调和管理,所以需要 CPU 时间跟踪线程;

线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。

2.创建线程有四种方式:

继承 Thread 类;

实现 Runnable 接口;

实现 Callable 接口;

使用 ThreadPoolExecutors 工具类创建线程池

3.守护线程和用户线程有什么区别呢?

用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

守护线程:运行在后台,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作。main 函数所在的线程就是一个用户线程,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

4.什么是线程局部变量?

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是使用线程局部变量的时候要特别小心,任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

6.Thread类中的yield方法有什么作用?
让当前线程退出CPU资源,并转到就绪状态,接着再抢。
它是一个静态方法而且只保证当前线程放弃CPU而不能保证其它线程一定能占用CPU,执行yield()的线程有可能在进入到就绪状态后马上又被执行。

7.sleep()和wait() 有什么区别?
类的不同:sleep() 是 Thread 类的静态方法,wait() 是 Object 的方法
释放锁:sleep() 不释放锁:当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行就绪状态,等待 CPU 的到来;
wait() 释放锁:必须与 synchronized 关键字一起使用,线程进入阻塞状态,当 notify 或者 notifyall 被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,释放互斥锁。
用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

8.如何停止一个正在运行的线程

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend一样都是过期作废的方法。

3、使用interrupt方法中断线程。

wait(); notify();notifyAll()都是Object类中的方法

sleep,yield 是Thread类中的方法

10.为什么 wait, notify 和 notifyAll 这些方法不在 thread类里面?
一个很明显的原因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。

11.为什么 wait 和 notify 方法要在同步块中调用?
Java API 强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException 异常。还有一个原因是为了避免 wait 和 notify之间产生竞态条件。

12.同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象。同步方法会锁住整个对象,哪怕这个类中有多个不相关的同步块,这会导致他们停止执行并需要等待获得这个对象上的锁。

同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。

13.Java 中你怎样唤醒一个阻塞的线程?

利用 Object 类的 wait()和 notify()方法实现线程阻塞。

首先,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执行;

其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。

14.怎么检测一个线程是否拥有锁?
在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。

15.为什么 Thread 类的 sleep()和 yield ()方法是静态的?
Thread 类的 sleep()和 yield()方法是在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

总结

1.线程的礼让 – yield

2.线程的合并 – join

3.线程的中断

4.守护线程

5.线程局部变量共享 – 重要

6.线程生命周期 — 重要

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A 北枝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值