在谈及多线程之前先简单提及一下进程与线程。
进程: 进程是程序的一次执行过程。
线程: 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,一个线程只能属于一个进程。
单线程: 单线程即一个进程有且仅有一个线程。在程序执行时,所走的程序路径会按照连续顺序排下来,前面的线程必须处理好,后面的才会执行。
例如在学生食堂中,每一个阿姨一天要卖300份饭,使用单线程就可以做到随时知道还有几份饭没有卖完,而且安全性较高,不用担心学生没给钱,但是缺点是速度慢,需要学生一个个排队。
单线程卖饭的示例代码如下:
public class SingleThreadTest {
private static final int TOTAL_FOOD = 300;
public static void main(String[] args) {
Auntie auntie = new Auntie();
int soldFood = 0;
while (soldFood < TOTAL_FOOD) {
soldFood++;
auntie.sellFood(soldFood);
}
System.out.println("单线程卖饭完成。");
}
private static class Auntie {
public void sellFood(Integer soldFood) {
System.out.println(Thread.currentThread().getName() + " 卖出了第" + soldFood + "份饭。");
}
}
}
多线程: 多线程就是一个进程有多个线程。在程序执行时,所有线程会交替执行。后面的线程不用等待前面的线程处理完毕即可执行。
同样是卖300份饭,使用多线程就可以让多个阿姨一块去卖饭,速度快,等待时间大大缩短,但是缺点是安全性低,无法准确得知还剩下多少份饭,可能上一秒还有200份饭,下一秒就只有八九十份了。高铁抢票也是一样的原理,大学生应该深有感触。
多线程卖饭的示例代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiThreadTest {
private static final int TOTAL_FOOD = 300;
private static final int NUM_AUNTIES = 5;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(NUM_AUNTIES);
Auntie[] aunties = new Auntie[NUM_AUNTIES];
for (int i = 0; i < NUM_AUNTIES; i++) {
aunties[i] = new Auntie();
executorService.execute(aunties[i]);
}
executorService.shutdown();
while (!executorService.isTerminated()) {
// 等待所有阿姨卖饭完成
}
System.out.println("多线程卖饭完成。");
}
private static class Auntie implements Runnable {
private static final Object lock = new Object();
private static int soldFood = 0;
@Override
public void run() {
while (soldFood < TOTAL_FOOD) {
synchronized (lock) {
if (soldFood < TOTAL_FOOD) {
soldFood++;
sellFood(soldFood);
}
}
}
}
public void sellFood(Integer soldFood) {
System.out.println(Thread.currentThread().getName() + " 卖出了第" + soldFood + "份饭。");
}
}
}
在Java中,可以通过两种方式创建线程:继承Thread类和实现Runnable接口。继承Thread类需要重写run()方法,实现Runnable接口需要实现run()方法,并将该Runnable对象传递给Thread类的构造方法。
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
System.out.println("原神,启动!");
}
}
实现Runnable接口需要实现run()方法,并将该Runnable对象传递给Thread类的构造方法。
public class MyRunnable extends Runnable {
@Override
public void run() {
// 线程执行的代码
System.out.println("原神,启动!");
}
}
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
多线程主要是为了提高我们应用程序的使用率。但同时,这会给我们带来很多安全问题例如共享资源的竞争和冲突问题。
如果我们在单线程中以“顺序”(串行–>独占)的方式执行代码是没有任何问题的。但是到了多线程的环境下(并行),如果没有设计和控制得好,就会给我们带来很多意想不到的状况,也就是线程安全性问题。
因为在多线程的环境下,线程是交替执行的,一般他们会使用多个线程执行相同的代码。如果在此相同的代码里边有着共享的变量,或者一些组合操作,我们想要的正确结果就很容易出现了问题。
如果说:当多个线程访问某个类的时候,这个类始终能表现出正确的行为,那么这个类就是线程安全的。
为了避免这些问题,可以使用同步机制,如synchronized关键字和Lock接口,去解决线程安全问题。