美文网首页Java 杂谈Java开发之路
Java并发编程(一)Thread详解

Java并发编程(一)Thread详解

作者: Java中文社群_老王 | 来源:发表于2018-10-09 19:42 被阅读1次

    一、概述

    在开始学习Thread之前,我们先来了解一下 线程和进程之间的关系:

    线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。 线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。

    由上描述,可以得知线程作为cpu的基本调度单位,只有把多线程用好,才能充分利用cpu的多核资源。

    本文基于JDK 8(也可以叫JDK 1.8)。

    二、线程使用

    2.1 启动线程

    创建线程有四种方式:

    • 实现Runnable接口
    • 继承Thread类
    • 使用JDK 8 的Lambda
    • 使用Callable和Future

    2.1.1 Runnable创建方式

    public class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    Thread thread = new Thread(new MyThread());
    thread.start();
    

    2.1.2 继承Thread创建方式

    public class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    MyThread thread = new MyThread();
    thread.start();
    

    以上代码有更简单的写法,如下:

    Thread thread = new Thread(){
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    };
    thread.start();
    

    2.1.3 Lambda创建方式

    new Thread(()-> System.out.println(Thread.currentThread().getName())).start();
    

    2.1.4 使用Callable和Future

    看源码可以知道Thread的父类是Runnable是JDK1.0提供的,而Callable和Runnable类似,是JDK1.5提供的,弥补了调用线程没有返回值的情况,可以看做是Runnable的一个补充,下面看看Callable的实现。

    public class MyThread implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            System.out.println(Thread.currentThread().getName());
            return Thread.currentThread().getName();
        }
    }
    
    Callable<String> callable = new MyThread();
    FutureTask<String> ft = new FutureTask<>(callable);
    new Thread(ft,"threadName").start();
    System.out.println(ft.get());
    

    2.1.5 run()和start()的区别

    真正启动线程的是start()方法而不是run(),run()和普通的成员方法一样,可以重复使用,但不能启动一个新线程。

    2.2 Thread的常用方法

    Thread类方法

    方法 说明
    start() 启动线程
    setName(String name) 设置线程名称
    setPriority(int priority) 设置线程优先级,默认5,取值1-10
    join(long millisec) 挂起线程xx毫秒,参数可以不传
    interrupt() 终止线程
    isAlive() 测试线程是否处于活动状态

    Thread静态(static)方法

    方法 说明
    yield() 暂停当前正在执行的线程对象,并执行其他线程。
    sleep(long millisec)/sleep(long millis, int nanos) 挂起线程xx秒,参数不可省略
    currentThread() 返回对当前正在执行的线程对象的引用
    holdsLock(Object x) 当前线程是否拥有锁

    2.3 sleep()和wait()的区别

    sleep为线程的方法,而wait为Object的方法,他们的功能相似,最大本质的区别是:sleep不释放锁,wait释放锁。

    用法上的不同:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来终止线程;wait()可以用notify()/notifyAll()直接唤起。

    重点: 测试wait和sleep释放锁的代码如下:

    public class SynchronizedTest extends Thread {
        int number = 10;
        public synchronized void first(){
            System.out.println("this is first!");
            number = number+1;
        }
        public synchronized void secord() throws InterruptedException {
            System.out.println("this is secord!!");
            Thread.sleep(1000);
    //        this.wait(1000);
            number = number*100;
        }
        @Override
        public void run() {
            first();
        }
    }
    
    SynchronizedTest synchronizedTest = new SynchronizedTest();
    synchronizedTest.start();
    synchronizedTest.secord();
    // 主线程稍等10毫秒
    Thread.sleep(10);
    System.out.println(synchronizedTest.number);
    

    根据结果可以得知:

    • 执行sleep(1000)运行的结果是:1001
    • 执行wait(1000)运行的结果是:1100

    总结: 使用 sleep(1000)不释放同步锁,执行的是10*100+1=1001,wait(1000)释放了锁,执行的顺序是(10+1)x100=1100,所以sleep不释放锁,wait释放锁。

    三、线程状态

    3.1 线程状态概览

    线程状态:

    • NEW 尚未启动
    • RUNNABLE 正在执行中
    • BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
    • WAITING 永久等待状态
    • TIMED_WAITING 等待指定的时间重新被唤醒的状态
    • TERMINATED 执行完成

    线程的状态可以使用getState()查看,更多状态详情,查看Thread源码,如下图:

    image

    3.2 线程的状态代码实现

    3.2.1 NEW 尚未启动状态

    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    };
    // 只声明不调用start()方法,得到的状态是NEW
    System.out.println(thread.getState()); // NEW
    

    3.2.2 RUNNABLE 运行状态

    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    };
    thread.start();
    System.out.println(thread.getState()); // RUNNABLE
    

    3.2.3 BLOCKED 阻塞状态

    使用synchronized同步阻塞实现,代码如下:

    public class MyCounter {
        int counter;
        public synchronized void increase()  {
            counter++;
            try {
                Thread.sleep(10*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    MyCounter myCounter = new MyCounter();
    // 线程1调用同步线程,模拟阻塞
    new Thread(()-> myCounter.increase()).start();
    // 线程2继续调用同步阻塞方法
    Thread thread = new Thread(()-> myCounter.increase());
    thread.start();
    
    // 让主线程等10毫秒
    Thread.currentThread().sleep(10);
    // 打印线程2,为阻塞状态:BLOCKED
    System.out.println(thread.getState());
    

    3.2.4 WAITING 永久等待状态

    public class MyThread extends Thread{
        @Override
        public void run() {
            synchronized (MyThread.class){
                try {
                    MyThread.class.wait();
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    Thread thread = new Thread(new MyThread());
    thread.start();
    // 主线程挂起200毫秒,等thread执行完成
    Thread.sleep(200);
    // 输出WAITING,线程thread一直处于被挂起状态
    System.out.println(thread.getState());
    

    唤醒线程: 可使用 notify/notifyAll 方法,代码如下:

    synchronized (MyThread.class) {
        MyThread.class.notify();
    }
    

    使线程WAITING的方法:

    • Object的wait() 不设置超时时间
    • Thread.join()不设置超时时间
    • LockSupport的park()

    查看Thread源码可以知道Thread的join方法,底层使用的是Object的wait实现的,如下图:

    image

    注意: 查看Object的源码可知wait(),不传递参数,等同于wait(0),设置的“0”不是立即执行,而是无限的等待,不执行,如下图:

    image

    3.2.5 TIMED_WAITING 超时等待状态

    TIMED_WAITING状态,只需要给wait设置上时间即可,代码如下:

    public class MyThread extends Thread{
        @Override
        public void run() {
            synchronized (MyThread.class){
                try {
                    MyThread.class.wait(1000);
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    调用代码还是一样的,如下:

    Thread thread = new Thread(new MyThread());
    thread.start();
    // 主线程挂起200毫秒,等thread执行完成
    Thread.sleep(200);
    // 输出TIMED_WAITING
    System.out.println(thread.getState());
    synchronized (MyThread.class) {
        MyThread.class.notify();
    }
    

    3.2.6 TERMINATED 完成状态

    Thread thread = new Thread(()-> System.out.println(Thread.currentThread().getName()));
    thread.start();
    // 让主线程等10毫秒
    Thread.currentThread().sleep(10);
    System.out.println(thread.getState());
    

    四、死锁

    根据前面的知识,我们知道使用sleep的时候是不释放锁的,所以利用这个特性我们可以很轻易的写出死锁的代码,具体的流程如图(图片来源于杨晓峰老师文章):

    image

    代码如下:

    static  Object object1 = new Object();
    static  Object object2 = new Object();
    
    public static void main(String[] args) {
    
        Thread thread = new Thread(){
            @Override
            public void run() {
                synchronized (object1){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (object2){
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        };
    
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                synchronized (object2){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (object1){
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        };
    
        thread.start();
        thread2.start();
    

    运行上面的代码,程序会处于无限等待之中。

    五、总结

    根据上面的内容,我们已经系统的学习Thread的使用了,然而学而不思则罔,最后留一个思考题:根据本文介绍的知识,怎么能避免死锁?(哈哈,卖个关子,根据文章的知识点组合可以得出答案)

    源码下载:https://github.com/vipstone/java-core-example.git


    推荐部分

    本人最近看了前Oracle首席工程师杨晓峰的课程,也是第四部分引用的流程图的主人,感觉很不错,推荐给你,一起来系统的学习Java核心吧。

    image

    参考文档

    https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html

    相关文章

      网友评论

        本文标题:Java并发编程(一)Thread详解

        本文链接:https://www.haomeiwen.com/subject/bavyaftx.html