Java多线程【1】 -Java多线程基础


前言

本篇是关于Java的多线程基础
在开始看之前可以带着以下问题:
线程有哪几种状态?
分别说明从一种状态到另一种状态转变有哪些方式?
通常线程有哪几种使用方式?
基础线程机制有哪些?
线程的中断方式有哪些?
线程的互斥同步方式有哪些? 如何比较和选择?
线程之间有哪些协作方式?


提示:以下是本篇文章正文内容,下面案例可供参考

一、Java创建线程

有三种使用线程的方法:

  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 继承 Thread 类。

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

实现Runable接口

需要实现 run() 方法。

通过 Thread 调用 start() 方法来启动线程。


	public static void main(String[] args) {
		//创建线程任务
		Runnable r = new Runnable() {
			@Override
			public void run() {
				System.out.println("Runnable running");
			}
		};
		//将Runnable对象传给Thread
		Thread t = new Thread(r);
		//启动线程
		t.start();
	}


      class MyRunnable implements Runnable {
      @Override
      public void run() {
         System.out.println("my runnable running...");
      }
   }
   public static void main(String[] args) {
      MyRunnable myRunnable = new MyRunnable();
      Thread thread = new Thread(myRunnable);
      thread.start();
   }

第二种写法的简化:使用lambda表达式简化操作
当一个接口带有@FunctionalInterface注解时,是可以使用lambda来简化操作的

所以第二种写法种的代码可以被简化为

public class Test2 {
	public static void main(String[] args) {
		//创建线程任务
		Runnable r = () -> {
            //直接写方法体即可
			System.out.println("Runnable running");
			System.out.println("Hello Thread");
		};
		//将Runnable对象传给Thread
		Thread t = new Thread(r);
		//启动线程
		t.start();
	}
}

实现Callable接口

使用FutureTask可以用泛型指定线程的返回值类型(Runnable的run方法没有返回值)

    class MyCallable implements Callable<Integer> {
        public Integer call() {
            return 123;
        }
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 123;
            }
        });
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }

继承Thread类

class Mythread extends Thread {
    @Override
    public void run() {

        System.out.println("running");
    }
}
    public static void main(String[] args)  {
        Mythread mythread = new Mythread();
        mythread.start();
    }

public static void main(String[] args)  {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("running");
            }
        };
        thread.start();
    }

实现接口 VS 继承 Thread

  • 实现接口会更好一些,因为: Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大

二、线程状态的转换

在这里插入图片描述

新建(New)

创建后尚未启动。

可运行(Runnable)

可能正在运行,也可能正在等待 CPU 时间片。 包含了操作系统线程状态中的 Running 和 Ready。

阻塞(Blocking)

等待获取一个排它锁,如果其线程释放了锁就会结束此状态。

无限期等待(Waiting)

等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

进入方法退出方法
没有设置 Timeout 参数的 Object.wait() 方法Object.notify() / Object.notifyAll()
没有设置 Timeout 参数的 Thread.join() 方法被调用的线程执行完毕
LockSupport.park() 方法-

限期等待(Timed Waiting)

无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。

进入方法退出方法
Thread.sleep() 方法时间结束
设置了 Timeout 参数的 Object.wait() 方法时间结束 / Object.notify() / Object.notifyAll()
设置了 Timeout 参数的 Thread.join() 方法时间结束 / 被调用的线程执行完毕 LockSupport.parkNanos() 方法
LockSupport.parkNanos() 方法-
LockSupport.parkUntil() 方法-

死亡(Terminated)

可以是线程结束任务之后自己结束,或者产生了异常而结束

常用方法

(1)start() vs run()

被创建的Thread对象直接调用重写的run方法时, run方法是在主线程中被执行的,而不是在我们所创建的线程中执行。所以如果想要在所创建的线程中执行run方法,需要使用Thread对象的start方法。

(2)sleep()与yield()

sleep (使线程阻塞)
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞),可通过state()方法查看

其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

睡眠结束后的线程未必会立刻得到执行

建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性 。如:

//休眠一秒
TimeUnit.SECONDS.sleep(1);
//休眠一分钟
TimeUnit.MINUTES.sleep(1);

yield (让出当前线程)

调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能被执行),然后调度执行其它线程
具体的实现依赖于操作系统的任务调度器

(3)join()方法

用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。

如在主线程中调用thread .join(),则是主线程等待thread线程结束

Thread thread = new Thread();
//等待thread线程执行结束
thread.join();
//最多等待1000ms,如果1000ms内线程执行完毕,则会直接执行下面的语句,不会等够1000ms
thread.join(1000);

(4)interrupt()方法

用于打断阻塞(sleep wait join…)的线程。 处于阻塞状态的线程,CPU不会给其分配时间片。

如果一个线程在在运行中被打断,打断标记会被置为true。
如果是打断因sleep wait join方法而被阻塞的线程,会将打断标记置为false

//用于查看打断标记,返回值被boolean类型
`t1.isInterrupted();

正常运行的线程在被打断后,不会停止,会继续执行。如果要让线程在被打断后停下来,需要使用打断标记来判断。

while(true) {
    if(Thread.currentThread().isInterrupted()) {
        break;
    }
}

interrupt方法的应用——两阶段终止模式

当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。
在这里插入图片描述
在这里插入图片描述

import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;

@Slf4j(topic = "c.A")
public class A {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        sleep(3500);
        tpt.stop();
    }
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    private Thread monitor;

    // 启动监控指令
    public void start() {

        monitor = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if(current.isInterrupted()) {
                    // 可以优雅的关闭线程 可以进行释放资源等操作
                    log.debug("料理后事");
                    break;
                }else {
                    try {
                        sleep(1000);
                        log.debug("监控进程");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        current.interrupt();
                    }
                }
            }
        });
        monitor.start();
    }
    // 停止监控指令
    public void stop() {
        monitor.interrupt();
    }
}

(5)不推荐使用的打断方法

stop方法 停止线程运行(可能造成共享资源无法被释放,其他线程无法使用这些共享资源)
suspend(暂停线程)/resume(恢复线程)方法

(6)守护线程

当JAVA进程中有多个线程在执行时,只有当所有非守护线程都执行完毕后,JAVA进程才会结束。但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。
使用 setDaemon() 方法将一个线程设置为守护线程。

//将线程设置为守护线程, 默认为false
monitor.setDaemon(true);
守护线程的应用

main() 属于非守护线程。
垃圾回收器线程就是一种守护线程
Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求


总结

本章讲了关于Java最基础的使用和Java的生命周期,是最最基础的东西

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值