Java并发编程系列-线程的基本使用
Posted tsliwei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程系列-线程的基本使用相关的知识,希望对你有一定的参考价值。
最近在学习java并发编程基础.一切从简,以能理解概念为主.
并发编程肯定绕不过线程.这是最基础的.
那么就从在java中,如何使用线程开始.
继承Thread类
继承Thread类,重写run方法,new出对象,调用start方法.
在新启的线程里运行的就是重写的run方法.
1 /** 2 * 集成Thread类 实现run() 3 */ 4 public class C1 extends Thread { 5 6 @Override 7 public void run() { 8 try { 9 Thread.sleep(100); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName() + " run()"); 14 } 15 16 public static void main(String[] args) { 17 C1 c1 = new C1(); 18 c1.start(); 19 } 20 }
run方法里先睡100毫秒,然后打印当前线程名称+run()
运行结果:
实现Runnable接口
实现Runnable接口run方法
1 /** 2 * 实现Runnable接口run() 3 */ 4 public class C2 implements Runnable { 5 6 @Override 7 public void run() { 8 try { 9 Thread.sleep(100); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName() + " run()"); 14 } 15 16 public static void main(String[] args) { 17 C2 c2 = new C2(); 18 new Thread(c2, "thread0").start(); 19 } 20 }
运行结果:
Lambda表达式
Lambda表达式本质上还是实现了Runnable的run方法,但是写起来相当的方便.注:java8或以上版本才支持
java中Lambda表达式的语法:
1.(parameters) -> expression
2.(parameters) ->{ statements; }
3.对象名::方法名 注:方法名后不加小括号
1 /** 2 * Lambda表达式 java8或以上版本 3 */ 4 public class C3 { 5 6 public void foo() { 7 try { 8 Thread.sleep(100); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 System.out.println(Thread.currentThread().getName() + " foo()"); 13 } 14 15 public static void main(String[] args) { 16 C3 c3 = new C3(); 17 new Thread(() -> c3.foo()).start(); 18 new Thread(() -> { 19 c3.foo(); 20 }).start(); 21 new Thread(c3::foo).start(); 22 } 23 }
运行结果:
可以看到三种写法是等价的.执行无参数的方法,用第三种双冒号的方式最简单,有参数的可以用第一种方式,执行多行的代码片段只能用第二种方式.
start() run() join()
首先看一段代码吧
1 /** 2 * start() run() join() 3 */ 4 public class C4 { 5 6 public void foo() { 7 try { 8 Thread.sleep(100); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 System.out.println(Thread.currentThread().getName() + " foo()"); 13 } 14 15 public static void main(String[] args) { 16 C4 c4 = new C4(); 17 //一个线程不能同时执行多次start() 否侧会抛出IllegalThreadStateException 18 Thread tStart=new Thread(c4::foo); 19 tStart.start(); 20 tStart.start(); 21 //同时执行多次start()没抛出IllegalThreadStateException 因为不在同一线程内 是new出来的 22 new Thread(c4::foo).start(); 23 new Thread(c4::foo).start(); 24 25 try { 26 Thread.sleep(1000); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 System.out.println("---分割线---"); 31 //run() 调用的就是重写的那个run 所以没有开启线程 是在主线程里执行的 32 new Thread(c4::foo).run(); 33 34 System.out.println("---分割线---"); 35 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss:SSS"); 36 Thread tJoin = new Thread(c4::foo); 37 tJoin.start(); 38 System.out.println(simpleDateFormat.format(new Date())); 39 try { 40 //阻塞代码 等待线程执行完 41 tJoin.join(); 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 } 45 System.out.println(simpleDateFormat.format(new Date())); 46 } 47 }
start()
先看main方法里的前5行,在同一时间两次调用同一个线程的start().
运行结果:
抛出了IllegalThreadStateException非法的线程状态异常.也就是说在同一线程里,不能同时多次执行同一段代码.这很好理解了,同时多次执行同一段代码应该用多个线程.
注释掉tStart相关代码,继续运行
运行结果:
第一部分结果说明了同时多次执行同一段代码应该用多个线程.
start方法开启新线程,见源码(删掉源注释):
1 public synchronized void start() { 2 //判断线程状态 抛出IllegalThreadStateException 3 if (threadStatus != 0) 4 throw new IllegalThreadStateException(); 5 6 group.add(this); 7 8 boolean started = false; 9 try { 10 //这里开启 11 start0(); 12 started = true; 13 } finally { 14 try { 15 if (!started) { 16 group.threadStartFailed(this); 17 } 18 } catch (Throwable ignore) { 19 } 20 } 21 }
可以看到调用start首先会判断线程状态,不是0的话,抛出非法的线程状态异常.
真正开启线程是在start0()这里.
转到定义可以看到start调用了本地方法,也就是真正开启线程是c语言写的.那么什么参数都没有.底层这么知道开启线程调用什么方法呢.
最上面,Thread类有个静态构造函数是类在实例化的时候,调用registerNatives本地方法,将自己注册进去的.
run()
第二部分是调用的run方法,显示的线程名是主线程.就是说调用run方法并不会开启新线程.
看看到底是怎么回事.
导航到源码可以看到,Thread类是实现了Runnable接口的.重写了run方法.
如果用继承Thread类的方式写线程类,要重写run方法.先不管源码重写的run是什么,那么你的重写会覆盖掉源码重写的run.这种情况下,new出自己的线程类,然后调用run,当然和线程没有任何关系,就是在主线程里调用了另一个类的普通的方法.
如果用实现Runnable接口的方式写线程类,那么会new一个这个接口的实例,传到new出的Thread对象里.继续往下传到init方法,最终赋值到target变量上
结合上面源码重写的run,那么它还是调用了你传过来的Runnable实例的run方法.
join()
join就是阻塞代码,等待线程执行完后再开继续始执行当前代码
可以看到第三部分结果,第一次输出时间和第二次输出时间相差了109毫秒(线程内睡了100毫秒).如果不用join阻塞,时间一般相差的会很小,当然具体是多少也不一定,得看机器当时的运行情况.用join相差多少毫秒也不一定,但至少一定不会小于100毫秒.(可自行多次尝试).
以上是关于Java并发编程系列-线程的基本使用的主要内容,如果未能解决你的问题,请参考以下文章