Skip to content

Latest commit

 

History

History
438 lines (366 loc) · 16 KB

线程.md

File metadata and controls

438 lines (366 loc) · 16 KB

目录

Thread的特性

  1. 每个线程都有优先级,高优先级的线程可能会比优先级低的优先执行。

  2. 父线程创建子线程后,优先级、是否是守护线程等属性父子线程是一致的。

  3. JVM 启动时,通常都启动 MAIN 非守护线程,以下情况,线程会停止

    • 退出方法被调用,并且安全机制允许这么做(比如调用 Thread.interrupt 方法);

    • 所有非守护线程都消亡,或者从运行的方法正常返回,或者运行的方法抛出了异常;

  4. 每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字。

Thread的初始化

  1. 继承Thread,实现run方法

    public class Main {
        class Task extends Thread {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }
    
        public void test() {
            new Task().start();
        }
        public static void main(String[] args) {
            new Main().test();
        }
    }

    输出结果为Thread-0,而主线程的名字是main,由此可见,开了一个子线程来执行打印的操作。

  2. 实现Runnable接口,作为Thread入场

    public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
            // 开一个子线程去执行
            thread.start();
            // 不会新起线程,是在当前主线程上继续运行
            thread.run();
        }

    要注意区分thread.start()和thread.run(),前者输出Thread0,是开一个子线程去执行。后者输出main,不会新起线程,是在当前主线程上继续运行。

start源码和run源码

  1. start源码

    public synchronized void start() {
            // 如果没有初始化,抛异常
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
    
      			//把线程添加到线程组,并通知线程组线程准备启动,线程组里面的未开始的线程数量减1
            group.add(this);
    				// started 是个标识符,线程启动之前标识符是 false,发生完成之后变成 true
            boolean started = false;
            try {
            // 创建一个新的线程,执行完成之后,新的线程已经在运行了,既 target 的内容已经在运行了
                start0();
               // 这里执行的还是主线程
                started = true;,
            } finally {
                try {
                  // 如果失败,把线程从线程组中删除,nUnstartedThreads++(启动失败线程数加1)
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } 
              // Throwable 可以捕捉一些 Exception 捕捉不到的异常,比如说子线程抛出的异常
              catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    // 开启新线程使用的是 native 方法
    private native void start0();
  2. run源码

    // 简单的运行,不创建新的线程,target 是 Runnable
    public void run() {
        if (target != null) {
            target.run();
        }
    }

Thread运行的状态图

线程执行流程图.png

  1. NEW 表示线程创建成功,但没有运行,在 new Thread 之后,没有 start 之前,线程的状态都是 NEW;
  2. 当我们运行 strat 方法,子线程被创建成功之后,子线程的状态变成 RUNNABLE,RUNNABLE 表示线程正在运行中;
  3. 子线程运行完成、被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED,TERMINATED 表示线程已经运行结束了;
  4. 如果线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED(阻塞状态);
  5. WAITING 和 TIMED_WAITING 类似,都表示在遇到 Object#wait、Thread#join、LockSupport#park 这些方法时,线程就会等待另一个线程执行完特定的动作之后,才能结束等待,只不过 TIMED_WAITING 是带有等待时间的。

守护线程

我们默认创建的线程都是非守护线程。创建守护线程时,需要将 Thread 的 daemon 属性设置成 true,守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程,JVM 仍然会退出,我们在工作中,可能会写一些工具做一些监控的工作,这时我们都是用守护子线程去做,这样即使监控抛出异常,但因为是子线程,所以也不会影响到业务主线程,因为是守护线程,所以 JVM 也无需关注监控是否正在运行,该退出就退出,所以对业务不会产生任何影响。

Thread初始化

// 无参构造器,线程名字自动生成
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
//参数为Runnable的构造方法,当线程启动的时候,执行Runnable里面的run方法
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
//这个不是公有构造方法,创建一个线程包含AccessControlContext对象
Thread(Runnable target, AccessControlContext acc) {
        init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    }
// g 代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
// target 是我们要运行的对象
// name 我们可以自己传,如果不传默认是 "Thread-" + nextThreadNum(),nextThreadNum 方法返回的是自增的数字
// stackSize 可以设置堆栈的大小
public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
				// 当前线程作为父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
 			  // 子线程会继承父线程的守护属性
        this.daemon = parent.isDaemon();
  			// 子线程继承父线程的优先级属性
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
  			// 当父线程的 inheritableThreadLocals 的属性值不为空时
    		// 会把 inheritableThreadLocals 里面的值全部传递给子线程
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
  			//线程 id 自增
        tid = nextThreadID();
    }

Thread常用方法

join方法

join 的意思就是当前线程等待另一个线程执行完成之后,才能继续操作。

public static void main(String[] args) throws InterruptedException {
        Thread main = Thread.currentThread();
        System.out.println(main + " is run");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is run");
                try {
                    System.out.println(Thread.currentThread().getName() + " is sleep");
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" is end");
            }
        });
        // 开一个子线程去执行
        thread.start();
        // 当前主线程等待子线程执行完成之后再执行
        thread.join();

        System.out.println(main + " is end");
    }



/**
 *输出
 *Thread[main,5,main] is run
 *Thread-0 is run
 *Thread-0 is sleep
 *Thread-0 is end
 *Thread[main,5,main] is end
/

执行的结果,就是主线程在执行 thread.join (); 代码后会停住,会等待子线程沉睡 30 秒后再执行,这里的 join 的作用就是让主线程等待子线程执行完成后,再去执行主线程的。

yield方法

yield 是个 native 方法,底层代码如下

public static native void yield();

意思是当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程过度使用 cpu,我们在写 while 死循环的时候,预计短时间内 while 死循环可以结束的话,可以在循环里面使用 yield 方法,防止 cpu 一直被 while 死循环霸占。

注意,让步不是绝不执行,重新竞争时,cpu 也有可能重新选中自己。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new MyThread("thread1");
        Thread t2 = new MyThread("thread2");
        t1.start();
        t2.start();
    }
}
class MyThread extends Thread {
    MyThread(String s) {
        super(s);
    }
    public void run() {
        for(int i = 1; i <= 20; i++) {
            System.out.println(Thread.currentThread().getName() + ":is run"  );
            if(i % 5 == 0) {//每次被5整除后让出cpu
                System.out.println(Thread.currentThread().getName() +" is yield");
                Thread.yield();
            }
        }
    }
}


/** 运行结果
thread1:is run
thread2:is run
thread2:is run
thread2:is run
thread2:is run
thread2:is run
thread1:is run
thread2is yield   thread2让出cpu但是cpu重新选择了thread2
thread2:is run
thread2:is run
thread2:is run
thread1:is run
thread1:is run
thread2:is run
thread1:is run
thread1is yield  thread1让出cpucpu重新选择了thread2
thread2:is run
thread1:is run
thread2is yield   thread2让出cpucpu重新选择了thread1
thread1:is run
thread2:is run
thread1:is run
thread1:is run
thread1:is run
thread1is yield    thread1让出cpucpu重新选择了thread2
thread2:is run
thread1:is run
thread1:is run
thread1:is run
thread1:is run
thread1:is run
thread1is yield    thread1让出cpucpu重新选择了thread2
thread2:is run
thread1:is run
thread1:is run
thread1:is run
thread1:is run
thread2:is run
thread2:is run
thread2is yield  thread2让出cpu但是cpu重新选择了thread2
thread2:is run
thread2:is run
thread2:is run
thread2:is run
thread2:is run
thread2is yield   thread2让出cpucpu重新选择了thread1
thread1:is run
thread1is yield
/

Sleep方法

sleep 也是 native 方法,可以接受毫秒的一个入参,也可以接受毫秒和纳秒的两个入参,意思是当前线程会沉睡多久,沉睡时不会释放锁资源,所以沉睡时,其它线程是无法得到锁的。

接受毫秒和纳秒两个入参时,如果给定纳秒大于等于 0.5 毫秒,算一个毫秒,否则不算。

public static void main(String[] args)  {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is sleep 3s");
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

Interrupt方法

可以打断中止正在运行的线程,如

  1. Object#wait ()、Thread#join ()、Thread#sleep (long) 这些方法运行后,线程的状态是 WAITING 或 TIMED_WAITING,这时候打断这些线程,就会抛出 InterruptedException 异常,使线程的状态直接到 TERMINATED;
  2. 如果 I/O 操作被阻塞了,我们主动打断当前线程,连接会被关闭,并抛出 ClosedByInterruptException 异常;
public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is run");
                try {
                    System.out.println(Thread.currentThread().getName() + " is sleep 30s");
                    Thread.sleep(30000L);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " is Interrupt");
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is end");
            }
        });
        thread.start();
        Thread.sleep(1000L);
        System.out.println("主线程" + Thread.currentThread().getName() + "运行1秒后,打断子线程");
        thread.interrupt();
    }

/** 输出结果
	Thread-0 is run
	Thread-0 is sleep 30s
	主线程main运行1秒后,打断子线程
	Thread-0 is Interrupt
	Thread-0 is end
	java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.Main$1.run(Main.java:17)
	at java.lang.Thread.run(Thread.java:748)
/