Java线程详解

Java线程的实现方式
  • 使用Thread类或继承Thread类

  • 实现Runnable接口配合Thread

  • 使用有返回值的Callable

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Object> future = executorService.submit(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        //TODO 线程业务代码
        return null;
     }
});
  • 使用lambda表达式

new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程,调用Thread#start启动线程最终都会调用Thread#run方法。

线程创建和启动流程

1)使用new Thread()创建一个线程,然后调用start()方法进行java层面的线程启动。

2)调用本地方法start0(),去调用JVM_StartThread方法进行线程创建和启动。

3)调用new JavaThread(&thread_entry,sz)进行线程的创建,并根据不同的操作系统平台调用对应的os::create_thread方法进行线程创建。

4)创建的线程状态为Initialized,调用sync->wait()的方法进行等待,等到被唤醒才继续执行thread ->run()。

5)调用Thread::start(native_thread)方法进行线程启动,此时将线程状态设置为RUNNABLE,接着调用os::start_thread(thread),根据不同的操作系统选择不同的线程启动方式。

6)线程启动之后的状态设置为RUNNABLE,并唤醒第4步种等待的线程,接着执行thread->run()方法。

7) JavaThread::run()方法会回调第1步new thread中复写的run()方法。

Java线程属于内核

内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤销、切换都是由内核实现。

用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。

Java线程的生命周期

Java语言中,线程共有6种状态:

  • NEW:初始化状态

  • RUNNABLE:可运行状态+运行状态

  • BLOCKED:阻塞状态

  • WAITING:无时限等待状态

  • TIMED_WAITING:有时限等待状态

  • TERMINATED:终止状态

在操作系统层面,Java线程中的BLOCKED、WAITING、TIMED_WAITING是一种状态(休眠状态),只要Java线程处于这三种状态之一,那么这个线程就永远没有CPU的使用权。
Java线程的调度机制

线程的调度机制是操作系统为线程分配处理器(CPU)使用权的过程,主要调度方式分为两种,分别是协同式线程调度抢占式线程调度

协同式线程调度:线程执行时间由线程本身来控制,线程把自己的工作执行完成之后,要主动的通知操作系统切换到另一个线程上。

抢占式线程调度:每个线程将由操作系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。

Java线程的调度机制就是抢占式线程调度。Java语言一共10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两线程同时处于ready状态时,优先级越高的线程越容易被系统选择执行。

优先级并不是很靠谱,因为Java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。
package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 模拟抢票案例,Java线程的抢占式调度方式
 */
public class SellTicketDemo implements Runnable {

    //票数
    private int ticket;

    public SellTicketDemo(int ticket) {
        this.ticket = ticket;
    }

    @Override
    public void run() {
        while (ticket > 0) {
            synchronized (this) {
                if (ticket > 0) {
                    //模拟抢票操作,耗时2毫秒
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":正在执行操作,余票:" + ticket--);
                }
            }
            //让出线程执行时间
            Thread.yield();
        }
    }

    public static void main(String[] args) {

        SellTicketDemo sellTicketDemo = new SellTicketDemo(1000);

        Thread thread1 = new Thread(sellTicketDemo,"thread1");
        Thread thread2 = new Thread(sellTicketDemo,"thread2");
        Thread thread3 = new Thread(sellTicketDemo,"thread3");
        Thread thread4 = new Thread(sellTicketDemo,"thread4");

        //设置线程优先级,默认是5,最低1,最好10
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MAX_PRIORITY);
        thread3.setPriority(Thread.MIN_PRIORITY);
        thread4.setPriority(Thread.MIN_PRIORITY);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}
Thread常用方法
sleep方法
  • 调用sleep方法会让当前线程从Running状态进入到TIMED_WAITING状态,不会释放对象锁。

  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptException,并且会清除中断标志。

  • 睡眠结束后的线程未必会立刻得到执行。

  • sleep当传入的参数为0时,和yield相同。

yield方法
  • yield会释放CPU资源,让当前线程从Running状态进入Runnable状态,让优先级更高(至少是相同)的线程获得执行的机会,不会释放对象锁。

  • 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程。

  • 具体的实现依赖于操作系统的任务调度器。

join方法

等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完成结果之后才能继续运行的场景。

package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 线程join调用案例
 */
public class ThreadJoinDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread begin...");

                try {
                    //模拟业务操作,耗时5s
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("thread finish ...");
            }
        });

        long start = System.currentTimeMillis();

        //启动线程
        thread.start();

        //主线程等待子线程执行完成
        thread.join();

        System.out.println("执行时间:" + (System.currentTimeMillis() - start));
        System.out.println("main finished");
    }
}
stop方法

stop()方法已经被jdk废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止,stop会释放对象锁,可能会造成数据不一致

package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 线程调用stop方法案例
 */
public class ThreadStopDemo {

    private static Object lock = new Object();

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

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " 获取锁");

                    //等待60s
                    try {
                        Thread.sleep(600000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " 释放锁");
            }
        }, "thread1");

        thread1.start();

        //主线程休眠2s,让线程1获得cpu分配执行
        Thread.sleep(2000);

        //停止thread1,并释放对象锁
        thread1.stop();

        //开启线程2获取对现实
        new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+" 等待获取锁");
            synchronized (lock){
                System.out.println(Thread.currentThread().getName()+" 获取锁");
            }
        },"thread2").start();
    }
}
Java线程的中断机制

Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制:是一种协作机制,即通过中断机制并不能直接终止另一个线程,而需要被中断的线程自己处理。被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

API的使用

  • interrupt():将线程的中断标志位设置为true,不会停止线程。

  • isInterrupt():判断当前线程的中断标志位是否为true,不会清除中断标志位。

  • Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中段标志位,重置为false。

package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 线程中断机制案例
 */
public class ThreadInterruptDemo {

    private static int i = 0;

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

        //创建一个线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(i++);
                    //Thread.interrupted() 清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("===================");
                    }

                    if(i == 10){
                        break;
                    }
                }
            }
        });

        thread.start();

        //不会停止线程thread,只会设置一个中断标志位flag=true
        thread.interrupt();
    }
}
利用中断机制优雅停止线程
package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 利用线程中断机制,优雅停止线程
 */
public class StopThreadDemo {

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

        Thread thread = new Thread(new Runnable() {

            int count = 0;

            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted() && count < 10000) {
                    System.out.println("count = " + count++);
                }
                System.out.println("====线程停止=======");
            }
        });

        thread.start();

        Thread.sleep(5);
        thread.interrupt();
    }
}
sleep期间能否感受到中断
package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 利用线程中断机制,优雅停止线程
 */
public class StopThreadDemo {

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

        Thread thread = new Thread(new Runnable() {

            int count = 0;

            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted() && count < 1000) {
                    System.out.println("count = " + count++);
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        System.out.println(e);
                        e.printStackTrace();
                    }
                }
                System.out.println("====线程停止=======");
            }
        });

        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

执行结果:

count = 998
count = 999
====线程停止=======
处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样就会导致while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个条件时退出。如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止。
try {
   Thread.sleep(1);
 } catch (InterruptedException e) {
    e.printStackTrace();
    //重新设置线程中断状态为true
   Thread.currentThread().interrupt();
 }
Java线程间通讯
volatile 可见性

volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。

package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * volatile可见性实现线程间通讯
 */
public class VolatileDemo {

    private static volatile boolean flag = true;

    public static void main(String[] args) {

        new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + " turn on");
                    flag = false;
                }
            }
        }, "Thread-1").start();

        new Thread(() -> {
            while (true) {
                if (!flag) {
                    System.out.println(Thread.currentThread().getName() + " turn off");
                    flag = true;
                }
            }
        }, "Thread-2").start();
    }
等待唤醒(等待通知)机制

等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,线程将进入等待队列进行等待直到被唤醒。

package com.warrior.juc.threadbase;

/**
 * @Author warrior
 * 等待唤醒机制实现线程间通讯
 */
public class WaitDemo {

    private static Object lock = new Object();

    private static boolean flag = true;

    public static void main(String[] args) {

        new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (flag) {
                        System.out.println(Thread.currentThread().getName() + " turn on .......");
                        try {
                            Thread.sleep(100);
                            flag = false;
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "Thread-turn-on").start();

        new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (!flag) {
                        System.out.println(Thread.currentThread().getName() + " turn off .......");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        flag = true;
                        lock.notify();
                    }
                }
            }
        }, "Thread-turn-off").start();
    }
}

LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待许可,调用unpark则为指定线程提供许可,使用它可以在任何场合使用线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,注意连续多次唤醒的效果和一次唤醒是一样的。

package com.warrior.juc.threadbase;

import java.util.concurrent.locks.LockSupport;

/**
 * @Author warrior
 * LockSupport 实现线程间通讯案例
 */
public class LockSupportDemo {

    private static boolean flag = true;

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

        TurnOnThread turnOnThread = new TurnOnThread();
        TurnOffThread turnOffThread = new TurnOffThread();

        turnOnThread.setTurnOffThread(turnOffThread);
        turnOffThread.setTurnOnThread(turnOnThread);

        turnOnThread.start();
        Thread.sleep(100);
        turnOffThread.start();
    }

    static class TurnOnThread extends Thread {

        private Thread turnOffThread;

        public void setTurnOffThread(Thread turnOffThread) {
            this.turnOffThread = turnOffThread;
        }

        @Override
        public void run() {
            if (flag) {
                System.out.println(Thread.currentThread().getName() + " turn on .......");
                flag = false;
                //阻塞当前线程
                LockSupport.park();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //唤醒turnOffThread
                LockSupport.unpark(turnOffThread);
            }
        }
    }

    static class TurnOffThread extends Thread {

        private Thread turnOnThread;

        public void setTurnOnThread(Thread turnOnThread) {
            this.turnOnThread = turnOnThread;
        }

        @Override
        public void run() {
            if (!flag) {
                System.out.println(Thread.currentThread().getName() + " turn off .......");
                flag = true;
                //唤醒turnOnThread
                LockSupport.unpark(turnOnThread);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //阻塞当前线程
                LockSupport.park();
            }
        }
    }

}
管道输入输出流

管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。

Thread.join

join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现其实是基于等待通知机制的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值