直接使用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);
}