直接使用Thread
- public class demo {
- public static void main(String[] args) {
- new Thread(){
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName());
- }
- }.start();
- System.out.println(Thread.currentThread().getName());
- }
- }
main
Thread-0
使用Runnable配合Thread将任务与线程创建分开
- public class demo {
- public static void main(String[] args) {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName());
- }
- };
- //第二个参数是指定线程名字
- new Thread(runnable,"t1").start();
- System.out.println(Thread.currentThread().getName());
- }
- }
main
t1
不过Runnable被@FunctionalInterface注解修饰,可以使用lambda表达式化简
- public class demo {
- public static void main(String[] args) {
- Runnable runnable = () -> {
- System.out.println(Thread.currentThread().getName());
- }
- //第二个参数是指定线程名字
- new Thread(runnable,"t1").start();
- System.out.println(Thread.currentThread().getName());
- }
- }
使用FutureTask配合Thread。(FutureTask参数是Callable接口)
- public class demo {
- public static void main(String[] args) throws Exception {
- FutureTask
task = new FutureTask(new Callable() { - @Override
- public Integer call() throws Exception {
- System.out.println(Thread.currentThread().getName());
- Thread.sleep(1000L);
- return 1;
- }
- });
- Thread t1 = new Thread(task, "t1");
- t1.start();
- System.out.println(task.get());
- }
- }
t1
1
FutureTask实现了RunnableFuture,而RunnableFuture又继承了Runnable与Future接口,Future主要提供了一个get()方法,用于接收子线程的返回值。task.get()在执行时会阻塞,当子线程执行完毕后返回结果才会恢复正常。
线程在执行过程中是交替执行的,谁先谁后不由我们控制,由操作系统中的任务调度器控制。
调用sleep会将线程状态从Running进入到Timed waiting状态,其他线程可以调用interrupt方法叫醒其他睡眠的线程,线程休眠结束后并不一定立即执行。
yield会将当前线程状态从Running到Runnable状态,然后调度执行其他线程。但是具体执行哪个线程还是由操作系统决定
sleep并不会释放锁,而wait会释放锁对象
sleep时Thread方法,wait是Object方法,并且wait需要配合synchronized使用
与wait与notify相似,都是让线程休眠。但是是LockSupport中的方法。
LocakSupport.park(),使作用域中的线程进入休眠。
LockSupport.unpart(线程对象),唤醒线程。
unpark可以在线程park之前进行执行,使未park的线程在执行park后起不到休眠的作用。
park会将线程中的某个属性值修改为0,如果本身就为0时执行park会使线程休眠。如果本身为1,则修改为0不会休眠。
unpark会将线程中属性修改为1,多次调用unpark也只是设置为1。如果线程本身就在休眠,那么会将其唤醒不修改其属性值还是为0,如果线程本身就在执行中,那么会将其属性修改为1。
tasklist
查看进程 筛选进程 tasklist | findstr 程序名taskkill
杀死进程ps -fe
查看所有进程 筛选 ps -fe | grep 程序名ps -fT -p
查看某个进程(PID)的所有线程kill
杀死进程top
按大写 H 切换是否显示线程top -H -p
查看某个进程(PID)的所有线程jps
命令查看所有 Java 进程jstack
查看某个 Java 进程(PID)的所有线程状态jconsole
来查看某个 Java 进程中线程的运行情况(图形界面)栈内存是给线程使用的,每启动一个线程JVM都会为其分配一个栈内存
每个栈由多个栈帧组成,对应每次方法调用所占用的内存。每个栈只有一个活动栈帧,对应正在运行的方法。
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
以上前三种是被动停止,最后一种属于主动停止自己的线程。
线程在进行切换时会记录停止线程的当前状态,方便恢复执行时,从停止地方接着执行。
在没有CPU计算时,不要让while(true)空转浪费CPU,这时可以通过sleep或yield让出CPU去执行其给程序。(在单核CPU如果存在while(true)会占用率为100%)
如果子线程在阻塞状态下如sleep、wait、join时,被interrupt方法打断会抛出异常。sleep被interrupt打断后,会清除打断标记!
阻塞状态下也会被标记为true,但是退出线程后会被清除为false。
- public class demo {
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(()->{
- try {
- Thread.sleep(1000L);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- });
- t1.start();
- //这里调用interrupt后子线程执行sleep方法,正常打断,抛出异常,结束线程
- t1.interrupt();
- System.out.println(t1.isInterrupted());
- //主线程休眠让子线程执行sleep
- Thread.sleep(100L);
- //线程结束,清除标记
- System.out.println(t1.isInterrupted());
- }
- }
true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at demo.lambda$main$0(demo.java:5)
at java.lang.Thread.run(Thread.java:745)
false
如果子线程正在执行,被其他线程打断的话,由子线程自身决定自己是否停止执行
- public class demo {
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(()->{
- while (true){
- //如果正常状态下被打断会被标记为true
- if (Thread.currentThread().isInterrupted()){
- System.out.println("我被打断了");
- break;
- }
- }
- });
- t1.start();
- Thread.sleep(100L);
- t1.interrupt();
- }
- }
指的是线程1终止线程2的进行
比如说一个后台监控系统,一个线程while循环持续监控,当不需要监控时,打断线程即可,如果在休眠期期间被打断,那么抓住异常手动设置打断标记,如果是执行监控时被打断,等到下一次判断时就会退出循环。
- public class TwoPhaseTerminationTest {
- public static void main(String[] args) throws InterruptedException {
- TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
- twoPhaseTermination.start();
- Thread.sleep(3000);
- twoPhaseTermination.stop();
- }
- }
-
- class TwoPhaseTermination{
- private Thread monitor;
-
- public void start(){
- monitor = new Thread(()->{
- System.out.println("开始监控");
- Thread thread = Thread.currentThread();
- while (true){
- if (thread.isInterrupted()){
- System.out.println("结束前终止操作");
- break;
- }
- try {
- Thread.sleep(2000);
- System.out.println("进行监控");
- } catch (InterruptedException e) {
- e.printStackTrace();
- thread.interrupt();
- }
- }
- });
-
- monitor.start();
- }
-
- public void stop(){
- System.out.println("停止监控");
- monitor.interrupt();
- }
- }
开始监控
进行监控
停止监控
结束前终止操作
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at TwoPhaseTermination.lambda$start$0(TwoPhaseTerminationTest.java:23)
at java.lang.Thread.run(Thread.java:745)
- public class TwoPhaseTerminationTest {
- public static void main(String[] args) throws InterruptedException {
- TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
- twoPhaseTermination.start();
- Thread.sleep(3000);
- twoPhaseTermination.stop();
- }
- }
-
- class TwoPhaseTermination{
- private Thread monitor;
-
- private volatile boolean stop = false;
-
- private boolean starting = false;
-
- public void start(){
- //防止主线程多次使用start()方法来创建多个相同的监控线程。
- synchronized(this){
- if(starting){
- return;
- }
- starting = true;
- }
- monitor = new Thread(()->{
- System.out.println("开始监控");
- Thread thread = Thread.currentThread();
- while (true){
- if (stop){
- System.out.println("结束前终止操作");
- break;
- }
- try {
- Thread.sleep(2000);
- System.out.println("进行监控");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
-
- monitor.start();
- }
-
- public void stop(){
- System.out.println("停止监控");
- //设置为false后break结束循环
- stop = true;
- //如果线程休眠时间较长,但是需要即使打断的话也可以使用interrupt方法来打断。
- monitor.interrupt();
- }
- }
通常情况下,当Java中所有线程结束后,程序才会结束,但是如果存在守护线程,当其他非守护线程结束后,即使守护线程还未执行结束,程序也会停止。
- public class demo {
- public static void main(String[] args) {
- new Thread(()->{
- try {
- Thread.sleep(10000);
- System.out.println("线程结束");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }).start();
- System.out.println("主线程结束");
- }
- }
主线程结束
线程结束
以上是主线程结束后程序等待子线程结束后才会停止。那么将子线程设置为守护线程测试
- public class demo {
- public static void main(String[] args) {
- Thread t1 = new Thread(() -> {
- try {
- Thread.sleep(10000);
- System.out.println("线程结束");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- });
- t1.setDaemon(true);
- t1.start();
- System.out.println("主线程结束");
- }
- }
主线程结束
此时程序并没有等待子线程的结束而是直接停止了运行。
从操作系统上来讲。线程一共存在五种运行状态,分别为初始状态、可运行状态、运行状态、阻塞状态、终止状态
从java层次来看,线程通过枚举一共有六种状态。
对于成员变量与静态变量,如果没有共享则线程安全。如果共享则要看他们状态是否会发生改变。
如果是只读下,线程安全,涉及到读写则线程不安全。
局部变量是线程安全的,但是局部变量引用的对象不一定线程安全,取决于被引用的对象有没有逃离作用范围。
- public class demo2 {
- private static final int THREAD_NUM = 2;
- private static final int FOR_NUM = 200;
-
- public static void main(String[] args) {
- ThreadUnsafe threadUnsafe = new ThreadUnsafe();
- for (int i = 0; i < THREAD_NUM; i++) {
- new Thread(()->{
- threadUnsafe.method1(FOR_NUM);
- },"t"+i).start();
- }
- }
- }
-
- class ThreadUnsafe {
- List
list = new ArrayList<>(); - public void method1(int num) {
- for (int i = 0; i < num; i++) {
- method2();
- method3();
- }
- }
-
- public void method2(){
- list.add("1");
- }
-
- public void method3(){
- list.remove(0);
- }
- }
Exception in thread "t0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.remove(ArrayList.java:492)
at ThreadUnsafe.method3(demo2.java:32)
at ThreadUnsafe.method1(demo2.java:23)
at demo2.lambda$main$0(demo2.java:12)
at java.lang.Thread.run(Thread.java:745)
至于为啥会报错,应该看add源码
重点在于size++。字节码文件add操作不是一个原子操作。
t1线程抢占CPU时间片后,对list进行size++,加完后要要返回size值,比如说初始是0,进行size++后应该返回1但是还未进行返回,线程t2拿到CPU时间片,拿到的size值还为0,进行size++后返回1,t1恢复还是返回1但实际上size应该为2。因此remove两次后就会报错。由此可以总结问题所在是list变量被线程共享了,只需要将list变为局部变量即可解决这个问题
- class ThreadSafe {
- public void method1(int num) {
- List
list = new ArrayList<>(); - for (int i = 0; i < num; i++) {
- method2(list);
- method3(list);
- }
- }
-
- public void method2(List list) {
- list.add("1");
- }
-
- public void method3(List list) {
- list.remove(0);
- }
- }
这样并不会报错。因为各自的线程内存在各自的list。
子类重写父类方法可能会导致线程不安全
- import java.util.ArrayList;
- import java.util.List;
-
- public class demo2 {
- private static final int THREAD_NUM = 2;
- private static final int FOR_NUM = 200;
-
- public static void main(String[] args) {
- ThreadSafeSubclass threadSafeSubclass = new ThreadSafeSubclass();
- for (int i = 0; i < THREAD_NUM; i++) {
- new Thread(() -> {
- threadSafeSubclass.method1(FOR_NUM);
- }, "thread" + i).start();
- }
- }
- }
-
- class ThreadUnsafe {
- List
list = new ArrayList<>(); -
- public void method1(int num) {
- for (int i = 0; i < num; i++) {
- method2();
- method3();
- }
- }
-
- public void method2() {
- list.add("1");
- }
-
- public void method3() {
- list.remove(0);
- }
- }
-
- class ThreadSafe {
- public void method1(int num) {
- List
list = new ArrayList<>(); - for (int i = 0; i < num; i++) {
- method2(list);
- method3(list);
- }
- }
-
- public void method2(List list) {
- // list.add("1");
- System.out.println(Thread.currentThread().getName()+":1");
- }
-
- public void method3(List list) {
- list.remove(0);
- }
- }
-
- class ThreadSafeSubclass extends ThreadSafe{
- @Override
- public void method3(List list) {
- new Thread(()->{
- // list.remove(0);
- System.out.println(Thread.currentThread().getName()+":2");
- }).start();
- }
- }
结果可知,没办法保证方法的执行顺序。
String类、Integer、StringBuffer、Random、JUC包下的所有方法等都是线程安全的。
他们单个方法都是线程安全的(因为加入了synchronized关键字),但是当他们组合使用是就不是线程安全的。如下图。
- HashTable hashTable = new HashTable();
- if(hashTable.get("key") == null){
- hashTable.put("key",value);
- }