1. 多线程基础

线程常见方法

start()
  • 启动一个新线程,在新的线程中运行run方法的代码
  • start方法只是让线程进入就绪状态,里面的代码不一定立刻执行(CPU的时间片还没有分给它),每个线程的对象的start方法只能调用一次,调用多次会出现IllegalThreadStateException异常
sleep()
  • 调用sleep方法会让当前线程从running进入到Time waiting状态(阻塞状态)
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
  • 睡眠结束后的线程未必会立即得到执行
  • 建议用TimeUnit的sleep代替Thread的sleep来获取到更好的可读性
yield()
  • 调用yeild会让当前线程从Running进入到Runnable状态,然后调度执行其他线程
  • 当前线程进入Runnable状态之后与其他线程竞争CPU资源,具体实现依赖于操作系统的任务调度器

下面通过一个代码来看

 public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            int count = 0;
            @Override
            public void run() {
                for(;;){
                    log.debug("----- t1 > {}",count++);
                }
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            int count = 0;
            @Override
            public void run() {
                for(;;){
                    Thread.yield(); //线程t2让出时间片
                    log.debug("           ----- t2 > {}",count++);
                }
            }
        },"t2");
        t1.start();
        t2.start();
    }

上面的代码如果在线程t2中没有添加Thread.yield()方法,相当于两个线程公平竞争CPU时间片,所以从结果上来说差不多,但是如果在t2线程中添加了Thread.yield()方法,相当于t2线程让出了时间片,从结果上来看 相差挺大,但是从结果上也证实了上面的结论,1 让出时间片并不代表不会执行,2 一切取决于调度器的执行,正常情况会有影响

setPriority()
  • 设置优先级
  • Thread中定义了三种优先级 MIN_PRIORITY(1),NORM_PRIORITY(5),MAX_PRIORITY(10),如果没有指定优先级,默认优先级就是NORM_PRIORITY,数值越多,优先级越高
  • 线程优先级会提示调度器有限调度该线程,但是它仅仅是一个提示,调度器可以忽略它
  • 如果CPU比较忙,那么优先级高的线程会获得更多的时间片,如果CPU闲时,优先级几乎没有作用

借用上面的线程代码 调用下面的语句

        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();

从结果上看并不明显,这有可能是测试电脑是多核的缘故,因此并不明显

join()
  • 这个语句的意思是等待线程结束
  • 比如在main线程中调用t1.join()方法表示main线程要等t1线程执行完才能往下执行
  • 如果同时执行多个线程的join()方法,各个方法之间应该是并行的,比如执行了t1.join(),t2.join(),其中t1线程sleep 1s,t2线程sleep 2s,等两个join执行完了花费的时间是2s,也就是说两个join是并行执行的
  • join(long) 表示的有时限的等待线程结束,其中参数是毫秒
  • 如果join(1.5s),而t1线程sleep 2s,则main线程在等待1.5s之后便不再等待
  • 如果join(3s),而t1线程sleep 2s,则main线程在t1线程执行完之后便不再继续等待
interrupt
  • interrupt 是打断线程
  • 如果是打断sleep,join,wait等阻塞线程,不会重置打断标志,Thread类有一个isInterrupt方法 返回boolean类型,这个就是打断标志,默认是false,当打断这些阻塞线程时,这个打断标志还是false,这也是当调用这些方法时,需要try catch 住打断异常
  • 如果打断的是正常线程,isInterrupt会被重置为true
  • 哪个线程调用interrupt,打断的是哪个线程
  • Thread 还有一个静态方法 interrupted 这个方法返回的也是boolean,表示线程是否被打断,它与isInterrupted的区别是调用isInterrupted之后打断标注并不会被清除,但是interrupted会清除打断标记,也就是说第一次调用返回的是true的话,下次调用就已经被清除掉了返回的是false
  • interrupt 打断park线程,LockSupport.park()方法可以让线程停下来,可以通过interrupt来打断,通过interrupt打断park之后,打断标志为true,当打断标志为true时,后续再调用LockSupport.park并不会让线程停下来,可以通过调用Thread.interrupted方法清除掉打断标志
park&unPark
  • park&unPark是LockSupport的静态方法
  • 每个线程都会关联到LockSupport
  • LockSupport里面有_count用来记录能否暂停线程,里面只能是1或者0,默认是0,当执行park方法时,会检查_count的值,如果是0,就暂停当前线程,如果是1就不会暂停当前线程,但是会把1变成0,当调用unPark(thread)的时候,会把_count变为1
  • 如果park被打断,之后无论调用多少次park,都不会暂停线程了,这也就是说明对线程的暂停不仅取决于_count这个变量,还取决于打断标记
  • park&unPark与wait¬ify有许多相似之处,但是也有不同的地方
    1. park&unPark不需要持有锁,wait¬ify需要持有锁
    2. unPark提前调用会影响到下一次的Park状态,而notify的先于wait调用好像并没有影响
    3. nofity的通知唤醒是随机的或者是全部的,而unPark的唤醒是指定的线程
    4. 两者之间的阻塞机制不一样 使用nofityAll 并不会唤醒被Park的线程
wait&nofity
  • wait¬ify是Object上的方法
  • 如果使用wait¬ify的方法必须首先获取到该Obj上的锁,这个相当于持有了锁如果调用该对象的wait方法,则线程会进入到Monitor的WaitSet队列中去(此时线程状态是TIME_wait),如果被唤醒会进入到EntrySet中等待调度(线程状态是BLOCKED)
  • wait()方法有个重载方法wait(n)表示等待n毫秒之后就不再等待了,而wait()自身相当于一只等待直到被唤醒
  • notify方法是在WaitSet中随机唤醒一个线程,nofityAll是唤醒WaitSet上的所有线程
  • wait和sleep的方法都是进入到TIME_WAITING状态,但是它们之间有区别
    1. wait是Obj的方法,调用需要持有该Obj的锁
    2. Sleep是Thread的静态方法,并不需要持有锁
    3. 调用了wait方法之后会释放锁,sleep并不会释放锁
  • 使用wait&noify需要注意虚假唤醒的情况,也就是WaitSet中有多个线程,如果有一个线程t3本来是想唤醒t2,线程结果却唤醒了t1线程,t1线程在被唤醒之后发现自身条件不满足,从而可能会造成逻辑上的错误,正确的做法是
    Object object = new Object();
        synchronized(object){

            while(条件判断){
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //do something
        }
    }

另外的线程使用notifyall()

守护线程

  • 当一个线程通过setDaemon(true)时就设置为守护线程了
  • 默认情况下,java进程需要等待所有线程都运行结束,进程才会结束
  • 如果非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
  • 垃圾回收器线程就是一种守护线程

线程状态

从CPU层面上讲,是五种状态
  • 初始状态:仅是从语言层面创建创建了线程对象,还未与操作系统线程关联
  • 可运行状态:指该线程已经被创建了(与操作系统线程关联),可以由CPU调度
  • 运行状态:指的是获取了CPU时间片运行中的状态
    1. 当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 阻塞状态:
    1. 如果调用了阻塞API,如BIO读写文件,这时该线程不会用到CPU,会导致线程上下文切换,进入【阻塞状态】
    2. 等BIO操作完毕,会由操作系统唤醒阻塞线程,转换至【可运行状态】
    3. 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要他们一直不唤醒,调度器就一直不会调度它们
  • 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态
从Java API层面上讲,是六种状态,这是Thread.State枚举定义的
  • NEW:线程刚被创建,但是还没有调用start()方法
  • RUNNABLE:当调用了start()方法之后,进入该状态
    1. 值得注意的是Java API 层面的RUNNABLE状态涵盖了操作系统的【可运行状态】,【运行状态】和【阻塞状态】,由于BIO导致的线程阻塞在Java里无法区分,仍然认为是可运行的
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED 表示线程代码运行结束

线程状态之间的切换:

  • 【情况1】 : NEW --> RUNNABLE :当调用了t.start()

  • 【情况2】 : RUNNABLE <--> WAITING: 线程用synchronized(obj)获取对象锁后

    1. 调用了obj.wait()方法时,线程从RUNNABLE -->WAITING
    2. 调用了obj.notify,obj.notifyAll,t.interrupt()方法时
      a. 如果竞争锁失败,从WAITING -->BLOCKED
      b. 如果竞争锁成功,从WAITING -->RUNNABLE
  • 【情况3】 : RUNNABLE <--> WAITING

    1. 当线程调用t.join()方法时,当前线程从RUNNABLE -->WAITING(当前线程在t线程对象的Monitor上等待)
    2. t线程运行结束或者调用当前线程的interrupt(),当前线程从WAITING --> RUNNABLE
  • 【情况4】 : RUNNABLE <-->WAITING

    1. 当前线程调用LockSupport.park()方法时,会让当前线程从RUNNABLE --> WAITING
    2. 调用LockSupport.unpark(目标线程)或者调用了线程的interrupt()方法会让目标线程 WAITING --> RUNNABLE
  • 【情况5】 : RUNNABLE <--> TIMED_WAITING t线程调用了synchronized(obj)获得对象锁后

    1. 调用obj.wait(long n)方法时,t线程从RUNNABLE --> TIMED_WAITING
    2. t线程等待时间超过了n毫秒,或调用了obj.notify,obj.notifyAll,t.interrupt()方法时时
      a. 竞争锁成功,t线程从TIMED_WAITING --> RUNNABLE
      b. 竞争锁失败,t线程从TIMED_WAITING --> BLOCKED
  • 【情况6】 : RUNNABLE <-->TIMED_WAITING

    1. 当前线程调用了t.join(long n)方法时,当前线程从RUNNABLE --> TIMED_WAITING(注意当前线程是在t线程对象的Monitor等待)
    2. 当前线程等待时间超过n毫秒或t线程运行结束或调用了当前线程的interrupt()方法时,当前线程从TIMED_WAITING -->RUNNABLE
  • 【情况7】 : RUNNABLE <--> TIMED_WAITING

    1. 当前线程调用Thread.sleep(long n),当前线程会从RUNNABLE --> TIMED_WAITING
    2. 当前线程等待时间超过n毫秒,当前线程从TIMD_WAITING --> RUNNABLE
  • 【情况8】 : RUNNABLE <--> TIMED_WAITING

    1. 当前线程调用了LockSupport.parkNanos(long nanos)或者LockSupport.parkUntil(long millis),当前线程从RUNNABLE -->TIMED_WAITING
    2. 调用LockSupport.unpark(目标线程)或调用了线程的interrupt()或者等待超时,会让目标线程从TIMED_WAITING -->RUNNABLE
  • 【情况9】 : RUNNABLE <--> BLOCKED

    1. t线程用synchronized(obj)获取对象锁时如果竞争失败,从RUNNABLE-->BLOCKED
    2. 持有obj锁对象的代码块执行完毕,会唤醒该对象上所有BLOCKED的线程重新竞争,如果竞争成功 从BLOCKED --> RUNNABLE,其他失败的仍然是BLOCKED
  • 【情况10】 : RUNNABLE --> TERMINATED

    1. 当前线程所有代码运行完毕,进入TERMINATED

你可能感兴趣的:(1. 多线程基础)