java多线程基本技能

1.什么是进程?什么是线程

进程相对于exe程序来说他是一个动态的概念,可以说是运行中的程序。进程是资源(CPU、内存等)分配的基本单位
线程可以理解为进程中独立运行的子任务。是程序执行时的最小单位
例如:qq.exe程序运行时很多的子任务也同时在运行,比如添加好友,视频,下载文件,听音乐
进程福州想操作系统申请资源。在一个进程中,多个线程可以共享进程中相同的内存或者文件资源

我们可以从jvm的角度来剖析:在这里插入图片描述
可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。

2.多线程的优势

使用多线程就是在使用异步 可以在同一时间执行多个任务

3.什么情况下使用多线程

1).阻塞。一旦系统中出现了阻塞现象,则可以根据实际情况来使用多线程技术提高运行效率
2).依赖。业务分为2个执行过程,分别为A和B。当A业务发生阻塞情况时,B业务的执行不依赖A业务的执行结果

4.如何创建线程

在学习如何创建线程前,先看一下thread类的声明结构:

public class Thread implements Runable

由于java关于继承的特点----不支持多继承,所以为了更好的扩展类我们一般使用实现runnable接口.即一边实现一边继承。但2种方式创建线程的功能是一样的没有本质的区别

创建线程的方法:
1.继承Thread方法

new Thread(()->{
    
    },"").start();

2.实现Runnable接口

public class MyThread2 implements Runnable {
    
    //实现Runnable接口
  public void run(){
    
    
  //重写run方法
  }
}
public class Main {
    
    
  public static void main(String[] args){
    
    
    //创建并启动线程

    MyThread2 myThread=new MyThread2();

    Thread thread=new Thread(myThread);

    thread().start();

    //或者    new Thread(new MyThread2()).start();

  }

}

扩展:strat()方法执行了如下步骤:

1.通过jvm告诉操作系统创建Thread
2.操作系统开辟内存并使用Windows SDK中的createThread()函数创建Thread线程对象。
3.操作系统对Thread对象进行调度,以确定执行时机
4.Thread在操作系统中被成功执行

3.使用线程池

4.实现Callable接口,重写==call() ==方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

public class Main {
    
    
    public static void main(String[] args) throws Exception {
    
    
    	 // 将Callable包装成FutureTask,FutureTask也是一种Runnable
        MyCallable callable = new MyCallable();
        //包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        //传入线程执行目标,实例化线程对象
        new Thread(futureTask).start();

        // get方法会阻塞调用的线程
        Integer sum = futureTask.get();
        System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
    }
}


class MyCallable implements Callable<Integer> {
    
    

    @Override
    public Integer call() throws Exception {
    
    
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
//具体重写call方法
        Thread.sleep(5000);

        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
        return sum;
    }
}

Callable 也是一种函数式接口

@FunctionalInterface
public interface Callable<V> {
    
    
    V call() throws Exception;
}

FutureTask

public class FutureTask<V> implements RunnableFuture<V> {
    
    
	// 构造函数
	public FutureTask(Callable<V> callable);
	
	// 取消线程
	public boolean cancel(boolean mayInterruptIfRunning);
	// 判断线程
	public boolean isDone();
	// 获取线程执行结果
	public V get() throws InterruptedException, ExecutionException;
}

RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    
    
    void run();
}

以上类之间的关系:在这里插入图片描述

三种方式比较:

  • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
  • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
  • Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
  • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
  • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

5.线程的调度是随机的

6.线程的生命周期

	/**
     * 线程生命周期中的的六种状态
     * NEW:还没有调用start()的线程实例所处的状态
     * RUNNABLE:正在虚拟机中执行的线程所处的状态
     * BLOCKED:等待在监视器锁上的线程所处的状态
     * WAITING:等待其它线程执行特定操作的线程所处的状态
     * TIMED_WAITING:等待其它线程执行超时操作的线程所处的状态
     * TERMINATED:退出的线程所处的状态
     * 给定时间点,一个线程只会处于以下状态中的一个,这些状态仅仅是虚拟机层面的线程状态,并不能反映任何操作系统中线程的状态
     */
    public enum State {
    
    
        //还没有调用start()开启的线程实例所处的状态
        NEW, 
        //正在虚拟机中执行或者等待被执行的线程所处的状态,但是这种状态也包含线程正在等待处理器资源这种情况
        RUNNABLE,
        // 等待在监视器锁上的线程所处的状态,比如进入synchronized同步代码块或同步方法失败
        BLOCKED,
        // 等待其它线程执行特定操作的线程所处的状态;比如线程执行了以下方法: Object.wait with no timeout、Thread.join with no timeout、 LockSupport.park
        WAITING,
       // 等待其它线程执行超时操作的线程所处的状态;比如线程执行了以下方法: Thread.sleep、Object.wait with timeout
       //Thread.join with timeout、LockSupport.parkNanos、LockSupport.parkUntil
        TIMED_WAITING,
        //退出的线程所处的状态
        TERMINATED;
    }

在这里插入图片描述
状态详细说明:
1.初始状态(NEW)
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
2.Runnable 线程调用了start()方法后进入该状态,该状态包含了三种情况:

  1. 就绪状态 :等待cpu分配时间片【1.就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。2.调用线程的start()方法,此线程进入就绪状态。】
  2. 运行状态:进入Runnable方法执行任务
  3. 阻塞状态:BIO 执行阻塞式io流时的状态

3.Blocked
没获取到锁时的阻塞状态(阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态)
4.等待(WAITING)
调用wait()、join()或者LockSupport.park以及Thread.sleep()等方法后的状态。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
5.超时等待(TIMED_WAITING)
调用 sleep(time)、wait(time)、join(time)等方法后的状态。处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
6. 终止状态(TERMINATED)
线程执行完成或抛出异常后的状态。
比如:1.当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。2.在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

7.线程优先级

Thread 类中,使用如下属性来代表优先级。

private int priority;

我们可以通过 ==setPriority(int newPriority)==来设置新的优先级,通过 getPriority() 来获取线程的优先级。
Thread初始化priority的源码

Thread parent = currentThread();
this.priority = parent.getPriority();

原来,线程默认的优先级是继承父线程的优先级。
严谨一点说,子线程默认优先级和父线程一样,Java 主线程默认的优先级是 5。
Java 中定义了 3 种优先级,分别是最低优先级(1)、正常优先级(5)、最高优先级(10),代码如下所示。Java 优先级范围是 [1, 10],设置其他数字的优先级都会抛出 IllegalArgumentException异常。
注意:代码执行顺序跟线程的优先级无关。高优先级的线程大概率比低优先的线程优先获得 CPU 资源。优先级高的运行的快

8.线程的基本方法

在这里插入图片描述

1.currentThread()方法:返回代码正在被哪个线程调用(获取当前线程)

public class CurrentThreadTest {
    
    
    public static void main(String[] args) {
    
    
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
class MyThread extends Thread{
    
    
    public MyThread(){
    
    
        System.out.println("构造方法的打印:"+Thread.currentThread().getName());
    }
    public void run(){
    
    
        System.out.println("run方法的打印:"+Thread.currentThread().getName());
    }
}

输出结果:在这里插入图片描述
通过执行结果可以看出:MyThread.java类的构造函数是被main线程调用的,而run方法是被Thread-0的线程调用1的
2.isAlive()方法:判断当前线程是否存活

public class IsAliveTest {
    
    
    @SneakyThrows
    public static void main(String[] args) {
    
    
        MythreadIsAlive myThread = new MythreadIsAlive();
        System.out.println("begin =="+myThread.isAlive());
        myThread.start();
        System.out.println("end =="+myThread.isAlive());
        //主线程睡眠1s 目的:让子线程结果
        Thread.sleep(1000);
        System.out.println("睡玩之后end =="+myThread.isAlive());
    }
}
class MythreadIsAlive extends Thread{
    
    

    @Override
    public void run() {
    
    
        System.out.println("run="+this.isAlive());
    }
}

输出结果:在这里插入图片描述
那么什么是活动状态呢?线程已经启动且尚未终止的状态即活动状态
3.sleep(Long millis)方法:在指定的时间(毫秒)内让当前“正在执行的线程"休眠,这个正在执行的线程是指this.currentThread()返回的线程
4.stackTraceElement[] getStackTrace()方法:返回一个表示该线程堆栈跟踪元素组。如果该线程尚未启动或者已经终止,则该方法返回一个零长度数组。如果返回的数组不是零长度,则其第一个元素代表堆栈顶,他是该数组中最新的方法调用。最后一个元素代表堆栈底,是该数组中最旧的方法调用
5.static void dumpStack()方法:作用是将当前线程的堆栈跟踪信息输出值标准错误流。
6.static Map<Thread,StackTraceElement[]> getAllStackTraces()方法:
作用是返回所有活动线程的堆栈跟踪的一个映射
7.getId()方法:获取线程的唯一标识
8.yield():放弃当前的cpu资源,将它让给其他的任务去占用CPU执行时间,但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片
9.interrupt():在当前线程中打了一个停止的标记,并不是真正的停止线程
10.interrupted():测试当前线程是否已经中断,执行后具有将状态标志清除为false的功能 //public static boolean interrupted()
11.isInterrupted():测试线程是否已经中断,但不清除状态标志 //public boolean isInterrupted()
12.suspend():暂停线程
13.resume():恢复线程

猜你喜欢

转载自blog.csdn.net/f_a_ker/article/details/114003842