Java并发编程基础-线程简介

从本篇文章开始,我将开始并发编程系列文章,线程是并发编程的基础,理解线程的基本概念和线程状态的转换是并发编程最基本的要求。

一、进程与线程

进程是内存中运行的应用程序,每个进程有自己独立的内存空间,一个进程包含了多个线程,所以线程又成为轻量级进程,这些线程有自己的计数器、栈空间和、局部变量等实现,而各个线程又共享进程的内存和资源。
进程是操作系统进行资源分配和调度的单位,而线程是是CPU调度和分派的基本单位,CPU通过对线程的高速切换让我们感觉这些线程在同时进行。

每个Java进程从main方法开始执行,这个时候启动的线程就是我们常说的主线程。其实在我们运行Java进程的时候,jvm给我们创建了很多其他线程,比如gc线程。我们可以通过ThreadMxBean查看线程的具体信息。

public class MultiThreads {

    public static void main (String[] args) {
        //获取线程管理类
        ThreadMXBean mThreadMXBean = ManagementFactory.getThreadMXBean();
        //获取所有的线程
        ThreadInfo[] threadInfos = mThreadMXBean.dumpAllThreads(true, true);

        if (threadInfos != null && threadInfos.length > 0) {
            for (ThreadInfo ti : threadInfos) {
                System.out.println(String.format("[%s] %s", ti.getThreadId(), ti.getThreadName()));
            }
        }
    }

}
运行结果:
[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main

二、如何创建并运行Java线程

在Java中有两种方式创建线程:
1、继承Thread并覆写父类的run方法
2、实现Runnable接口

启动线程都是执行Thread的start方法

public class ThreadInit {

    public static void main (String[] args) throws InterruptedException {

        //通过继承Thread类的方式新建线程
        Thread t1 = new Thread(){
            @Override
            public void run () {
                System.out.println("继承Thread类的线程运行中..");
            }

        };

        //通过实现Runnable接口的方式新建线程
        Thread t2 = new Thread(new MyRunnable());

        //多线程执行开始,实际是调用的run方法
        t1.start();
        t2.start();

        //主线程等待直到子线程执行完毕
        t1.join();
        t2.join();

        System.out.println("主线程运行完成..");

    }

    static final class MyRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println("实现Runnable接口的线程运行中..");
        }
    }

}
运行结果:
继承Thread类的线程运行中..
实现Runnable接口的线程运行中..
主线程运行完成..

对于这两种方式哪种好并没有一个确定的答案,它们都能满足要求。就我个人意见,我更倾向于实现Runnable接口这种方法。因为线程池可以有效的管理实现了Runnable接口的线程,如果线程池满了,新的线程就会排队等候执行,直到线程池空闲出来为止。而如果线程是通过实现Thread子类实现的,这将会复杂一些。(关于线程池的原理,后续会专门介绍)

三、线程的状态

通过查看Thread.State可以知道线程一共有6个状态:
1、NEW - 新建,表示线程构建了但没有调用start方法
2、RUNNABLE - 运行/就绪,线程就绪和运行统称为‘运行中’
3、BLOCKED - 阻塞,表示线程阻塞于锁
4、WAITING - 等待,表示线程进入等待状态,需要被其他线程通知或者中断
5、TIME_WAITING - 超时等待
6、TERMINATED - 终止,表示线程的run方法执行完毕
其中4和5属于一种类型,差别就是5指定了等待的最长时间,超时后状态会自动恢复。

public class ThreadStates {

    private static final Object lock = new Object();

    private static final Object lock2 = new Object();

    public static void main (String[] args) {

        Thread timeWaiting = new Thread(new TimeWaiting(), "TIME_WAITING_THREAD");
        timeWaiting.start();

        Thread waiting = new Thread(new Waiting(), "WAITING THREAD");
        waiting.start();

        //如下两个线程中,一个线程获取了锁未释放,另外一个线程阻塞于锁
        Thread blocked1 = new Thread(new Blocked(), "BLOCING_THREAD1");
        blocked1.start();
        Thread blocked2 = new Thread(new Blocked(), "BLOCING_THREAD2");
        blocked2.start();

    }
    //调用sleep(long)进入超时等待
    static final class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    //调用wait方法进入等待状态
    static final class Waiting implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //一个线程获取锁之后,未释放锁时其他线程阻塞于锁
    static final class Blocked implements Runnable {

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

}
通过jps查看java pid,使用jstack pid查看线程状态:
"BLOCING_THREAD2" #14 prio=5 os_prio=0 tid=0x0000000018ba9000 nid=0x256c waiting for monitor entry [0x0000000019a7e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.orange.threads.ThreadStates$Blocked.run(ThreadStates.java:64)
        - waiting to lock <0x00000000d5d8b008> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"BLOCING_THREAD1" #13 prio=5 os_prio=0 tid=0x0000000018ba8800 nid=0x1ff4 waiting on condition [0x000000001997f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.orange.threads.ThreadStates$Blocked.run(ThreadStates.java:64)
        - locked <0x00000000d5d8b008> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"WAITING THREAD" #12 prio=5 os_prio=0 tid=0x0000000018ba5800 nid=0x187a0 in Object.wait() [0x000000001987f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d8aff8> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.orange.threads.ThreadStates$Waiting.run(ThreadStates.java:49)
        - locked <0x00000000d5d8aff8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"TIME_WAITING_THREAD" #11 prio=5 os_prio=0 tid=0x0000000018ba3000 nid=0x207e0 waiting on condition [0x000000001977f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.orange.threads.ThreadStates$TimeWaiting.run(ThreadStates.java:36)
        at java.lang.Thread.run(Thread.java:745)

四、线程状态转换

如下是线程状态转换图


Java并发编程基础-线程简介_第1张图片
Java线程状态转换.png

从图中可以看出线程创建之后,
通过调用start()开始运行。
当主动调用wait()或者join()时线程进入等待状态。等待状态需要被其他线程的通知才能返回。
超时等待相当于在等待状态的基础上增加了一个超时限制,如果线程在限制时间内没有收到其他线程的通知,会主动回到运行态。
当现场执行同步方法时,如果没有获取到锁,此时线程会阻塞在临界区的入口。
线程执行run方法或者被中断之后进入终止状态。

总结

1、线程是轻量级的进程,线程是操作系统调度的最基本单位。
2、可以通过继承Thread并覆写run()方法构建一个线程,也可以实现Runnable接口初始化一个线程(推荐使用这种方式),通过调用start()方法执行线程。
3、线程存在6种状态:new、runnable、waiting、time_waiting、blocked、terminated
4、线程随着程序运行和系统调度,状态会不断发生变化。

你可能感兴趣的:(Java并发编程基础-线程简介)