之前我们介绍了什么是进程与线程,那么我们如何使用代码去创建一个线程呢?线程操作是操作系统中的概念,操作系统内核实现了线程这样的机制,并且用户层提供了一些API供用户使用,Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的封装和抽象,所以我们创建的方式有很多我们可以;
继承Thread来创建一个线程类
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("t1");
}
}
}
public class Threadcreat {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
while (true){
System.out.println("main");
}
}
}
实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("t1");
}
}
}
public class ThreadCreat2 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
while (true) {
System.out.println("main");
}
}
}
以上两种方法还可以使用内部类的方法实现:
public class ThreadCreat3 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("t1");
}
}
};
thread.start();
while (true) {
System.out.println("main");
}
}
}
public class ThreadCreat3 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("t1");
}
}
});
t1.start();
while (true) {
System.out.println("main");
}
}
}
但是我们实际的开发中最常用的创建线程的方式是使用lambda表达式的方式创建
public class ThreadCreat4 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println("t1");
}
});
t1.start();
while (true) {
System.out.println("main");
}
}
}
多线程是用来提高程序的整体运行效率的,类似上面的几个代码如果我们没有使用多线程,那么将会在一个循环里面卡死,而多线程操作就可以同时跑两个死循环,统一时间可以干两件事。
有的同学还是会有一点疑问,上面代码中的start run
等方法都是什么呢?
run
方法表示了线程的入口方法是什么,里面的逻辑描述了此线程需要做哪些事,不需要程序员自己调用,系统会自动调用。
start
方法表示真正从系统中创建一个新的线程,新的线程将会执行run
方法。
public class ThreadCreat4 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println("t1");
}
});
t1.start();
while (true) {
System.out.println("main");
}
}
}
此时我们觉得t1
线程和主线程打印速度太快了,我们能否让我们的打印速度慢一点呢?可以,让线程睡一会觉不就可以了。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1");
}
});
t1.start();
while (true) {
Thread.sleep(1000);
System.out.println("main");
}
}
}
sleep()
方法就可以让线程休眠一会,方法的参数可以设置休眠的时间,此时主线程每个1s打印一次,t1
线程每隔3s打印一次。
InterruptedException
这个是多线程中常见的一个异常,如果在sleep()
的线程被提前唤醒,可能会造成一些错误,所以当sleep()
的线程被提前唤醒时会抛出一个InterruptedException
。
public class ThreadInterrupt {
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
while(true) {
System.out.println("t1");
}
});
t1.start();
System.out.println("mian");
}
}
我们来看这个代码,因为线程t1中有一个死循环,此时会导致入口方法run
无法执行结束,怎么办呢?我们需要设计一个标记。
public class ThreadInterrupt {
public static boolean isQuit = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() ->{
while(isQuit) {
System.out.println("t1");
}
System.out.println("线程终止");
});
t1.start();
System.out.println("mian");
Thread.sleep(1000);
isQuit = false;
}
}
此时通过我们自己创建的结束标记位进行控制循环的结束,为了让我们更方便的实现上述功能,Thread类内置了一个标志位。
public class ThreadCreat4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("t1");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread.sleep(3000);
t1.interrupt();
}
}
我们使用标记位的目的是让循环结束,但是我们执行程序发现,循环并没有结束,这是为什么呢?使用interrupt()
的作用其实有两个:
1、
Thread.currentThread().isInterrupted()
这个方法可以获取到标记位的状态,标记位默认为false
。
2、t1.interrupt();
将标记位变为true
,并且如果该线程在阻塞中,此时就会把阻塞状态唤醒,sleep()
方法提前被唤醒就会抛出一个异常,sleep()
被唤醒会自动的把isinterrupted
标记位清空,所以导致下次循环继续执行。
如何解决这个问题呢?我们进行这样的修改:
public class ThreadCreat4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("t1");
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
t1.start();
Thread.sleep(3000);
t1.interrupt();
}
}
此时当t1.interrupt()
将标志位变为true
,并且提前将sleep()
唤醒,此时我们直接结束循环就可以完成需求了。
因为线程的调度是随机的无序的,所以当两个线程一起运行时可能会有一些错误,比如下面这个例子:
public class ThreadJoin {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for(int i = 0;i < 5000;i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for(int i = 0;i < 5000;i++) {
count++;
}
});
t1.start();
t2.start();
Thread.sleep(3000);
System.out.println(count);
}
}
根据代码的逻辑我们希望每个线程对变量count
进行5000次++
操作,最后的结果应该是10000,但实际结果并不符合预期,这是因为两个线程在同时进行的过程中可能出现两次++
变量只增加一次的情况,这个我们之后在详细分析原因,现在我们只需要让一个线程等待另一个线程执行完就可以避免这个错误,线程等待我们可以使用join()
方法。
在主线程中,通过t1
线程调用join()
方法的意思是,让主线程等待t1
线程执行完之后在执行之后的代码,此时就控制了t1
与t2
线程的执行顺序了。