线程创建

Java中有三种线程创建方式,分别为:
1实现Runnable接口的run方法,
2继承Thread类并重写run的方法,
3使用FutureTask方式。

继承Thread类

Java中通过继承java.lang.Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

Thread的构造函数如下:

  1. Thread()
  2. Thread(Runnable target)
  3. Thread(ThreadGroup group, Runnable target)

eg:

  1. package threadcore.createthread;
  2. public class ThreadType extends Thread {
  3. @Override
  4. public void run() {
  5. System.out.println("用thread类实现线程");
  6. }
  7. public static void main(String[] args) {
  8. ThreadType t = new ThreadType();
  9. t.start();
  10. }
  11. }

如果没有为线程显式地指定一个名字,那么线程将会以“ Thread-”作为前缀与一个自 增数字进行组合,这个自增数字在 整个 JVM 进程中将会不断自增:

  1. public Thread() {
  2. init(null, null, "Thread-" + nextThreadNum(), 0);
  3. }

用无参的构造函数创建了 5 个线程,并且分别输出线程的名字

  1. class MyThread extends Thread{
  2. @Override
  3. public void run() {
  4. System.out.println(Thread.currentThread().getName());
  5. }
  6. }
  7. public class CreateThread {
  8. public static void main(String[] args) {
  9. for(int i = 0;i<5;i++){
  10. MyThread myThread = new MyThread();
  11. myThread.start();
  12. }
  13. }
  14. }

打印结果

  1. Thread-0
  2. Thread-1
  3. Thread-2
  4. Thread-3
  5. Thread-4

线程初始化函数Thread 的所有构造函数,最终都会去调用一个 init方法

  1. private void init(ThreadGroup g, Runnable target, String name,
  2. long stackSize, AccessControlContext acc) {
  3. if (name == null) {
  4. throw new NullPointerException("name cannot be null");
  5. }
  6. this.name = name;
  7. Thread parent = currentThread();
  8. SecurityManager security = System.getSecurityManager();
  9. if (g == null) {
  10. /* Determine if it's an applet or not */
  11. /* If there is a security manager, ask the security manager
  12. what to do. */
  13. if (security != null) {
  14. g = security.getThreadGroup();
  15. }
  16. /* If the security doesn't have a strong opinion of the matter
  17. use the parent thread group. */
  18. if (g == null) {
  19. g = parent.getThreadGroup();
  20. }
  21. }
  22. /* checkAccess regardless of whether or not threadgroup is
  23. explicitly passed in. */
  24. g.checkAccess();
  25. /*
  26. * Do we have the required permissions?
  27. */
  28. if (security != null) {
  29. if (isCCLOverridden(getClass())) {
  30. security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
  31. }
  32. }
  33. g.addUnstarted();
  34. this.group = g;
  35. this.daemon = parent.isDaemon();
  36. this.priority = parent.getPriority();
  37. if (security == null || isCCLOverridden(parent.getClass()))
  38. this.contextClassLoader = parent.getContextClassLoader();
  39. else
  40. this.contextClassLoader = parent.contextClassLoader;
  41. this.inheritedAccessControlContext =
  42. acc != null ? acc : AccessController.getContext();
  43. this.target = target;
  44. setPriority(priority);
  45. if (parent.inheritableThreadLocals != null)
  46. this.inheritableThreadLocals =
  47. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  48. /* Stash the specified stack size in case the VM cares */
  49. this.stackSize = stackSize;
  50. /* Set thread ID */
  51. tid = nextThreadID();
  52. }

Thread没有执行 start方法之前,它只能算是一个 Thread 的实例,并不意味着一个新的线程被创建,因此 currentThread()代表的将会是创建它的那个线程,因此我们可以得出以下结论 :
1.一个线程的创建肯定是由另一个线程完成的 。被创建线程的父线程是创建它的线程 。
2 如果在构造 Thread 的时候没有显示地指定 一个 ThreadGroup,那么子线程将会被加入父线程所在的线程组

实现Runable接口

一个类仅需实现一个run()的简单方法,该方法声明如下:

  1. public void run( )

eg:

  1. package threadcore.createthread;
  2. public class RunnableType implements Runnable {
  3. @Override
  4. public void run() {
  5. Thread thread = new Thread(new RunnableType());
  6. thread.start();
  7. }
  8. public static void main(String[] args) {
  9. System.out.println("实现Runnable接口创建thread");
  10. }
  11. }

在run()中可以定义代码来构建新的线程。run()方法能够像主线程那样调用其他方法,引用其他类,声明变量。仅有的不同是run()在程序中确立另一个并发的线程执行入口。当run()返回时,该线程结束。

  1. public static class ThreadRunner implements Runnable{
  2. @Override
  3. public void run() {
  4. for (int i =0;i<10;i++){
  5. System.out.printf("%s start %d run\n", Thread.currentThread().getName(),i);
  6. try {
  7. long sleepTime = (long) Math.random()*1000;
  8. Thread.sleep(sleepTime);
  9. }catch (InterruptedException e){
  10. }
  11. }
  12. System.out.println("current thread finish "+Thread.currentThread().getName());
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Thread t1 = new Thread(new ThreadRunner(),"Thread-0");
  17. t1.start();
  18. Thread t2 = new Thread(new ThreadRunner(),"Thread-1");
  19. t2.start();
  20. }
  1. public synchronized void start() {
  2.      //判断是否首次启动
  3. if (threadStatus != 0)
  4. throw new IllegalThreadStateException();
  5. group.add(this);
  6. boolean started = false;
  7. try {
  8.        //启动线程
  9. start0();
  10. started = true;
  11. } finally {
  12. try {
  13. if (!started) {
  14. group.threadStartFailed(this);
  15. }
  16. } catch (Throwable ignore) {
  17. /* do nothing. If start0 threw a Throwable then
  18. it will be passed up the call stack */
  19. }
  20. }
  21. }
  22. private native void start0();

FutureTask的方式

图片.png在main函数内首先创建了一个FutrueTask对象(构造函数为CallerTask的实例),然后使用创建的FutrueTask对象作为任务创建了一个线程并且启动它,最后通过futureTask.get()等待任务执行完毕并返回结果。

Lambda匿名内部类

  1. public class AnonymousInnerThread {
  2. public static void main(String[] args) {
  3. new Thread(){
  4. @Override
  5. public void run() {
  6. System.out.println(Thread.currentThread().getName());
  7. }
  8. }.start();
  9. new Thread(new Runnable() {
  10. @Override
  11. public void run() {
  12. System.out.println(Thread.currentThread().getName());
  13. }
  14. }).start();
  15. }
  16. }

Lambda表达式

  1. public class LambdaThread {
  2. public static void main(String[] args) {
  3. new Thread(()-> System.out.println(Thread.currentThread().getName())).start();
  4. }
  5. }

Thread方法

方法声明 功能描述
sleep(long millis) 线程休眠,让当前线程暂停,进入休眠等待状态
join() 线程加入,等待目标线程执行完之后再继续执行,调用该方法的线程会插入优先先执行
yield() 线程礼让,暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(boolean on) 将该线程标记为守护线程(后台线程)或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
stop(),suspend(),resume() 让线程停止,已经废弃方法,建议不要使用
interrupt() 中断线程。 把线程的状态终止,并抛出一个InterruptedException。
setPriority(int newPriority) 更改线程的优先级
isInterrupted() 线程是否被中断

currentThread

�获取当前执行的线程

  1. public class CurrentThread implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println(Thread.currentThread().getName());
  5. }
  6. public static void main(String[] args) {
  7. new CurrentThread().run();
  8. new Thread(new CurrentThread()).start();
  9. new Thread(new CurrentThread()).start();
  10. }
  11. }

获取当前线程的id

  1. public class Id {
  2. public static void main(String[] args) {
  3. System.out.println("主线程的ID:"+Thread.currentThread().getId());
  4. Thread one = new Thread();
  5. System.out.println("子线程的ID:"+one.getId());
  6. Thread two = new Thread();
  7. System.out.println("子线程的ID:"+two.getId());
  8. }
  9. }

线程执行

start

启动新线程
下面是start方法的源码

  1. public synchronized void start() {
  2. if (threadStatus != 0)
  3. throw new IllegalThreadStateException();
  4. group.add(this);
  5. boolean started = false;
  6. try {
  7. start0();
  8. started = true;
  9. } finally {
  10. try {
  11. if (!started) {
  12. group.threadStartFailed(this);
  13. }
  14. } catch (Throwable ignore) {
  15. }
  16. }
  17. }
  18. private native void start0();

启动新线程检查线程状态,如果多次调用会出现错误
加入线程组
调用start0()

run

run 方法源码

  1. public void run() {
  2. if (target != null) {
  3. target.run();
  4. }
  5. }

start方法和run 方法对比:

  1. 执行start方法,是用来启动线程的,此时线程处于就绪状态,获得调度后运行run方法。run方法执行结束,线程就结束。
  2. 执行run方法,相对于普通方法调用,在主线程调用。程序是顺序执行的,执行完才会执行下面的程序。 ```java public class StartThread extends Thread{ public static void main(String[] args) {

    1. StartThread thread = new StartThread();
    2. thread.run();
    3. thread.start();

    }

    @Override public void run() {

    1. System.out.println(Thread.currentThread().getName());

    } }

  1. 打印结果
  2. ```java
  3. main
  4. Thread-0

线程中断

sleep

sleep是一个静态方法,其有两个重载方法 , 其中一个需要传入毫秒数 , 另外一个既需要毫秒数也需要纳秒数 。

  1. public static void sleep(long millis) throws InterruptedException
  2. public static void sleep(long millis, int nanos) throws InterruptedException

sleep 方法会使当前线程进入指定毫秒数的休眠,暂停执行,Thread.sleep只会导致当前线程进入指定时间的休眠,但是其不会放弃 monitor锁的所有权,如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

  1. public class CreateThread {
  2. public static void main(String[] args) {
  3. new Thread(() -> {
  4. long startTime = System.currentTimeMillis();
  5. sleep(2000);
  6. long endTime = System.currentTimeMillis();
  7. System.out.println(String.format("total time: %d", endTime - startTime));
  8. }).start();
  9. long startTime = System.currentTimeMillis();
  10. sleep(1000);
  11. long endTime = System.currentTimeMillis();
  12. System.out.println(String.format("main total time: %d", endTime - startTime));
  13. }
  14. private static void sleep(long ms) {
  15. try {
  16. Thread.sleep(ms);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }

在 JDKl.5 以后,引入了一个枚举 TimeUnit,其对 sleep方法提供了很好的封装, 使用它可以省去时间单位的换算步

  1. private static void sleep(long ms) {
  2. try {
  3. TimeUnit.SECONDS.sleep(ms);
  4. } catch (InterruptedException e) {
  5. e.printStackTrace();
  6. }
  7. }

sleep方法响应中断:
1 抛出InterruptedException
2 清除中断状态
总结:sleep 方法可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后在执行,休眠期间如果被中断,会抛出异常并清除中断状态

interrupt

此操作会将线程的中断标志位置位,至于线程作何动作那要看线程了。

  • 如果线程sleep()、wait()、join()等处于阻塞状态,那么线程会定时检查中断状态位如果发现中断状态位为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断状态位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
  • 如果线程正在运行、争用synchronized、lock()等,那么是不可中断的,他们会忽略。

可以通过以下三种方式来判断中断:
1)isInterrupted()
此方法只会读取线程的中断标志位,并不会重置。
2)interrupted()
此方法读取线程的中断标志位,并会重置。
3)throw InterruptException
抛出该异常的同时,会重置中断标志位。
线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出了!至于目标线程接收到通知之后如何处理,则完全由目标线程自己决定,这点很重要,如果中断后,线程立即无条件退出,我们又会到stop方法的老问题。
Thread提供了3个与线程中断有关的方法,这3个方法容易混淆,大家注意下:

  1. public void interrupt() //中断线程
  2. public boolean isInterrupted() //判断线程是否被中断
  3. public static boolean interrupted() //判断线程是否被中断,并清除当前中断状态
  1. public static void main(String[] args) throws InterruptedException {
  2. Thread thread = new Thread(){
  3. @Override
  4. public void run() {
  5. System.out.println("start");
  6. while (true){
  7. if (this.isInterrupted()){
  8. System.out.println("interrupt");
  9. break;
  10. }
  11. }
  12. }
  13. };
  14. thread.start();
  15. TimeUnit.SECONDS.sleep(1);
  16. thread.interrupt();
  17. System.out.println(thread.getState());
  18. //当前线程休眠1秒
  19. TimeUnit.SECONDS.sleep(1);
  20. //输出线程thread1的状态
  21. System.out.println(thread.getState());
  22. }

sleep方法由于中断而抛出异常之后,线程的中断标志会被清除(置为false)

  1. public static void main(String[] args) throws InterruptedException {
  2. Thread thread = new Thread(){
  3. @Override
  4. public void run() {
  5. System.out.println("start");
  6. while (true){
  7. try {
  8. TimeUnit.SECONDS.sleep(100);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. if (this.isInterrupted()) {
  13. System.out.println("end");
  14. break;
  15. }
  16. }
  17. }
  18. };
  19. thread.start();
  20. TimeUnit.SECONDS.sleep(1);
  21. thread.interrupt();
  22. }

异常中需要执行this.interrupt()方法,将中断标志位置为true,下面程序会在一毫秒时发送中断异常

  1. import java.util.concurrent.TimeUnit;
  2. public class StopThreadWithoutSleep implements Runnable {
  3. @Override
  4. public void run() {
  5. int i = 0;
  6. while (i<=10000){
  7. if (!Thread.currentThread().isInterrupted()&&i%3==0){
  8. System.out.println(i+"是3的倍数");
  9. }
  10. i++;
  11. }
  12. }
  13. public static void main(String[] args) throws InterruptedException {
  14. Thread thread = new Thread(new StopThreadWithoutSleep());
  15. thread.start();
  16. TimeUnit.MILLISECONDS.sleep(1);
  17. thread.interrupt();
  18. }
  19. }

eg

  1. public class JoinInterrupt {
  2. public static void main(String[] args) {
  3. Thread mainThread = Thread.currentThread();
  4. Thread oneThread = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. try {
  8. mainThread.interrupt();
  9. Thread.sleep(5000);
  10. System.out.println("Thread-1 finished.");
  11. } catch (InterruptedException e) {
  12. System.out.println("子线程中断");
  13. }
  14. }
  15. });
  16. oneThread.start();
  17. System.out.println("等待子线程运行完毕");
  18. try {
  19. oneThread.join();
  20. } catch (InterruptedException e) {
  21. System.out.println(Thread.currentThread().getName()+"主线程中断了");
  22. oneThread.interrupt();
  23. }
  24. System.out.println("子线程已运行完毕");
  25. }
  26. }

执行结果

  1. 等待子线程运行完毕
  2. main主线程中断了
  3. 子线程已运行完毕
  4. 子线程中断

yield

yield是谦让的意思

  1. public static native void yield();

线程通信

Object中有几个用于线程同步的方法:wait、notify、notifyAll。

  1. public class Object {
  2. public final native void wait(long timeout) throws InterruptedException;
  3. public final native void notify();
  4. public final native void notifyAll();
  5. }
方法 说明
wait wait会释放当前锁 直到以下4种情况之一发生的时候才会被唤醒:
1. 另外一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程;
1. 另外一个线程调用这个对象的notifyAll()方法;
1. 过了wait(long timeout)规定的超时时间,如果参数是0永久等待
1. 线程自身调用了interrupt()
notify 任意选择一个(无法控制选哪个)正在这个对象上等待的线程把它唤醒,其它线程依然在等待被唤醒
notifyAll 唤醒所有线程,让它们去竞争,不过也只有一个能抢到锁

wait

wait 演示代码如下

  1. import java.util.concurrent.TimeUnit;
  2. public class Wait {
  3. public static void main(String[] args)throws InterruptedException {
  4. final Object object = new Object();
  5. Thread waitThread = new Thread(){
  6. @Override
  7. public void run() {
  8. synchronized (object) {
  9. System.out.println(Thread.currentThread().getName()+"开始执行");
  10. try {
  11. System.out.println(Thread.currentThread().getName()+"准备释放锁");
  12. object.wait();
  13. System.out.println(Thread.currentThread().getName()+"再次获取到锁");
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println(Thread.currentThread().getName()+"结束执行");
  18. }
  19. }
  20. };
  21. Thread notifyThread = new Thread(){
  22. @Override
  23. public void run() {
  24. synchronized (object) {
  25. System.out.println(Thread.currentThread().getName()+"开始执行");
  26. object.notify();
  27. System.out.println(Thread.currentThread().getName()+"结束执行");
  28. }
  29. }
  30. };
  31. waitThread.start();
  32. TimeUnit.MILLISECONDS.sleep(100);
  33. notifyThread.start();
  34. }
  35. }

最终执行结果

  1. Thread-0开始执行
  2. Thread-0准备释放锁
  3. Thread-1开始执行
  4. Thread-1结束执行
  5. Thread-0再次获取到锁
  6. Thread-0结束执行

sleep() wait()两者主要的区别:

  1. 所属类不同,sleep()方法是Thread类的静态方法,而wait是Object类实例方法
  2. 同步方法处理,wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
  3. 是否释放锁和指定时间,sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

总结:Object.wait()方法和Thread.sleeep()方法都可以让现场等待若干时间。除wait()方法可以被唤醒外,另外一个主要的区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放锁。

notify

notify:唤醒一个线程,其他线程依然处于wait的等待唤醒状态,如果被唤醒的线程结束时没调用notify,其他线程就永远没人去唤醒,只能等待超时,或者被中断

  1. import java.util.Date;
  2. import java.util.concurrent.TimeUnit;
  3. import java.util.concurrent.atomic.AtomicInteger;
  4. public class WaitNotify {
  5. private AtomicInteger count = new AtomicInteger();
  6. public static void main(String[] args) throws InterruptedException {
  7. WaitNotify instance = new WaitNotify();
  8. for (int i =0;i<10;i++){
  9. new Thread(instance::waitThread).start();
  10. }
  11. TimeUnit.MILLISECONDS.sleep(100);
  12. for(int i =0;i<5;i++){
  13. instance.notifyThread();
  14. }
  15. }
  16. private synchronized void notifyThread(){
  17. notify();
  18. }
  19. private synchronized void waitThread(){
  20. try{
  21. log("wait开始");
  22. wait();
  23. log("wait结束");
  24. }catch (InterruptedException e){
  25. e.printStackTrace();
  26. }
  27. }
  28. private void log(String s) {
  29. System.out.println(count.incrementAndGet() + " "
  30. + new Date().toString().split(" ")[3]
  31. + "\t" + Thread.currentThread().getName() + " " + s);
  32. }
  33. }

程序执行结果,例子中有10个线程在wait,但notify了5次,然后其它线程一直阻塞,这也就说明使用notify时如果不能准确控制和wait的线程数对应,可能会导致某些线程永远阻塞。

  1. 1 21:21:34 Thread-0 wait开始
  2. 2 21:21:34 Thread-9 wait开始
  3. 3 21:21:34 Thread-8 wait开始
  4. 4 21:21:34 Thread-7 wait开始
  5. 5 21:21:34 Thread-6 wait开始
  6. 6 21:21:34 Thread-5 wait开始
  7. 7 21:21:34 Thread-4 wait开始
  8. 8 21:21:34 Thread-3 wait开始
  9. 9 21:21:34 Thread-2 wait开始
  10. 10 21:21:34 Thread-1 wait开始
  11. 11 21:21:34 Thread-0 wait结束
  12. 12 21:21:34 Thread-6 wait结束
  13. 13 21:21:34 Thread-7 wait结束
  14. 14 21:21:34 Thread-8 wait结束
  15. 15 21:21:34 Thread-9 wait结束

eg:

  1. public class Wait {
  2. public static Object object = new Object();
  3. public static void main(String[] args) throws InterruptedException {
  4. Thread zeroThread = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. synchronized (object) {
  8. System.out.println(Thread.currentThread().getName() + "开始执行了");
  9. try {
  10. object.wait();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
  15. }
  16. }
  17. });
  18. Thread oneThread = new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. synchronized (object) {
  22. object.notify();
  23. System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
  24. }
  25. }
  26. });
  27. zeroThread.start();
  28. Thread.sleep(200);
  29. oneThread.start();
  30. }
  31. }

执行结果

  1. Thread-0开始执行了
  2. 线程Thread-1调用了notify()
  3. 线程Thread-0获取到了锁。

notifyAll

所有线程退出wait的状态,开始竞争锁,但只有一个线程能抢到,这个线程执行完后,其他线程又会有一个线程而出得到锁

  1. import java.util.Date;
  2. import java.util.concurrent.TimeUnit;
  3. import java.util.concurrent.atomic.AtomicInteger;
  4. public class WaitNotifyAll {
  5. private AtomicInteger count = new AtomicInteger();
  6. public static void main(String[] args) throws InterruptedException {
  7. WaitNotifyAll instance = new WaitNotifyAll();
  8. for(int i =0;i<10;i++){
  9. new Thread(instance::waitThread).start();
  10. }
  11. TimeUnit.MILLISECONDS.sleep(100);
  12. instance.notifyThread();
  13. }
  14. private synchronized void notifyThread(){
  15. notifyAll();
  16. }
  17. private synchronized void waitThread(){
  18. try{
  19. log("wait开始");
  20. wait();
  21. log("wait结束");
  22. }catch (InterruptedException e){
  23. e.printStackTrace();
  24. }
  25. }
  26. private void log(String s) {
  27. System.out.println(count.incrementAndGet() + " "
  28. + new Date().toString().split(" ")[3]
  29. + "\t" + Thread.currentThread().getName() + " " + s);
  30. }
  31. }

程序执行结果

  1. 1 21:28:07 Thread-0 wait开始
  2. 2 21:28:07 Thread-9 wait开始
  3. 3 21:28:07 Thread-8 wait开始
  4. 4 21:28:07 Thread-7 wait开始
  5. 5 21:28:07 Thread-6 wait开始
  6. 6 21:28:07 Thread-5 wait开始
  7. 7 21:28:07 Thread-4 wait开始
  8. 8 21:28:07 Thread-3 wait开始
  9. 9 21:28:07 Thread-2 wait开始
  10. 10 21:28:07 Thread-1 wait开始
  11. 11 21:28:07 Thread-1 wait结束
  12. 12 21:28:07 Thread-2 wait结束
  13. 13 21:28:07 Thread-3 wait结束
  14. 14 21:28:07 Thread-4 wait结束
  15. 15 21:28:07 Thread-5 wait结束
  16. 16 21:28:07 Thread-6 wait结束
  17. 17 21:28:07 Thread-7 wait结束
  18. 18 21:28:07 Thread-8 wait结束
  19. 19 21:28:07 Thread-9 wait结束
  20. 20 21:28:07 Thread-0 wait结束

总结
wait(),notify(),notifyAll()可以这么理解,obj对象上有2个队列,如图1,q1:等待队列,q2:准备获取锁的队列;两个队列都为空。
线程Thread - 图2
obj.wait()过程:

  1. synchronize(obj){
  2. obj.wait();
  3. }

假如有3个线程,t1、t2、t3同时执行上面代码,t1、t2、t3会进入q2队列,如图2,进入q2的队列的这些线程才有资格去争抢obj的锁,假设t1争抢到了,那么t2、t3机型在q2中等待着获取锁,t1进入代码块执行wait()方法,此时t1会进入q1队列,然后系统会通知q2队列中的t2、t3去争抢obj的锁,抢到之后过程如t1的过程。最后t1、t2、t3都进入了q1队列,如图3。
线程Thread - 图3
线程Thread - 图4
上面过程之后,又来了线程t4执行了notify()方法,如下:

  1. synchronize(obj){
  2. obj.notify();
  3. }

t4会获取到obj的锁,然后执行notify()方法,系统会从q1队列中随机取一个线程,将其加入到q2队列,假如t2运气比较好,被随机到了,然后t2进入了q2队列,如图4,进入q2的队列的锁才有资格争抢obj的锁,t4线程执行完毕之后,会释放obj的锁,此时队列q2中的t2会获取到obj的锁,然后继续执行,执行完毕之后,q1中包含t1、t3,q2队列为空,如图5
线程Thread - 图5
线程Thread - 图6
接着又来了个t5队列,执行了notifyAll()方法,如下:

  1. synchronize(obj){
  2. obj.notify();
  3. }

2.调用obj.wait()方法,当前线程会加入队列queue1,然后会释放obj对象的锁
t5会获取到obj的锁,然后执行notifyAll()方法,系统会将队列q1中的线程都移到q2中,如图6,t5线程执行完毕之后,会释放obj的锁,此时队列q2中的t1、t3会争抢obj的锁,争抢到的继续执行,未增强到的带锁释放之后,系统会通知q2中的线程继续争抢索,然后继续执行,最后两个队列中都为空了。
线程Thread - 图7

生产者消费者模式

  1. package com.github.thread.objectthread;
  2. import java.util.LinkedList;
  3. import java.util.Random;
  4. class EventStorage {
  5. private Integer maxSize;
  6. private LinkedList<Integer> storage;
  7. private Random random;
  8. public EventStorage() {
  9. maxSize = 10;
  10. storage = new LinkedList<>();
  11. random = new Random();
  12. }
  13. public synchronized void put() {
  14. while (storage.size() == maxSize) {
  15. try {
  16. wait();
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. Integer p = Integer.valueOf(random.nextInt(100) + 1);
  22. storage.add(p);
  23. System.out.println("生产:" + p + "\t 总共" + storage.size() + "个");
  24. notify();
  25. }
  26. public synchronized void take() {
  27. while (storage.size() == 0) {
  28. try {
  29. wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. System.out.println("消费:" + storage.poll() + "\t 剩下" + storage.size() + "个");
  35. notify();
  36. }
  37. }
  38. class Producer implements Runnable {
  39. private EventStorage storage;
  40. public Producer(EventStorage storage) {
  41. this.storage = storage;
  42. }
  43. @Override
  44. public void run() {
  45. for (int i = 0; i < 100; i++) {
  46. storage.put();
  47. }
  48. }
  49. }
  50. class Consumer implements Runnable {
  51. private EventStorage storage;
  52. public Consumer(EventStorage storage) {
  53. this.storage = storage;
  54. }
  55. @Override
  56. public void run() {
  57. for (int i = 0; i < 100; i++) {
  58. storage.take();
  59. }
  60. }
  61. }
  62. public class ProducerConsumerModel {
  63. public static void main(String[] args) {
  64. EventStorage storage = new EventStorage();
  65. Producer producer = new Producer(storage);
  66. Consumer consumer = new Consumer(storage);
  67. new Thread(producer).start();
  68. new Thread(consumer).start();
  69. }
  70. }

Thread.join

方法join的作用是使所属的线程对象正常执行 run() 方法中的任务, 而使当前线程进行无限期(或指定时间)的阻塞, 通俗的作用就是等待方法join所属线程执行完后再继续执行当前线程后续的代码; 实际上就是抢占。

  1. join()
  2. join(long millis) //参数为毫秒
  3. join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:
1)等待时间到;
2)目标线程已经run完(通过isAlive()来判断)。

  1. import java.util.concurrent.TimeUnit;
  2. public class Join implements Runnable{
  3. @Override
  4. public void run() {
  5. try {
  6. TimeUnit.SECONDS.sleep(1);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println(Thread.currentThread().getName()+"\tfinish");
  11. }
  12. public static void main(String[] args) throws InterruptedException {
  13. Join joinRunnable = new Join();
  14. Thread threadOne = new Thread(joinRunnable);
  15. Thread threadTwo = new Thread(joinRunnable);
  16. threadOne.start();
  17. threadTwo.start();
  18. threadTwo.join();
  19. threadOne.join();
  20. System.out.println(Thread.currentThread().getName()+"\tfinish");
  21. }
  22. }

执行结果

  1. Thread-0 finish
  2. Thread-1 finish
  3. main finish

如果我们把线程的下面的join方法 语句去掉

  1. threadTwo.join();
  2. threadOne.join();

执行可能结果如下,主线程一般会先执行

  1. main finish
  2. Thread-1 finish
  3. Thread-0 finish

eg:

  1. public class Join {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread one = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. System.out.println(Thread.currentThread().getName() + "执行完毕");
  12. }
  13. });
  14. Thread two = new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. try {
  18. Thread.sleep(1000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println(Thread.currentThread().getName() + "执行完毕");
  23. }
  24. });
  25. one.start();
  26. two.start();
  27. System.out.println("开始等待子线程运行完毕");
  28. one.join();
  29. two.join();
  30. System.out.println("所有子线程执行完毕");
  31. }
  32. }

最终执行结果可能是

  1. 开始等待子线程运行完毕
  2. Thread-0执行完毕
  3. Thread-1执行完毕
  4. 所有子线程执行完毕

或者是

  1. 开始等待子线程运行完毕
  2. Thread-1执行完毕
  3. Thread-0执行完毕
  4. 所有子线程执行完毕


线程生命周期

image.png

线程状态 状态说明
新建状态 等待状态,调用start()方法启动
就绪状态 有执行资格,但是没有执行权
运行状态 具有执行资格和执行权
阻塞状态 遇到sleep()方法和wait()方法时,失去执行资格和执行权,sleep()方法时间到或者调用notify()方法时,获取执行资格,变为临时状态
死亡状态 中断线程,或者run()方法结束

NEW

关键字new 创建一个Thread 对象,此时它并不处于执行状态,因为在没有Start之前,该线程根本不存在,和使用关键字new 创建一个普通的Java对象没有什么区别。

RUNNABLE

线程被创建等待执行的状态称为可执行RUNNABLE,表示线程已经被创建,正在等待系统调度分配CPU使用权。线程对象进入RUNNABLE状态必须调用start方法,此时才真正在JVM进程中创建了一个线程,注意线程的启动并不代表理解开始执行,线程的运行与否和进程一样都要听命于CPU的调度。由于存在 Running状态,所以不会直接进入 BLOCKED 状态和 TERMINATED 状态,即使是在线程的执行逻辑中调用wait、 sleep或者其他block的IO操作等, 也必须先获得 CPU 的调度执行权才可以,严格来讲, RUNNABLE 的线程只能意外终止或者进入 RUNNING 状态 。

RUNNING

一旦 CPU 通过轮询或者其他方式从任务可执行队列中选中了线程,那么此时它才能真 正地执行自己的逻辑代码,需要说明的一点是一个正在 RUNNING 状态的线程事实上也是 RUNNABLE 的,但是反过来则不成立 。
在该状态中,线程的状态可以发生如下的状态转换 。

  1. 直接进入 TERMINATED 状态,比如调用 JDK 已 经不推荐使用 的 stop 方 法或者 判断 某个逻辑标识 。
  2. 进入 BLOCKED 状态,比如调用了 sleep,或者 wait方法而加入了 waitSet 中。
  3. 进行某个阻塞的 IO 操作, 比如 因网络数据的读写而进入了 BLOCKED 状态 。
  4. 获取某个锁资源,从而加入到该锁的阻塞队列中而进入了 BLOCKED 状态 。
  5. 由于 CPU 的调度器轮询使该线程放弃执行,进入 RUNNABLE 状态 。
  6. 线程主动调用 yield 方法,放弃 CPU 执行权,进入 RUNNABLE 状态 。

    BLOCKED

    线程在 BLOCKED 状态中可以切换至如下几个状态。

  7. 直接进入 TERMINATED 状态,比如调用 JDK 已经不推荐使用 stop 方法或者意外 死亡 (JVMCrash)。

  8. 线程阻塞的操作结束,比如读取了想要的数据字节进入到 RUNNABLE 状态。
  9. 线程完成了指定时间的休眠,进入到了 RUNNABLE 状态 。
  10. Wait 中的线程被其他线程 notify/notifyall 唤醒,进入 RUNNABLE 状态 。 口线程获取到了某个锁资源,进入 RUNNABLE 状态 。
  11. 线程在阻塞过程中被打断,比如其他线程调用了 interrupt方法,进入 RUNNABLE 状态。
  12. 从Object.wait()状态刚被唤醒时,通常不能立刻抢到monitor锁,那就会从Waiting先进入到Block状态,抢到锁后转换到Runnable状态。

    TERMINATED

    线程进入 TERMINATED 状态,意味着该线程的整个生命周期都结束了,在该状态中线程将不会切换到其他任何状态,下列这些情况将 会使线程进入TERMINATED 状态。

  13. 线程运行正常结束,结束生命周期 。

  14. 线程运行出错意外结束。
  15. JVM Crash导致所有的 线程都结束。

阻塞与等待的区别

阻塞:当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断
等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,可响应中断。例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。
  需要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但话又说回来,虽然等锁时进入的状态不一样,但被唤醒后又都进入runnable态,从行为效果来看又是一样的。

Callable 接口

创建过程

  1. 创建实现Callable接口的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 创建FutureTask的对象,将此Callable接口实现类的对象作为传递到FutureTask构造器中。
  5. 创建Thread对象,将FutureTask的对象作为参数传递到Thread类的构造器中
  6. 调用Thread对象的start()方法
  1. package cn.bx.thread;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. class Foo implements Callable {
  6. public Object call() throws Exception {
  7. int sum = 0;
  8. for (int i = 1; i <= 100; i++) {
  9. if ((i & 1) == 0) {
  10. sum += i;
  11. }
  12. }
  13. return sum;
  14. }
  15. }
  16. public class ThreadCall {
  17. public static void main(String[] args) {
  18. Foo foo = new Foo();
  19. FutureTask futureTask = new FutureTask(foo);
  20. new Thread(futureTask).start();
  21. try {
  22. Object sum = futureTask.get();
  23. System.out.println("1-100偶数和是:" + sum);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. } catch (ExecutionException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }

线程池

corePoolSize:核心池的大小*
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
创建过程

  1. 创建线程池对象。
  2. 创建Runnable接口子类对象。(task)
  3. 提交Runnable接口子类对象。
  4. 关闭线程池

创建线程池方法

  1. newCachedThreadPool() 创建一个可缓存的线程池对象
  2. newFixedThreadPool(int) 创建一个固定大小的线程池对象
  3. newSingleThreadExecutor() 创建一个单线程的线程池对象
  4. newScheduledThreadPool() 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任
  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. class FooThread implements Runnable{
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 100; i++) {
  7. if ((i & 1) != 0) {
  8. try {
  9. Thread.sleep(1);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName() + ": " + i);
  14. }
  15. }
  16. }
  17. }
  18. class BarThread implements Runnable{
  19. @Override
  20. public void run() {
  21. for (int i = 0; i < 100; i++) {
  22. if ((i & 1) == 0) {
  23. try {
  24. Thread.sleep(1);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println(Thread.currentThread().getName() + ": " + i);
  29. }
  30. }
  31. }
  32. }
  33. public class ThreadPool{
  34. public static void main(String[] args) {
  35. ExecutorService executorService = Executors.newFixedThreadPool(10);
  36. executorService.execute(new FooThread());
  37. executorService.execute(new BarThread());
  38. executorService.shutdown();
  39. }
  40. }

参考文章

https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html
https://www.cnblogs.com/waterystone/p/4920007.html
https://docs.qq.com/doc/DSVNyZ2FNWWFkeFpO