线程是什么?多线程的实现,生命周期,安全问题及线程的通信

一、程序、进程、线程是什么?

  • 程序(program):为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
  • 程序(program):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
    • 如:运行中的QQ,运行中的MP3播放器
    • 程序是静态的,进程是动态的
  • 线程(thread):进程可进一 步细化为线程,是一个程序内部的一条执行路径
    • 若一个程序可同一时间执行多个线程,就是支持多线程的

二、线程调度

(一)线程的调度方式

线程调度分以下两种

  • 时间片(你执行一会儿,我执行一会儿)
    在这里插入图片描述
  • 抢占式高优先级的线程抢占CPU(谁抢到就是谁用)

注:

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

(二)线程的优先级控制

注:优先级的高低只能说明抢到CPU执行权的概率变大,而不是总是先执行

MIN_ PRIORITY(1) ;
NORM_ PRIORITY(5) ;(默认优先级)
MAX_ PRIORITY (10) ;

  • getPriority() :返回线程优先值
  • setPriority(int newPriority):改变线程的优先级
  • 线程创建时继承父线程的优先级

(三)使用线程时常用方法

  1. start():启动线程并执行相应的run( )方法
  2. run():子线程要执行的代码放入run( )方法中
  3. currentThread():静态的,调取当前的线程
  4. getName( ) :获取此线程的名字
  5. setName( ) :设置此线程的名字
  6. yield() :调用此方法的线程释放当前CPU的执行权
  7. join():在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行
  8. isAlive():判断当前线程是否还存活
  9. sleep(long l) :显式的让当前线程睡眠l毫秒
  10. 线程通信: wait()、notify()、notifyAll()。这三个方法是Object的方法
  11. getPriority() :返回线程优先值
  12. setPriority(int newPriority):改变线程的优先级

二、实现线程的两种方式

实现线程的两种方式:继承Thread类、实现Runnable接口

(一)通过继承Thread类实现线程

 //1. 创建一个继承于Thread的子类
class myThread extends Thread {
   
   // 2.重写Thread类中的run()方法,方法内实现此子线程要完成的功能
   @Override
   public void run() {
   	for (int i = 0; i < 20; i++) {
   		System.out.println(Thread.currentThread().getName() + ":" + i);
   	}
   }
}


   public static void main(String[] args) {
//		3.创建子类对象
   	Thread thread = new myThread();

//		4.调用线程的start():启动此线程;调用相应的run( )方法,一个线程只能够执行一次start(),
   	thread.start();

//		注:不能通过Thread实现类对象的run()去启动一个线程!
   	thread.run();

   	for (int i = 0; i < 20; i++) {
   		System.out.println(Thread.currentThread().getName() + ":" + i);
   	}
   }

(二)通过实现Runnable接口实现线程的例子

//1.创建一个实现了Runnable接口的类
class PrintNum1 implements Runnable {
	//实现接口的抽样方法
	public void run() {
	//子线程执行的代码
	for(inti=1;i<=100;i++){
		if(i%2==0){
		System. out. println( Thread. current Thread(). getName() + ":" + i);
		}
	}
}

public class TestThread1
	public static void main(String[] args) {
	//3.创建一个Runnable接口实现类的对象
	PrintNum1 p = new PrintNum1();
	//要想启动一个多线程,必须调用start()
	//4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
	Thread t1 = new Thread(p) ;
	//5.调用start()方法:启动线程并执行run()
	t1.start();//启动线程;执行Thread对象生成时构造器形参的对象的run()方法。
	//再创建一个线程,只需用同一个对象即可
	Thread t2 = new Thread(p);
	t2.start();
}

(三)对比一下继承的方式vs实现的方式

  1. 联系: Thread implements Runnable
  2. 哪个方式好?实现的方式优于继承的方式
    why?① 避免了java单继承的局限性
    ②如果多个线程要操作同一份资源(或数据),更适合使用实现的方式,共享数据所在的类可以作为Runnable接口的实现类。

三、线程的生命周期

JDK中用Thread.State枚举表示了线程的几种状态,要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

  • 新建(New):当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态, 例如new Thread(r)

  • 就绪(Runnable):处于新建状态的线程被start()后, 将进入线程队列等待CPU时间片,此时它已具备了运行的条件

  • 运行(Running):当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能

  • 阻塞(Blocked):在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU 并临时中止自己的执行,进入阻塞状态;

    • 堵塞的情况分三种:

    1)、等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。

    2)、同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。

    3)、其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。

  • 死亡(Dead):有两个原因会导致线程死亡:

    1. run方法正常退出而自然死亡,
    2. 一个未捕获的异常终止了run方法而使线程猝死。

在这里插入图片描述

四、线程安全

1.线程安全问题存在的原因?

由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题。

2.如何来解决线程的安全问题?

必须让一个线程操作共享数据完毕以后,其它线程才有机会参与共享数据的操作。

3. java如何实现线程的安全?

方式一:同步代码块

synchronized(同步监视器) {
	//需要被同步的代码块(即为操作共享数据的代码)
)}

注:

  1. 共享数据:多个线程共同操作的同一一个数据(变量)
  2. 同步监视器(俗称:锁):由任意一个类(要求是唯一的对象)的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。
    要求:所有的线程必须共用同一把锁!
  3. 在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this

方式二:同步方法

将操作共享数据的方法声明为synchronized即此方法为同步方法
比如: public synchronized void show(){ //操作共享数据的代码}

注:

  1. 同步方法的锁就是当前对象
  2. 对于非静态的方法而言.使用同步的话,默认锁为: this。如果使用在继承的方式实现多线程的话,慎用!
  3. 对于静态的方法,如果使用同步.默认的锁为:当前类本身。以单例的懒汉式为例。synchronized (Singleton.class){}

线程的同步的弊端:由于同个时间只能有一个线程访问共享数据,效率变低了

4. 释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束(正常执行
  • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中执行了线程,对象的wait()方法,当前线程暂停,并释放锁。

5:不会释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()Thread.yield() 方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的suspend() 方法将该线程挂起,该线程不会释放锁(同步监视器)
    ➢应尽量避免使用 suspend()和resume() 来控制线程

6:线程死锁问题

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

解决方法:

  • 专门的算法、原则
  • 尽量减少同步资源的定义

7:线程的通信

wait()与notify()和notifyAll()介绍

  • wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

  1. 使用要注意要用锁对象去使用 ,锁.wait()
  2. Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.langlllegalMonitorStateException异常

例子:

/*
*生产者/消费者问题
*生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
*店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一 下,
*如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一 下,
*本如果店中有产品了再通知消费者来取走产品。
*/

//店员
class Clerk {
	int product;

	public synchronized void addProduct() {// 生产产 品
		if (product >= 20) {
			System.out.println("产品已满,停止生产产品");
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			System.out.println(Thread.currentThread().getName()+"已生产,剩余:" + ++product);
			notify();
		}
	}

	public synchronized void consumeProduct() {// 消费产品
		if (product == 0) {
			System.out.println("产品不足,正在等待生产者生产产品。。。");
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			System.out.println(Thread.currentThread().getName()+"已消费,剩余:" + --product);
			notify();
		}
	}
}

//生产者
class Producer implements Runnable {
	Clerk clerk;

	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		while (true) {
			System.out.println(Thread.currentThread().getName()+"开始生产");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	}
}

//消费者
class Customer implements Runnable {

	Clerk clerk;

	public Customer(Clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		while (true) {
			try {
				Thread.currentThread().sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"开始消费");

			clerk.consumeProduct();
		}
	}
}

public class AccountDemo {

	public static void main(String[] args) {

		Clerk clerk = new Clerk();
		Producer p1 = new Producer(clerk);
		Producer p2 = new Producer(clerk);
		Customer c1 = new Customer(clerk);
		
		Thread tp1 = new Thread(p1);
		Thread tp2 =new Thread(p2);
		Thread tc1 =new Thread(c1);
		
		tp1.setName("生产者1号");
		tp2.setName("生产者2号");
		tc1.setName("消费者1号");
		
		tp1.start();
		tp2.start();
		tc1.start();

	}
}

运行结果:

生产者1号开始生产
生产者2号开始生产
生产者2号已生产,剩余:1
生产者2号开始生产
生产者1号已生产,剩余:2
生产者1号开始生产
生产者2号已生产,剩余:3
生产者2号开始生产
生产者1号已生产,剩余:4
生产者1号开始生产
消费者1号开始消费
消费者1号已消费,剩余:3
生产者2号已生产,剩余:4
生产者2号开始生产
生产者1号已生产,剩余:5
生产者1号开始生产
生产者2号已生产,剩余:6
生产者2号开始生产
生产者1号已生产,剩余:7
生产者1号开始生产
生产者1号已生产,剩余:8
生产者1号开始生产
生产者2号已生产,剩余:9
生产者2号开始生产
消费者1号开始消费
消费者1号已消费,剩余:8
生产者2号已生产,剩余:9
生产者2号开始生产
生产者1号已生产,剩余:10
生产者1号开始生产
生产者2号已生产,剩余:11
生产者2号开始生产
生产者1号已生产,剩余:12
生产者1号开始生产
生产者2号已生产,剩余:13
生产者2号开始生产
生产者1号已生产,剩余:14
生产者1号开始生产
消费者1号开始消费
消费者1号已消费,剩余:13
生产者1号已生产,剩余:14
生产者1号开始生产
生产者2号已生产,剩余:15
生产者2号开始生产
生产者1号已生产,剩余:16
生产者1号开始生产
生产者2号已生产,剩余:17
生产者2号开始生产
生产者1号已生产,剩余:18
生产者1号开始生产
生产者2号已生产,剩余:19
生产者2号开始生产
消费者1号开始消费
消费者1号已消费,剩余:18
生产者1号已生产,剩余:19
生产者1号开始生产
生产者2号已生产,剩余:20
生产者2号开始生产
产品已满,停止生产产品
产品已满,停止生产产品
消费者1号开始消费
消费者1号已消费,剩余:19
生产者1号开始生产
生产者1号已生产,剩余:20
生产者1号开始生产
生产者2号开始生产
产品已满,停止生产产品
产品已满,停止生产产品
消费者1号开始消费
消费者1号已消费,剩余:19
生产者2号开始生产
生产者2号已生产,剩余:20
生产者2号开始生产
生产者1号开始生产
产品已满,停止生产产品
产品已满,停止生产产品
消费者1号开始消费
消费者1号已消费,剩余:19
生产者2号开始生产

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值