浅谈java多线程的生命周期和同步通信多线程的生命周期和同步通信

举报
运气男孩 发表于 2022/01/18 21:42:23 2022/01/18
【摘要】 关于上一篇的多线程-线程的创建和使用漏掉了一部分,这里补充说明一下Thread类的有关方法(具体查看API) void start(): 启动线程,并执行对象的run()方法 run(): 线程在被调度时执行的操作 String getName(): 返回线程的名称 void setName(String name):设置该线程名称 static Thread currentThread()...

关于上一篇的多线程-线程的创建和使用漏掉了一部分,这里补充说明一下

Thread类的有关方法(具体查看API) 

  • void start(): 启动线程,并执行对象的run()方法 
  • run(): 线程在被调度时执行的操作 
  • String getName(): 返回线程的名称 
  • void setName(String name):设置该线程名称 
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类 
  • static void yield():线程让步  暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程  若队列中没有同优先级的线程,忽略此方法
  • join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止  低优先级的线程也可以获得执行 
  • static void sleep(long millis):(指定时间:毫秒)  令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。  抛出InterruptedException异常 
  • stop(): 强制线程生命期结束,不推荐使用 
  • boolean isAlive():返回boolean,判断线程是否还活着 

线程调度 

接着来说说线程调度,线程有两种调度模型

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程        

获取的 CPU 时间片相对多一些 Java使用的是抢占式调度模型

假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权, 才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

Thread类中设置和获取线程优先级的方法

public final int getPriority():返回此线程的优先级

public final void setPriority(int newPriority):更改此线程的优先级

线程默认优先级是5;线程优先级的范围是:1-10

线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果 

言归正传,下面来说说本篇的重点,线程的生命周期

线程生命周期

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

新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。 

就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 。

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

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

死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。 

用一张图简易表示一下:

这就是经典的线程五态转换,接着继续说一下线程的同步思想

线程的同步思想

问题的提出 

多个线程执行的不确定性引起执行结果的不稳定。

多个线程对数据的共享,会造成操作的不完整性,会破坏数据。 

例如:火车站售票程序,开启三个窗口售票,售10张票。 

class Ticket implements Runnable {  
  private int ticket = 10;    
public void run() {
       while (true) {        
   try {                
Thread.sleep(10);//休息一段时间,让该线程没有执行完                
if (ticket > 0) {                 
System.out.println(Thread.currentThread().getName() + "售出车票,ticket号:" + ticket--);                } else                   
break;          
 } catch (InterruptedException e) {              
  e.printStackTrace();           
}        
}   
}}
public class ThreadSync {
   public static void main(String[] args) {       
Ticket ticket = new Ticket();        
Thread thread1 = new Thread(ticket,"1窗口");
       Thread thread2 = new Thread(ticket,"2窗口");       
Thread thread3 = new Thread(ticket,"3窗口");      
thread1.start();        
thread2.start();        
thread3.start();   }
}

上面的代码会出现,三个线程同时买一张票的问题,会造成操作的不完整性,会破坏数据。 

为什么出现问题?这也是我们判断多线程程序是否会有数据安全问题的标准

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境 怎么实现呢? 把操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可 Java提供了同步代码块的方式来解决 

同步代码块

锁操作共享数据多条语句,可以使用同步代码块实现    

格式:        synchronized(任意对象) {                 多条语句操作共享数据的代码         }         

synchroized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁 同步的好处和弊端    

好处:解决了多线程的数据安全问题

弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率 

同步方法

同步方法:就是把synchronized关键字加到方法上

格式:        修饰符 synchronized 返回值类型 方法名(方法参数) {    }

同步方法的锁对象是什么呢?

this

同步静态方法:就是把synchronized关键字加到静态方法上

格式:        修饰符 static synchronized 返回值类型 方法名(方法参数) {    }

同步静态方法的锁对象是什么呢?

类名.class 

Lock(锁) 

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。 

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。

 ReentrantLock 类实现了Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。  

例如:

class A{    private final ReentrantLock lock = new ReentrantLock();   
 public void method() {	
lock.lock();         
   try {	   
  // 保证线程安全的代码;        
   } catch (Exception e) {           
     e.printStackTrace();         
   }finally {             
   lock.unlock();      
    }      
  }    }}

注意:如果同步代码有异常,要将unlock()写入finally语句块 

synchronized 与 Lock 的对比 

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放 。

Lock只有代码块锁,synchronized有代码块锁和方法锁 。

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)  

优先使用顺序:  Lock —>同步代码块(已经进入了方法体,分配了相应资源)—>同步方法 (在方法体之外)  

最后提一下经典的生产者消费者模式

生产者消费者模式-多线程通信 

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻,所谓生产者消费者问题,实际上主要是包含了两类线程:

  • 一类是生产者线程用于生产数据
  • 一类是消费者线程用于消费数据

为了解生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为 

生产者消费者模式概述 

为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中Object类的等待和唤醒方法: 

注意: 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。  因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明 

好了,关于此次JAVA多线程的生命周期和同步通信就说到这里了,如有不足之处,欢迎指正!

感恩能与大家在华为云遇见!希望能与大家一起在华为云社区共同成长。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。