并发(一):了解多线程

103 阅读6分钟

1、什么是线程

线程是CPU进行调度的最小单位
多线程的特点:异步和并行

2、并发和并行的区别

并发:单个CPU在一定的时间间隔内靠时间片切换处理多个任务,他不可能真正的同时执行多个任务。
并行:多个CPU在同一时刻同时处理多个任务,是真正的同时处理

3、java中创建线程的方式

(1)继承Thread类

public class ThreadDemo extends Thread {

    /**
     * If this thread was constructed using a separate
     * {@code Runnable} run object, then that
     * {@code Runnable} object's {@code run} method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of {@code Thread} should override this method.
     *
     * @see #start()
     * @see #stop()
     * @see #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        System.out.println("子线程处理任务...");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new ThreadDemo());
        thread.start();
        System.out.println("main线程处理任务...");
    }
}

(2) 实现Runable接口

public class RunableDemo implements Runnable{
    /**
     * When an object implementing interface {@code Runnable} is used
     * to create a thread, starting the thread causes the object's
     * {@code run} method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method {@code run} is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        System.out.println("子线程处理任务...");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new RunableDemo());
        thread.start();
        System.out.println("main线程处理任务...");
    }
}

(3)实现Callable接口

如果想要获取线程的执行结果,可以用这个。使用线程池提交一个任务,用Future的get方法获取结果,线程池使用完记得关闭。

public class CallableDemo implements Callable<String> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    @Override
    public String call() throws Exception {
        return "任务执行成功";
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> submit = executorService.submit(new CallableDemo());
        try {
            System.out.println("线程执行结果:" + submit.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

(4)线程池创建线程

一个核心线程的线程池,最大线程数也是1
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
固定大小的线程池,核心线程数和最大线程数相等,都是传入的参数
ExecutorService executorService2 = Executors.newFixedThreadPool(1);
具有缓存功能的线程池
ExecutorService executorService3 = Executors.newCachedThreadPool();
具有定时任务功能的线程池
ExecutorService executorService4 = Executors.newScheduledThreadPool(1);

4、线程的状态

java中线程的状态:6种
操作系统层面:5种

image.png 写一个案例来验证一下

public static void main(String[] args) {
    new Thread(() -> {
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "thread-01").start();

    new Thread(() -> {
        while (true) {
            synchronized (Test.class) {
                try {
                    Test.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }, "thread-02").start();

    new Thread(new TestDemo(), "TestDemo-01").start();
    new Thread(new TestDemo(), "TestDemo-02").start();
}


static class TestDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            synchronized (TestDemo.class) {
                try {
                    TimeUnit.SECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行main方法,发生锁等待,使用jps命令找到我们启动的java进程的进程号,我这里是9712

image.png 然后再使用jstack打印堆栈信息, jstack 9712
`"thread-01" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=26.77s tid=0x000002ebaa74d800 nid=0x5e60 waiting on condition [0x000000fc93eff000 ] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(java.base@14.0.2/Native Method) at java.lang.Thread.sleep(java.base@14.0.2/Thread.java:337) at java.util.concurrent.TimeUnit.sleep(java.base@14.0.2/TimeUnit.java:446) at com.alibaba.Test.lambdamainmain0(Test.java:37) at com.alibaba.Test$$Lambda$15/0x0000000800b94440.run(Unknown Source) at java.lang.Thread.run(java.base@14.0.2/Thread.java:832)

"thread-02" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=26.77s tid=0x000002ebaa74e800 nid=0x5124 in Object.wait() [0x000000fc93ffe000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(java.base@14.0.2/Native Method) - waiting on <0x0000000711aa7170> (a java.lang.Class for com.alibaba.Test) at java.lang.Object.wait(java.base@14.0.2/Object.java:321) at com.alibaba.Test.lambdamainmain1(Test.java:48) - locked <0x0000000711aa7170> (a java.lang.Class for com.alibaba.Test) at com.alibaba.Test$$Lambda$16/0x0000000800b95040.run(Unknown Source) at java.lang.Thread.run(java.base@14.0.2/Thread.java:832)

"TestDemo-01" #18 prio=5 os_prio=0 cpu=0.00ms elapsed=26.77s tid=0x000002ebaa74f800 nid=0x1268 waiting on condition [0x000000fc940fe0 00] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(java.base@14.0.2/Native Method) at java.lang.Thread.sleep(java.base@14.0.2/Thread.java:337) at java.util.concurrent.TimeUnit.sleep(java.base@14.0.2/TimeUnit.java:446) at com.alibaba.TestTestDemo.run(Test.java:68)locked<0x0000000711aac468>(ajava.lang.Classforcom.alibaba.TestTestDemo.run(Test.java:68) - locked <0x0000000711aac468> (a java.lang.Class for com.alibaba.TestTestDemo) at java.lang.Thread.run(java.base@14.0.2/Thread.java:832)

"TestDemo-02" #20 prio=5 os_prio=0 cpu=0.00ms elapsed=26.77s tid=0x000002ebaa751000 nid=0x7118 waiting for monitor entry [0x000000fc9 41ff000] java.lang.Thread.State: BLOCKED (on object monitor) at com.alibaba.TestTestDemo.run(Test.java:68)waitingtolock<0x0000000711aac468>(ajava.lang.Classforcom.alibaba.TestTestDemo.run(Test.java:68) - waiting to lock <0x0000000711aac468> (a java.lang.Class for com.alibaba.TestTestDemo) at java.lang.Thread.run(java.base@14.0.2/Thread.java:832) `
可以看到thread-01:TIMED_WAITING,thread-02:WAITING,TestDemo-01:TIMED_WAITING,TestDemo-02:BlCOKED
这几个状态我们在Thread类的内部枚举类中都可以看到

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called {@code Object.wait()}
     * on an object is waiting for another thread to call
     * {@code Object.notify()} or {@code Object.notifyAll()} on
     * that object. A thread that has called {@code Thread.join()}
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

5、线程的启动和停止

在java中是没有线程的概念的,我们调用start方法启动一个线程,实际上是调了一个本地方法启动了一个操作系统的线程

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    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方法的实现可以看到在start方法里面调了一个start0方法,而start0是一个本地方法 \

image.png

针对于不同的操作系统,jvm有不同的实现去启动一个线程。
在jvm的启动线程的方法中,又会回调我们java中的run方法,这里是使用了JNI的机制,这里不是重点,Hotspot源码就不看了

如何中断一个正在运行的线程

在java中提供了一个interrupt去友好地中断线程,不推荐使用stop方法强制中断,如果此时指令执行到一半,将会产生意想不到的异常。 \

public class InterruptDemo extends Thread {

    /**
     * If this thread was constructed using a separate
     * {@code Runnable} run object, then that
     * {@code Runnable} object's {@code run} method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of {@code Thread} should override this method.
     *
     * @see #start()
     * @see #stop()
     * @see #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("我在执行任务...");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new InterruptDemo());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

在未响应中断之前,一直在执行任务,循环调用isInterrupt方法判断是否中断,调用interrupt方法之后响应中断。

image.png

如何中断一个睡眠的线程?

线程在睡眠中也是可以响应中断的,调用Thread.sleep方法会强制你捕获异常InterruptedException来响应中断

public class InterruptDemo extends Thread {

    /**
     * If this thread was constructed using a separate
     * {@code Runnable} run object, then that
     * {@code Runnable} object's {@code run} method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of {@code Thread} should override this method.
     *
     * @see #start()
     * @see #stop()
     * @see #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new InterruptDemo());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

从运行结果中可以看到处于睡眠状态的线程响应了中断,但是只是响应,并没有真正的中断,可以看到进程还在。 image.png 在捕获到异常之后,我们再次中断,此时就会看到进程已经结束

image.png

这里其实就是在jvm中维护了一个状态变量interrupt,默认是false,当我们调用interrupt方法之后设置成true。