Java 多线程系列Ⅰ

Java
241
0
0
2024-02-28
标签   Java多线程

一、创建线程的四种方法

首先,所有的创建线程的方式都是基于Thread类来实现,每个线程都必须通过 Thread 类的构造方法创建,并实现 run() 方法来执行线程的任务。

  1. 继承Thread类实现多线程

继承类Thread是支持多线程的功能类,只要创建一个子类就可以实现多线程的支持。

所有的java程序的起点是main方法,所以线程一定有自己的起点,那这个起点就是run方法;因为多线程的每个主体类之中必须重写Thread的run方法。

这个run方法没有返回值,那就说明线程一旦开始就一直执行不能返回内容。

多线程启动的唯一方法是调用Thread的start方法,如果调用的是run方法就是普通run方法的调用(调用此方法执行的是run方法体)。

总结:使用Thread类的start方法不仅仅启动多线程的执行代码,还要从不同操作系统中分配资源。

步骤:

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start():start()作用①启动当前线程 ② 调用当前线程的run()

继承Thread类(java不支持多继承)

public class ExtendsThread extends Thread {
    @Override
    public void run() {System.out.println('用Thread类实现线程');}
}
  1. 实现Runnable(优先使用)

Java具有单继承局限,所有的Java程序针对类的继承都应该是回避,那么线程也一样,为了解决单继承的限制,因此才有Runnable接口。

使用方法:让一个类实现Runnable接口即可,并且也需要覆写run()方法。

疑问:但是此接口只有run方法,没有start方法,怎么启动多线程呢?

不管任何情况下,如果要想启动多线程一定要依靠Thread类完成,在Thread类中有参数是Runnable参数的构造方法:Thread(Runnable target) 接收的是Runnable接口,可以创建一个参数是Runnable实现类的Thread类,调用start方法启动。

总结:实现Runnable接口来写多线程的业务类,用Thread来启动多线程。

实现 Runnable 接口(优先使用)

public class RunnableThread implements Runnable {
    @Override
    public void run() {System.out.println('用实现Runnable接口实现线程');}
}
  1. 实现Callable接口

实现Callable接口(有返回值可抛出异常)

步骤:

  1. 实现Callable接口
  2. 重写里面的Call方法(注意是Call不是Run)
  3. 创建Callable实现类的对象
  4. 将实现类对象作为参数传递给FutureTask构造函数
  5. 将FutureTask对象作为参数传递给Thread构造函数(因为FutureTask实现了Runnable接口,所以可以这么传)
  6. 调用Thread类的start方法
//class CallableTask implements Callable<Integer> {
    //@Override
    //public Integer call() throws Exception { return new Random().nextInt();}
//}
@Override
    public Object call() throws Exception {
        System.out.println("CallableImpl");
        return "我是Call方法的返回值";
    }

   public static void main(String[] args) {
        CallableImpl callable=new CallableImpl();
        FutureTask<Object> futureTask=new FutureTask<>(callable);
        Thread thread=new Thread(futureTask);
       
        需要注意一件事:
        FutureTask类中的get方法获取返回值只能执行一次
        而且,如果使用了这个方法但是线程还没有运行到可以返回的那行代码,那么就会一直阻塞
        比如如果我在这里执行了如下代码:
        Object result=futureTask.get();
        那么就永远阻塞了
        当然,我更想说的是,如果你使用的是这种方法创建线程并且需要返回值的话,里面就别写死循环
        否则就是死锁在召唤
            
		thread.start();
        try {
            Object result=futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

  1. call()可以返回值的。
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的
  4. 线程池

创建线程池方法:

  1. 通过ThreadPoolExecutor构造函数来创建(推荐)。
  2. 通过 Executor 框架的工具类 Executors 来创建。

(底层都是实现run方法)

static class DefaultThreadFactory implements ThreadFactory {
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + poolNumber.getAndIncrement() +"-thread-";
    }
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);
        if (t.isDaemon()) t.setDaemon(false);  //是否守护线程
        if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); //线程优先级
        return t;
    }
}

好处:

  • 1.提高响应速度(减少了创建新线程的时间)
  • 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 3.便于线程管理

二、Thread类及常见方法

在Java中,Thread类是实现多线程的关键类之一。Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联,即Java代码中的Thread对象和操作系统中的线程是一一对应的。 而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

1、构造方法

方法

说明

Thread()

创建线程对象

Thread(Runnable target)

使用 Runnable 对象创建线程对象

Thread(String name)

创建线程对象,并命名

Thread(Runnable target, String name)

使用 Runnable 对象创建线程对象,并命名

2、其他方法

返回值类型

方法名

说明

long

getId()

返回线程标识符Id

String

getName()

返回线程名称

Thread.State

getState()

返回线程状态

int

getPriority()

返回线程优先级

boolean

isDaemon()

判断是否为后台线程

boolean

isAlive()

判断线程是否存活

boolean

isInterrupted()

判断线程是否被中断

3、启动线程

run()start() 很多人容易搞混这两个方法的关系,只有调用了 start()方法,才会表现出多线程的特性,不同线程的 run()方法里面的代码交替执行。如果只是调用 run()方法,那么代码还是同步执行的,必须等待一个线程的 run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其 run()方法里面的代码。

4、线程睡眠

sleep()方法让当前正在执行的线程休眠(暂停执行),提到sleep()就得提一下它和wait()的区别了。

  • wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
  • wait 方法会主动释放锁,在同步代码中执行 sleep 方法时,并不会释放锁。
  • wait 方法意味着永久等待,直到被中断或被唤醒才能恢复,不会主动恢复,sleep 方法中会定义一个时间,时间到期后会主动恢复。
  • wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

三、线程的状态

线程状态:创建、就绪、运行、阻塞、死亡

  1. 新建状态(New) :线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
  2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
  3. 运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入"等待池"中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法 (2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入"锁池"中。 (3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者l/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
  5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

图片.png