zoukankan      html  css  js  c++  java
  • java 笔记(5) —— 线程,yield,join

    一、线程各个状态与转换:

    新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
    就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权 
    运行状态:处于这个状态的线程占用CPU,执行程序的代码 
    阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。 

     

    阻塞状态的三种情况

    1、 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中 
    2、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。 
    3、 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。 

    死亡状态:当线程退出了run()方法,就进入了死亡状态,该线程结束了生命周期。 
               或者正常退出 
               或者遇到异常退出 
               Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余的状态下,该方法返回true. 

    二、线程调度线程调度模型:分时调度模型和抢占式调度模型 

    JVM采用抢占式调度模型。 
    所谓的多线程的并发运行,其实是指宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。 
    (线程的调度不是跨平台,它不仅取决于java虚拟机,它还依赖于操作系统) 

    如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一 
    1、 调整各个线程的优先级 
    2、 让处于运行状态的线程调用Thread.sleep()方法 
    3、 让处于运行状态的线程调用Thread.yield()方法 
    4、 让处于运行状态的线程调用另一个线程的join()方法 

    调整各个线程的优先级 
    Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。 
    如果希望程序能够移值到各个操作系统中,应该确保在设置线程的优先级时,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这3个优先级。 

    线程睡眠:当线程在运行中执行了sleep()方法时,它就会放弃CPU,转到阻塞状态。 
    线程让步当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。 
    Sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程,两者的区别在于: 
             1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。 
    2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。 
              3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常 
              4、sleep()方法比yield()方法具有更好的移植性 。

    等待其它线程的结束:join() 
              当前运行的线程可以调用另一个线程的 join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。 

    定时器Timer:在JDK的java.util包中提供了一个实用类Timer, 它能够定时执行特定的任务。 


    三、线程同步

    原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。 

    自增操作(++)不是原子操作,因为它涉及到一次读和一次写。 

    原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。 

    同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。 

    同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。 

    当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。 
    1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。 
    2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 
    (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 
    如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中) 

    线程同步的特征: 
    1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
    2、 每个对象都有唯一的同步锁 
    3、 在静态方法前面可以使用synchronized修饰符。 
    4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。 
    5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。 

    线程安全的类: 
    1、 这个类的对象可以同时被多个线程安全的访问。 
    2、 每个线程都能正常的执行原子操作,得到正确的结果。 
    3、 在每个线程的原子操作都完成后,对象处于逻辑上合理的状态。 

    释放对象的锁: 
    1、 执行完同步代码块就会释放对象的锁 
    2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放 
    3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。 

    死锁 
    当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。 

    如何避免死锁 
    一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。 

    线程通信 
    Java.lang.Object类中提供了两个用于线程通信的方法 
    1、 wait():执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒 
    2、 notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。

    =========================================================================

    sleep:从执行中状态转变为等待中/睡眠中状态,睡眠时间结束自动转为可之执行状态等等下一步的调度;它不会存在调用notify() 或者notifyAll()被唤醒;而且其拥有的锁不会被释放,会一直拥有到睡醒,并执行结束才会释放锁;

    wait使得当前线程睡眠,但调用notify() 或者notifyAll()会随时唤醒线程;在wait的时候,会把自己拥有的锁释放,被唤醒之后重新竞争锁;也有解释说,当wait(times)之后,就是显式给出休眠时间,时间到了会自动进入执行状态,而不是可执行状态,这个会在继续研究;

    yield:从图中可以看出它将从执行中状态转变为可执行(就绪);

    而join是将一个新的A线程插入一个B线程之后,当B线程执行结束之后就执行A线程,这个类似修改线程优先级。强制提升A线程优先级,让B线程执行完就直接执行A线程,而不是调度别的线程执行。采用该方法可以完成一些特殊的使用场景。比如某个步骤必须在上一个步骤完成之后才可以完成,这个时候你就可以采用join,把一个操作join到其前置依赖的操作之后。

    参考:

    http://hi.baidu.com/richarwu/item/49b3033ed0ad75c0382ffacd

    【Java】java多线程状态转换与sleep wait yield join方法小解

  • 相关阅读:
    svn command line tag
    MDbg.exe(.NET Framework 命令行调试程序)
    Microsoft Web Deployment Tool
    sql server CI
    VS 2010 One Click Deployment Issue “Application Validation did not succeed. Unable to continue”
    mshtml
    大厂程序员站错队被架空,只拿着五折工资!苟活和离职,如何选择?
    揭秘!Windows 为什么会蓝屏?微软程序员竟说是这个原因...
    喂!千万别忘了这个C语言知识!(~0 == -1 问题)
    Linux 比 Windows 更好,谁反对?我有13个赞成理由
  • 原文地址:https://www.cnblogs.com/549294286/p/3517698.html
Copyright © 2011-2022 走看看