这篇文章是介绍Java基础知识——多线程的,读完这篇文章,各位小伙伴们可以收获哪些呢?
废话不多说,干货满满,赶快来看看吧~
什么是程序?
什么是进程?
什么是线程?

继承 Thread类 ,看代码:
- public class Thread_one {
- public static void main(String[] args) throws InterruptedException {
- Person person = new Person();
- person.start();
- //当启动线程时,子线程和主线程会交替执行
- //主线程结束,子线程不一定结束
- //person.run()就是一个普通的方法,没有真正的启动一个线程
- for (int i = 0; i < 20; i++) {
- System.out.println("main线程执行");
- Thread.sleep(1000);
- }
- }
- }
- class Person extends Thread {
- @Override
- public void run() {
- int times = 0;
- while (true) {
- try {
- System.out.println("大家好,我是卷心菜~"
- + (++times)
- + "号" + "线程名是——"
- + Thread.currentThread().getName());
- Thread.sleep(2000);
- if (times == 50) break;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
该代码的功能是:启动一个子线程,在控制台打印50次 “大家好,我是卷心菜” ,主线程在控制台打印 “main线程执行” ,运行的部分结果如下:

我们来重点剖析一下这种创建方式,因为后面三种方式本质上区别不大。
当我们 Person类 继承 Thread类 以后,就要重写 run() ,方法体内写上自己需要的代码逻辑。语句 Thread.sleep(2000); 意思是,每打印一句话在控制台后,子线程就要“休息”2秒,在这“休息”的时间,主线程就开始执行自己的语句;同样,在主线程“休息”的时间,子线程也开始执行自己的语句。
总之,子线程和主线程会交替执行,多次运行可以发现,主线程结束,子线程不一定结束。这里需要特别注意。
爱思考的小伙伴们可能就要问了:我们在Person类中重写了run(),但是怎么使用线程的时候,调用的是start(),那run()谁来执行的呢?别着急,让我们进去源码看一看。

我们从源码中可以发现,start()调用了start0(),而start0()是一个本地方法,由JVM调用,只是我们看不到而已。
方式一明白了,再看看第二种方式,代码如下:
- public class Thread_two {
- public static void main(String[] args) {
- Animal animal = new Animal();
- Thread thread = new Thread(animal);
- thread.start();
- }
- }
- class Animal implements Runnable {
- int times = 0;
- @Override
- public void run() {
- while (true) {
- System.out.println("大家好,我是Tomcat~");
- try {
- Thread.sleep(2000);
- ++times;
- if (times == 10) break;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
此代码逻辑也很好理解,这里就不过多讲述了。我们来分析一下:为什么要使用类实现接口的方式来创建线程?
学过基础的小伙伴们都知道,我们Java是单继承的。因此,在某些情况下一个类可能已经继承了某个类,这时再用继承Thread类方法来创建线程就不可以了,所以我们有了Runnable接口。
再来看看方式三,代码如下:
- public class Thread_six {
- public static void main(String[] args) throws Exception {
- D d = new D();
- FutureTask futureTask = new FutureTask(d);
- Thread thread = new Thread(futureTask);
- thread.start();
- Object o = futureTask.get();
- System.out.println(o);
- }
- }
- class D implements Callable {
- @Override
- public Object call() throws Exception {
- int sum = 0;
- for (int i = 0; i < 5; i++) {
- System.out.println(i + 1);
- sum += i;
- }
- return sum;
- }
- }
来看看运行结果:

与 Runnable类 相比,类 Callable 功能更强大些,具体有如下优点:
最后来看看第四种方式,使用线程池,也是最推荐使用的方式,代码如下:
- public class Thread_seven {
- public static void main(String[] args) {
- ExecutorService service =
- Executors.newFixedThreadPool(10);
- // service 可以理解为池子
- service.execute(new F()); //适用于Runnable
- // service.submit(); //适用于Callable
- service.shutdown();//关闭线程池
- }
- }
- class F implements Runnable {
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println(i + 1);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
运行结果没有任何问题:

这么多的方法,用代码实践一下:
- public class Thread_five {
- public static void main(String[] args) {
- A a = new A();
- Thread thread = new Thread(a);
- thread.setName("棒棒糖线程");
- thread.setPriority(Thread.MIN_PRIORITY);
- thread.start();
- thread.interrupt();
- for (int i = 0; i < 20; i++) {
- System.out.println("主线程开始行动");
- try {
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- class A implements Runnable {
- @Override
- public void run() {
- while (true) {
- try {
- for (int i = 0; i < 10; i++) {
- System.out.println("我要吃" + (i + 1) +
- "棵棒棒糖,所处的线程是:"
- + Thread.currentThread().getName());
- }
- System.out.println("吃多了,要消化20秒");
- Thread.sleep(20000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
带大家看一下线程的优先级:
MAX_PRIORITY:10 MIN _PRIORITY:1 NORM_PRIORITY:5
截取一部分运行结果:

解析一下:由于把子线程的优先级设置为最低,主线程拿到了优先执行的语句,输出 "主线程开始行动" ,然后在主线程睡眠时间时,子线程开始执行,当执行完:吃多了,要消化20秒时,本应该等待20秒,但是interrupt()打断了子线程,所以就会出现上图的情况。
如果把语句 thread.interrupt(); 删去,就会出现下图的结果:

直接上代码演示:
- public class Thread_one {
- public static void main(String[] args) throws InterruptedException {
- T t = new T();
- Thread thread = new Thread(t);
- thread.start();
- for (int i = 0; i < 15; i++) {
- Thread.sleep(100);
- System.out.println("主线程吃了" + (i+ 1) + "颗棒棒糖");
- if (i == 4) {
- System.out.println("让子线程先吃吧");
- thread.join();
- // thread.yield();
- System.out.println("子线程吃完了,该我了");
- }
- }
- }
- }
- class T implements Runnable{
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("子线程吃了" + (i + 1) + "颗棒棒糖");
- }
- }
- }
运行结果:

由代码和运行结果图可以看出:因为语句 thread.join(); 的出现,导致主线程必须等到子线程结束后才能继续执行自己的代码,这就是 join() 的作用。
Java中的线程分为两类:一种是守护线程,一种是用户线程。 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true) 可以把一个用户线程变成一个守护线程。
下面用代码演示一下:
- public class Thread_two {
- public static void main(String[] args) throws InterruptedException {
- A a = new A();
- Thread thread = new Thread(a);
- //下面的方法要在start方法使用前使用,目的是当我们希望main方法结束后,子线程也可以结束;即把子线程设为守护线性
- thread.setDaemon(true);
- thread.start();
- for (int i = 0; i < 10; i++) {
- Thread.sleep(100);
- System.out.println("我在疯狂打游戏~");
- }
- }
- }
- class A implements Runnable{
- @Override
- public void run() {
- while (true) {
- try {
- Thread.sleep(1000);
- System.out.println("室友们在努力内卷学习~");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }

即当语句 我在疯狂打游戏 结束之后,守护线程的执行语句 室友们在努力内卷学习 就结束了,不在执行。
要想实现多线程,必须在主线程中创建新的线程对象。我们可以使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:

- public class Thread_three {
- public static void main(String[] args) throws InterruptedException {
- B b = new B();
- Thread thread = new Thread(b);
- System.out.println(thread.getName() + "状态为" + thread.getState());
- thread.start();
- //只要子线程状态不是终止状态,就继续查看子线程的状态
- while (Thread.State.TERMINATED != thread.getState()) {
- System.out.println(thread.getName() + "状态" + thread.getState());
- Thread.sleep(500);
- }
- System.out.println(thread.getName() + "状态" + thread.getState());
- }
- }
- class B implements Runnable {
- @Override
- public void run() {
- while (true) {
- for (int i = 0; i < 5; i++) {
- try {
- Thread.sleep(1000);
- System.out.println("我说了" + (i + 1) + "次Hi");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- break;
- }
- }
- }
运行结果:

下面案例都以模仿卖票入手,解决卖超的情形
- public class Thread_four {
- public static void main(String[] args) {
- Ticket ticket = new Ticket();
- Thread thread1 = new Thread(ticket);
- Thread thread2 = new Thread(ticket);
- thread1.start();
- thread2.start();
- }
- }
- class Ticket implements Runnable{
-
- private int tickets = 30;
- @Override
- public synchronized void run() {
- while (true) {
- try {
- if (tickets <= 0) {
- System.out.println("票没了,售票结束!");
- break;
- }
- System.out.println("窗口" + Thread.currentThread().getName()
- + "出售了一张票,还剩下"
- + --tickets + "张票");
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
运行结果:

- class Ticket implements Runnable {
- private int tickets = 30;
- @Override
- public void run() {
- synchronized (this) {
- while (true) {
- try {
- if (tickets <= 0) {
- System.out.println("票没了,售票结束!");
- break;
- }
- System.out.println("窗口" + Thread.currentThread().getName()
- + "出售了一张票,还剩下"
- + --tickets + "张票");
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- public class Thread_five {
- public static void main(String[] args) {
- DeadLock deadLock1 = new DeadLock(true);
- DeadLock deadLock2 = new DeadLock(false);
-
- Thread thread1 = new Thread(deadLock1);
- Thread thread2 = new Thread(deadLock2);
- thread1.start();
- thread2.start();
- }
- }
-
- class DeadLock implements Runnable {
- static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
- static Object o2 = new Object();
- boolean flag;
- public DeadLock(boolean flag) {
- this.flag = flag;
- }
- @Override
- public void run() {
- //下面业务逻辑的分析 //1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
- // 2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
- // 3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
- // 4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
- if (flag) {
- synchronized (o1) {//对象互斥锁, 下面就是同步代码
- System.out.println(Thread.currentThread().getName() + " 进入 1");
- synchronized (o2) { // 这里获得 li 对象的监视权
- System.out.println(Thread.currentThread().getName() + " 进入 2");
- }
- }
- } else {
- synchronized (o2) {
- System.out.println(Thread.currentThread().getName() + " 进入 3");
- synchronized (o1) { // 这里获得 li 对象的监视权
- System.out.println(Thread.currentThread().getName() + " 进入 4");
- }
- }
- }
- }
- }
运行结果:

因为不好演示,所以就介绍一下释放锁的各种情况:
最后再介绍不会释放锁的情况:
乐莫乐兮新相知,很高兴各位小伙伴可以坚持看完这篇文章。 一起加油,一起进步!