3.1创建线程以及运行线程
创建线程
方法一:直接使用线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "c.Test01") public class Test01 { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run(){ log.debug("running"); } }; t.setName("t1"); t.start(); t.setName("t2"); log.debug("running"); } }
|
方法二:使用runnable配合Thread
把【线程】和【任务】需要执行的代码分开
Thread代表线程
runnable代表可运行的任务(线程需要执行的代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "c.Test02") public class Test02 { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { log.debug("running"); } }; Thread t = new Thread(r, "t2"); t.start(); } }
|
上述的代码可以使用Lambda表达式进行简化
Thread与Runnable之间的关系
分析一下Thread与Runnable之间的关系

runnable对象作为参数传给了Thread的构造方法,其中又调用了init方法,然后又被传给了一个重载的Init()方法,最后发现this.target = target;实际上就是将Runnable对象赋值给了Thread中的一个成员变量
方法二实际上走的还是人家Thread 的run方法,如果你有target(runnable)对象,采用runnable对象的run方法。不论是方法一还是方法二,将来线程运行时候,走的都是线程对象中的run方法。
- 方法一就是将线程和任务合并在一块了,方法二是将线程和任务分开了。
- 用Runnable更更容易与线程池等高级API配合使用
- 用Runnable让任务脱离了Thread继承体系,更灵活。实际上这两就是组合的关系。
方法三:FutureTask配合Thread
- FutureTask能够接收Callable类型地参数,用来处理有返回结果的情况
FutureTask是runnable的一个扩展,可以用来获取任务的执行结果
1
| public class FutureTask<V> implements RunnableFuture<V>
|
他是一个实现类实现了RunnableFuture的一个接口
1
| public interface RunnableFuture<V> extends Runnable, Future<V>
|
它间接实现了runnable接口,它可以作为一个任务对象,它比Runnable多了一个future接口
1
| public interface Future<V>
|
future用来返回任务的执行结果
1
| V get() throws InterruptedException, ExecutionException;
|
就是这个get方法。
但是Runnable接口返回值是一个void,所以它并不能方便地在两个线程直接把一个结果传给另外一个线程。
Callable和runnable接口非常像,但是它是有返回值的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;
@Slf4j(topic = "cTest03") public class Test03 { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { log.debug("running"); Thread.sleep(10000); return 100; } });
Thread t1 = new Thread(task, "t1"); t1.start(); log.debug("{}", task.get()); } }
|
3.2观察多个线程同时执行
主要是理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import lombok.extern.slf4j.Slf4j; @Slf4j public class practice1 { public static void main(String[] args) { new Thread(() -> { while (true){ log.debug("running"); } }, "t1").start(); new Thread(() -> { while (true){ log.debug("running"); } },"t2").start(); } }
|
3.3 查看进程的方法
Windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist
- taskkill
Linux查看进程和查看线程的信息
- ps -fT -p 查看某个进程(PID)的所有线程


- top 按下大写H是否切换线程
- top -H -p 查看某个进程(PID)的所有线程(看哪一个进程的线程)
Java
- jps命令查看所有的Java进程(同时也适用于Linux系统)
- jstack 查看Java进程(PID)的所有线程(那一时刻的)情况(相当于快照)

jconsole 来查看某个Java进程中线程的运行情况(图形界面)
3.4 原理之线程运行
3.4.1 栈与栈帧
JVM中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机都会为其分配一块栈内存。
- 每个栈由多个栈帧组成,对应着每次方法调用时候所占用的内存
- 每个线程只能有一个活动栈帧,对应正在执行的那一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Test04 { public static void main(String[] args) { method1(10); } private static void method1(int x){ int y = x + 1; Object m = method2(); System.out.println(m); } private static Object method2(){ Object n = new Object(); return n; } }
|
再创建一个线程,在method1(20);和method1(10);建立断点(以线程的方式添加断点),这段程序的主要就是要明白线程的栈内存是相互独立的,每个线程有自己独立的栈内存(里面有多个栈帧),它们之间互不干扰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Test04 { public static void main(String[] args) { Thread t1 = new Thread(){ @Override public void run(){ method1(20); } }; t1.setName("t1"); t1.start(); method1(10); } private static void method1(int x){ int y = x + 1; Object m = method2(); System.out.println(m); }
private static Object method2(){ Object n = new Object(); return n; } }
|
3.4.2 线程上下文切换(Thread Context Switch)
什么时候会发生线程上下文切换?当任务调度器将这些时间片都分配给每个线程运行的时候,每个线程的时间片用完的时候,用完了,就得把CPU的使用权交给其他线程,这时候当前线程就会发生线程上下文切换。也就是从使用CPU到了不使用CPU,这就称之为一次线程上下文切换。
发生的原因:
- 线程的CPU时间片用光
- 垃圾回收(它会暂停当前所有的工作线程)
- 有更高的优先级的线程需要运行
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法
1、2、3都是被动的4是主动的。
当(Context Switch)发生的时候,需要由操作系统保存当前线程的状态(和Tmux的作用好像),并且恢复另一个线程的状态,Java中对应的概念就是程序计数器(Progmer Counter Register) ,它的作用是记住下一条JVM指令的地址,是线程私有的。
- 状态包括程序计数器、虚拟机栈中每一个栈帧的信息,如局部变量、操作栈数、返回地址等。
- Context Switch频繁发生会影响性能