01-多线程-基础

线程的生命周期

线程共包括一下5种状态

  • 新建状态(New):线程对象被创建以后,就进入了新建创建,例如:Thread thread = new Thread()。
  • 就绪状态(Runnable):也被称为:可执行的状态,线程对象被创建以后,其他线程调用了该对象的start()方法,从而来启动该线程,例如:thread.start(),处于就绪状态的线程可以被CPU调用
  • 运行状态(Running):线程获取cpu权限进行执行,需要注意的是:线程只能从就绪状态进入运行状态
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了cpu的使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态,阻塞分为三种:
    • 等待阻塞 :通过调用线程的wait()方法,让线程等待某工作的完成
    • 同步阻塞 :线程在获取synchornized同步锁失败(因为锁被其他线程所占用),他会进入同步阻塞状态
    • 其他阻塞 :通过调用线程的sleep()或者join()或者发出了I/O请求时,线程会进入到阻塞的状态,当sleep()状态超时,join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  • 死亡状态(Dead): 线程执行完课或者因为异常退出了run()方法,该线程结束生命周期

线程的实现方式

常用的实现方式
  • 继承抽象类Thread 重写run()方法
  • 实现接口Runnable
线程池的实现
  • 还可以通过java.util.concurrent包中的线程池来实现多线程。

Thread中start()和run()的区别

  • start():它的作用就是启动一个新线程,新线程会执行相应的run()方法,start()不能被重复调用
  • run():和普通的成员方法一样,可以被重复的调用,单独调用run()的话,会在当前线程中执行run(),而且并不会启动新线程
源码分析 基于jdk8
 
    //工具的Java线程状态, 初始化表示线程'尚未启动'
    private volatile int threadStatus = 0;
    
     
     //这个线程的组
    private ThreadGroup group;
    
    public synchronized void start() {
          //如果线程不是"就绪状态",则抛出异常!
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

       //将线程添加到ThreadGroup中
       //通知组该线程即将启动,这样它就可以添加到组的线程列表中,并且该组的未启动计数可以递减
        group.add(this);

        boolean started = false;
        try {
        //通过该方法启动线程
            start0();
            //设置标记
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
复制代码

start()实际上是通过本地方法start0()启动线程的。而start0()会新运行一个线程,新线程会调用run()方法。

run()方法分析
 /* What will be run. */
    private Runnable target;

   public void run() {
        if (target != null) {
            target.run();
        }
    }
复制代码

target是一个Runnable对象。run()就是直接调用Thread线程的Runnable成员的run()方法,并不会新建一个线程。

synchronized

原理
  • 在java中,每一个对象有且仅有一个同步锁,这就意味着,同步锁是依赖于对象存在的
  • 当我们调用某对象的synchronized方法时,就获取了该对象的同步锁
  • 不同线程对同步锁的访问是互斥的
synchronized方法 和 synchronized代码块

“synchronized方法”是用synchronized修饰方法,而 “synchronized代码块”则是用synchronized修饰代码块。

synchronized 实例锁 和 全局锁
  • 实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。 实例锁对应的就是synchronized关键字。
  • 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是staticsynchronized(或者是锁在该类的class或者classloader对象上)。

线程的等待和唤醒

wait(), notify(), notifyAll()等方法介绍
  • 三个方法都定义在Object对象中
  • wait():让当前线程进入等待状态,即阻塞状态,同时会释放当前线程持有的锁,可以使用 notify() 方法或 notifyAll() 方法唤醒线程,wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
  • notify():唤醒单个线程,唤醒当前对象上的等待线程
  • notifyAll():唤醒当前对象上的所有等待线程
为什么notify(), wait()等函数定义在Object中,而不是Thread中
  • Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
  • wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
  • 线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
  • 负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
  • notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

线程的让步

yield()介绍
  • yield()的作用是让步,让当前线程由运行状态进入到就绪状态,从而让其他具有相同优先级的等待线程获取执行权
  • 但是,并不能保证当前线程调用yield()之后,其他具有相同优先级的线程就一定能获取执行权;也有可能是当前线程又进入到运行状态继续运行
yield() 与 wait()的比较
  • wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,yield()是让线程由“运行状态”进入到“就绪状态”。
  • wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。

线程的休眠

sleep()介绍
  • sleep() 定义在Thread.java中。
  • sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。
  • sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
sleep() 与 wait()的比较
  • wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。
  • sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。 但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。

线程的礼让

join()介绍
  • join() 定义在Thread.java中。
  • join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行

线程的终止方式和interrupt()

interrupt()说明
  • 作用:中断本线程
  • 本线程中断自己是被允许的,其他线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
终止线程的方式
  • Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!
终止处于“阻塞状态”的线程
  • 通常,我们通过“中断”方式终止处于“阻塞状态”的线程。
  • 当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true
  • 处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程,
@Override
public void run() {
    while (true) {
        try {
            // 执行任务...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)循环体内。
            // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出
            break;
        }
    }
}
复制代码
终止处于“运行状态”的线程
  • 通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
终止线程的示例
  • interrupt()常常被用来终止“阻塞状态”线程。参考下面示例:
// Demo1.java的源码
class MyThread extends Thread {
    
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {  
            int i=0;
            while (!isInterrupted()) {
                Thread.sleep(100); // 休眠100ms
                i++;
                System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
            }
        } catch (InterruptedException e) {  
            System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {  
        try {  
            Thread t1 = new MyThread("t1");  // 新建“线程t1”
            System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  

            t1.start();                      // 启动“线程t1”
            System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  

            // 主线程休眠300ms,然后主线程给t1发“中断”指令。
            Thread.sleep(300);
            t1.interrupt();
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");

            // 主线程休眠300ms,然后查看t1的状态。
            Thread.sleep(300);
            System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
        } catch (InterruptedException e) {  
            e.printStackTrace();
        }
    } 
}
复制代码
interrupted() 和 isInterrupted()的区别
  • interrupted() 和 isInterrupted()都能够用于检测对象的“中断标记”。
  • interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。

线程的优先级和守护线程

线程优先级的介绍
  • java 中的线程优先级的范围是1~10,默认的优先级是5。
  • “高优先级线程”会优先于“低优先级线程”执行。
  • java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们
  • 如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
  • 用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务
  • Java虚拟机在“用户线程”都结束后会后退出。
//设置该线程的优先级
thread.setPriority(1);
复制代码

转载于:https://juejin.im/post/5cee406de51d4555fd20a2c0

猜你喜欢

转载自blog.csdn.net/weixin_33810302/article/details/91465396
今日推荐