Java的多线程概念

创建线程的两种方式

  • 继承Thread类

定义一个MyThread类继承Thread,并赋予该类一个run方法,线程逻辑是在该方法里执行的。

启动顺序:调用start方法 ---> 等待CPU资源 ---> 转入run方法中执行线程逻辑 ---> 执行完线程就消亡了
class MyThread extends Thread{
	......
	@Override
	public void run(){
		......		//线程逻辑的执行区域
	}
}

MyThread myThread = new MyThread();		//创建线程
myThread.start();		//启动线程
  • 实现Runnable接口

定义一个MyThread2类再implements Runnable,赋予该类一个run方法,线程逻辑是在该方法里执行的。

启动顺序:调用start方法 ---> 接收从Thread传来的参数 ---> 等待CPU资源 ---> 转入run方法中执行线程逻辑 ---> 执行完线程就消亡了
class MyThread2 implements Runnable{
	......
	@Override
	public void run(){
		......
	}
}

MyThread2 myThread2 = new MyThread2();
Thread td = new Thread(myThread2);		//创建线程
td.start();		//启动线程
  • 两种方法的共同点

创建线程都需要new出来一个MyThread的对象

启动线程都需要调用这个对象中的start方法

  • 两种方法的比较
  • 1、Runnable方式可以避免Thread方式由于java单继承特性带来的缺陷

补充:java中一个子类可以实现多个接口但是只能继承一个父类(如下图)

在这里插入图片描述

  • 2、Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理统一资源的情况

卖票实例测试

  • Thread模拟测试:
class MyThread extends Thread{
	private int ticketsCont = 5;		//一共5张火车票
	private String name;		//窗口,即线程名字
	
	public MyThread(String name) {
		this.name = name;
	}
	
	@Override
	public void run() {
		while(ticketsCont > 0) {
			ticketsCont --;		//如果还有票,就卖掉一张
			System.out.println(name+"卖了1张票,剩余票数为:"+ticketsCont);
		}
	}
}

public class TicketsThread {

	public static void main(String[] args) {

		//创建三个线程,模拟三个窗口买票
		MyThread mt1 = new MyThread("窗口1");
		MyThread mt2 = new MyThread("窗口2");
		MyThread mt3 = new MyThread("窗口3");
		
		//启动三个线程,即是窗口,开始买票
		mt1.start();
		mt2.start();
		mt3.start();
		
	}

}
  • 运行结果:
    在这里插入图片描述
  • Thread模拟测试:
class MyThread implements Runnable{
	private int ticketsCont = 5;		//一共5张火车票
	
	@Override
	public void run() {
		while(ticketsCont > 0) {
			ticketsCont --;		//如果还有票,就卖掉一张
			System.out.println(Thread.currentThread().getName()/*获得当前线程的名字*/+
					"卖了1张票,剩余票数为:"+ticketsCont);
		}
	}
}

public class TicketsRuunable {

	public static void main(String[] args) {

		MyThread mt = new MyThread();
		
		//创建三个线程,模拟三个窗口买票
		Thread th1 = new Thread(mt,"窗口1");
		Thread th2 = new Thread(mt,"窗口2");
		Thread th3 = new Thread(mt,"窗口3");

		//启动三个线程,即是窗口,开始买票
		th1.start();
		th2.start();
		th3.start();
	}

}
  • 测试结果:
    在这里插入图片描述

补充:控制台输出的语句顺序,主要看是哪个线程先拿到CPU的资源(如上图)

课外小知识

  1. 调用start()方法是启动线程的正确方式
  2. 停止线程有三种方式:
    一、使线程正常运行完run()方法后,自动终止;
    二、使用stop()方法强行终止,但这个方法是不安全的,已经是废弃掉的方法;
    三、使用interrupt方法中断线程

线程的生命周期

线程生命周期状态转换图
在这里插入图片描述

创建:新建一个线程对象,如:Thread thd = new Thread()

就绪状态:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)

运行状态:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里的逻辑

终止:线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态(注意,stop()方法已经很少使用了)

阻塞状态:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如:调用了sleep()方法

线程的守护神–守护线程

java线程有两类

1、用户线程:运行在前台执行具体的任务(如:程序的主线程、连接网络的子线程等)
2、守护线程:运行在后台,为其他前台线程服务(特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作;应用:数据库连接池中的检测线程、JVM虚拟机启动后的检测线程)

设置守护线程

调用Thread类的setDaemon(true)方法将当前线程设置为守护线程

注意事项

  • setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
  • 在守护线程中产生的新线程也是守护线程
  • 不是所有的任务都可以分配给守护线程来执行,如:读写操作、计算逻辑

thread、notify、join、yield的方法说明

notify():如果一个线程执行了notify方法,那么就会唤醒以锁对象为标识符的线程池中等待线程中其中一个

join():join方法是实现线程同步,可以将原本并行执行的多线程方法变成串行执行

yield():使当前线程从运行状态变为就绪状态。cpu会从众多的就绪状态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。

线程异常处理

测试代码:

class MyThread extends Thread{
    public void run(){
        System.out.println("Throwing in " +"MyThread");
        throw new RuntimeException();
    }
}
class Main {
    public static void main(String[] args){
        MyThread t = new MyThread();
        t.start();
        try{
            Thread.sleep(1000);
        }
        catch (Exception x){
            System.out.println("Caught it" + x);
        }
        System.out.println("Exiting main");
    }
}

测试结果:
在这里插入图片描述

死锁的解决方案

导致死锁的原因

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结束。

死锁产生的四个必要条件

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

当上述四个条件都成立的时候,便形成死锁

解决方案

  1. 死锁预防:破坏导致死锁必要条件中的任意一个就可以预防死锁。例如,要求用户申请资源时一次性申请所需要的全部资源,这就破坏了保持和等待条件;将资源分层,得到上一层资源后,才能够申请下一层资源,它破坏了环路等待条件。预防通常会降低系统的效率
  2. 死锁避免:避免是指进程在每次申请资源时判断这些操作是否安全,例如,使用银行家算法。死锁避免算法的执行会增加系统的开销
  3. 死锁检测:死锁预防和避免都是事前措施,而死锁的检测则是判断系统是否处于死锁状态,如果是,则执行死锁解除策略
  4. 死锁解除:这是与死锁检测结合使用的,它使用的方式就是剥夺。即将某进程所拥有的资源强行收回,分配给其他的进程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值