多线程基础

线程介绍


什么是线程

现代操作系统运行一个程序,就会为其创建一个进程,(为什么程序不直接操作进程而需要操作线程呢,主要在于进程如果阻塞挂起,那么整个进程操作的程序都会暂停,不如线程细粒度高。进程一个时间只能干一件事),在一个进程里面可以创建多个线程,每个线程都有自己的堆、栈、程序计数器等属性,还可以访问共享内存。cpu切换不同的线程,让不同线程感觉可以同时执行。

譬如我们启动一下tomcat,操作系统就会为java分配一个进程。

jps查看tomcat启动线程的pid:

java进程同时也会使用线程来执行各种程序:

线程生命周期

  • 线程的状态:
    NEW :初始状态,线程被构建,但是没有调用start方法
    RUNNABLE :运行状态
    BLOCKED :阻塞状态
    WAITING :等待状态,等待其他线程执行
    TERMINATED :终止状态,线程已经执行完毕了
  • 生命周期:

main线程


main方法的执行,会构建main线程、启动main线程来执行

public static void main(String[] args) {
   ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
   ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);

    Arrays.stream(threadInfos).forEach(threadInfo -> {
        System.out.println("threadId = " + threadInfo.getThreadId() + " threadName = " + threadInfo.getThreadName());
    });
}

Daemon线程


Daemon线程是一中守护线程,主要用于程序中后台调度和支持性工作。譬如jvm中垃圾回收线程就是守护线程(注意守护线程需要在用户线程前启动)。

public static void main(String[] args) {
    Thread workerThread = new Thread(() -> {
        // 建立远程连接
        System.out.println(Thread.currentThread().getName() + " create connect");
        // 开启守护线程,ping测试连接状态
        Thread doemonThread = new Thread(() -> {
            while (true) {
                System.out.println(Thread.currentThread().getName() + " tcp ping test connect!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "doemonThread");
        doemonThread.setDaemon(true);
        doemonThread.start();

        // 断开连接,守护线程也推出
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " exit connect");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "workerThread");
    workerThread.start();
}

线程中断


线程中断就是给线程打一个中断属性,并不是真的停止线程,其他线程通过调用该线程的interrupt方法对其进行中断操作。

interrupt并非中断线程

调用interrupt()方法仅仅就是给当前线程打了一个中断标记,并不是真的停止线程。

public class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0 ; i < 500000; i++) {
            System.out.println("i = " + (i+1));
        }
    }
}

public class Test {
    
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        mt.start();
        mt.interrupt();
    }
}
interrupt结果.png

interrupted和isInterrupted区别

interrupted例子:

public static void main(String[] args) throws Exception {
    Thread.currentThread().interrupt();
    System.out.println("Thread.interrupted() " + Thread.interrupted());
    System.out.println("Thread.interrupted() " + Thread.interrupted());
}

结果:

Thread.interrupted() true
Thread.interrupted() false

isInterrupted例子:

public static void main(String[] args) throws Exception {
    Thread t = Thread.currentThread();
    t.interrupt();
    System.out.println("t.isInterrupted() " + t.isInterrupted());
    System.out.println("t.isInterrupted() " + t.isInterrupted());
}

结果:

t.isInterrupted() true
t.isInterrupted() true

interrupted源码:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

/**
  * Tests if some Thread has been interrupted.  The interrupted state
  * is reset or not based on the value of ClearInterrupted that is
  * passed.
  */
private native boolean isInterrupted(boolean ClearInterrupted);

说明:设置为true值状态,返回当前线程状态位,然后清除状态位。

isInterrupted源码:

/**
 * Tests whether this thread has been interrupted.  The interrupted
 * status of the thread is unaffected by this method.
 *
 * 

A thread interruption ignored because a thread was not alive * at the time of the interrupt will be reflected by this method * returning false. * * @return true if this thread has been interrupted; * false otherwise. * @see #interrupted() * @revised 6.0 */ public boolean isInterrupted() { return isInterrupted(false); }

说明:设置为false值状态,返回当前线程状态位,不清除状态位。

interrupted例子:第一次打印结果为true,是返回中断状态位。然后清除状态位,所以第二次打印结果为false。
isInterrupted例子:第一次打印结果为true,是返回中断状态位。然后不清除状态位。所以第二次打印结果还为true。

中断异常InterruptedException

sleep/wait/notify方法抛出中断异常前,会清除中断状态位。

public class InterruptedCase {

    public static void main(String[] args) throws Exception {
        Thread sleepRunner = new Thread(new SleepRunner(), "SleepRunner");
        sleepRunner.setDaemon(true);

        Thread busyRunner = new Thread(new BusyRunner(), "BusyRunner");
        busyRunner.setDaemon(true);

        sleepRunner.start();
        busyRunner.start();

        TimeUnit.SECONDS.sleep(5);

        sleepRunner.interrupt();
        busyRunner.interrupt();

        System.out.println("SleepRunner interrupted is " + sleepRunner.isInterrupted());
        System.out.println("BusyRunner interrupted is " + busyRunner.isInterrupted());

        TimeUnit.SECONDS.sleep(2);
    }

    static class SleepRunner implements Runnable {

        @Override
        public void run() {
            while(true) {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class BusyRunner implements Runnable {

        @Override
        public void run() {
            while(true) {

            }
        }
    }
}

结果:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.sunpy.threadtest.test.InterruptedCase$SleepRunner.run(InterruptedCase.java:34)
    at java.lang.Thread.run(Thread.java:748)
SleepRunner interrupted is false
BusyRunner interrupted is true

BusyRunner发生中断,中断状态位为true。
SleepRunner调用sleep方法,在此期间,发生中断,抛出中断异常,中断已经结束,状态位也被清除了,所以返回为false。

sleep方法的中断逻辑:

/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          interrupted status of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

中断异常的英文@throws InterruptedException:
如果任一当前线程发生中断,那么当前线程的中断异常状态会被清除,然后抛出异常。(可见sleep方法抛出异常前会清除中断状态位)

线程关闭


安全关闭线程思路

利用中断操作来关闭线程,也可以利用标识位来关闭线程。

利用中断操作关闭线程

public class InterruptedCase {

    public static void main(String[] args) throws Exception {
        Thread closeRunner = new Thread(new CloseRunner(), "CloseRunner");
        closeRunner.start();

        TimeUnit.SECONDS.sleep(1);

        closeRunner.interrupt();
    }

    static class CloseRunner implements Runnable {
        private long i;
        
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()) {
                i++;
            }

            System.out.println("i value = " + i);
        }
    }
}

结果:

i value = 453049242

利用标识位来关闭线程

public class InterruptedCase {

    public static void main(String[] args) throws Exception {
        CloseRunner closeRunner = new CloseRunner();
        Thread closeThread = new Thread(closeRunner, "CloseRunner");
        closeThread.start();

        TimeUnit.SECONDS.sleep(1);

        closeRunner.cancelByFlag();
    }

    static class CloseRunner implements Runnable {
        private long i;
        private volatile boolean on = true;

        @Override
        public void run() {
            while(on) {
                i++;
            }

            System.out.println("i value = " + i);
        }

        public void cancelByFlag() {
            on = false;
        }
    }
}

结果:

i value = 405669970

思考:为什么boolean标志位需要volatile修饰?

  • CloseRunner线程从主内存读取标志位on为true,则继续循环。
  • 当我们的main线程修改volatile修饰的标志位on为false时,将main线程的本地内存共享变量on刷新到主内存了。
  • 这样当CloseRunner线程再从主内存读取标志位on时为false了,就结束了线程的循环。

线程等待/通知


wait/notify

wait()方法的作用就是将当前执行代码的线程进行等待,wait()方法时Object类的方法用来将当前线程置入 “预执行队列” 中,在wait()方法所在的代码行处停止执行,直到收到通知或者中断为止。
notify()方法的作用就是将多个等待线程,由线程规划期随机挑选出其中一个wait状态的线程,发出notify唤醒。
1. wait/notify方法调用前,需要先获取对象锁

public class Test {
    
    public static void main(String[] args) {
        try {
            Object obj = new Object();
            obj.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

[Console output redirected to file:D:\console.txt]
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:503)
    at cn.spy.thread.test.Test.main(Test.java:8)

2. 等待唤醒例子

public class MyThread1 extends Thread {

    private Object lock;

    public MyThread1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " start wait " + System.currentTimeMillis());
                lock.wait();
                System.out.println(Thread.currentThread().getName() + " end wait " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MyThread2 extends Thread {

    private Object lock;

    public MyThread2(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " start notify " + System.currentTimeMillis());
            lock.notify();
            System.out.println(Thread.currentThread().getName() + " end notify " + System.currentTimeMillis());
        }
    }
}

public class Test {
    
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            MyThread1 mt1 = new MyThread1(lock);
            mt1.setName("MyThread1");
            mt1.start();
            Thread.sleep(5000);
            MyThread2 mt2 = new MyThread2(lock);
            mt2.setName("MyThread2");
            mt2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

[Console output redirected to file:D:\console.txt]
MyThread1 start wait 1541050962125
MyThread2 start notify 1541050967127
MyThread2 end notify 1541050967127
MyThread1 end wait 1541050967127

说明:可以发现MyThread2在执行notify方法唤醒MyThread1,然后MyThread2没有马上释放线程,MyThread1没有马上获取线程。
① 等待通知的执行顺序是如果我们执行了notify();方法并不会马上就释放线程了,而那个wait状态的线程也不能马上获取该对象锁。得等到notify();方法的线程执行完(也就是退出了synchronized代码块后),线程才会释放锁,而呈wait(); 状态的线程才可以获取该对象锁。
② 当前如果有多个线程处于等待的状态,那么使用notify();方法,线程规划器只会随机的挑选出其中一个线程。
③ wait方法执行后,锁自动释放,但是执行完notify方法,锁不会自动释放。

总结

  • wait/notify方法调用前,需要先获取对象锁
  • notify唤醒多个等待线程,是随机挑选出的。
  • wait方法执行后,锁自动释放,但是执行完notify方法,锁不会自动释放。
  • notify唤醒的线程不会马上执行(因为notify方法的执行,锁不会自动释放)。
  • 当线程呈wait();状态时,调用线程对象的interrupt();方法会报一个InterruptedException异常。

wait扩展(join)

join的作用:
join可以让线程排队执行,而join的内部采用wait方法来实现等待。而join的作用是等待线程对象销毁。

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run start");
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println(Thread.currentThread().getName() + "run end");
    }
}

public class JoinTest {

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.setName("MyThread");
        mt.start();
        
        try {
            mt.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println(Thread.currentThread().getName() + " execute");
    }
}

说明:默认join方法等待0秒,所以t1线程在睡眠5秒之后,wait(0),相当于没等待,所以main execute最后执行。

join(long time)设置等待时间

public class JoinTest {

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.printf("%s start %d\n", Thread.currentThread().getName(), System.currentTimeMillis());
                
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                System.out.printf("%s end %d\n", Thread.currentThread().getName(), System.currentTimeMillis());
            }
        });
        
        t1.setName("t1");
        t1.start();
        
        try {
            t1.join(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.printf("%s start %d\n", Thread.currentThread().getName(), System.currentTimeMillis());
    }
}

结果:

t1 start 1541408642457
main start 1541408644457
t1 end 1541408648500

说明:可以发现在join设置的时间到时之后,将会让join的线程等待,让其他的线程执行。也可以看到join到时间将释放锁。

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