基本线程类

作者: 逐鹿者不见山 | 来源:发表于2020-08-30 19:05 被阅读0次

一、Thread 类

Thread类实现了Runnable接口,在Thread类中,有一些比较关键的属性

public class Thread implements Runnable{
    private char name[];//表示Thread名字,可以通过Thread构造器中的参数指定线程的名字
    private int priority;//线程的优先级(最大值为10,最小值为1,默认为5)
    // 守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
    private boolean daemon = false;//该线程是否为守护线程
    private Runnable target;//要执行的任务
}

Thread类常用的方法如下

// start() 用来启动一个线程,实现多线程,当调用start方法后,系统会开启一个新线程用来执行用户定义的子任务,并为响应线程分配资源。这时线程处于就绪状态,但并没有运行,一旦得到cpu时间片,就开始执行run方法(run()称为线程体,包含要执行这个线程的内容,run()方法运行结束则线程终止)
public static Thread.start()
// run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
public static Thread.run()
// 当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)
// 调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
// 注意1,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
// 注意2,yield方法并不会让线程释放锁
public static Thread.yield() 
// sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
// 但是有一点要非常注意,sleep方法不会释放锁(相当于一直持有该对象的锁),也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
// 还有一点要注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
sleep(long millis)     //参数为毫秒
sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒
// 在一个线程中调用other.join(),将等待other执行完后才继续本线程。  
// 假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。  
join()
join(long millis)     //参数为毫秒
join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒
// interrupt()是Thread类的一个实例方法,用于中断本线程。这个方法被调用时,会立即将线程的中断标志设置为“true”。所以当中断处于“阻塞状态”的线程时,由于处于阻塞状态,中断标记会被设置为“false”,抛出一个 InterruptedException。所以我们在线程的循环外捕获这个异常,就可以退出线程了。
// interrupt()并不会中断处于“运行状态”的线程,它会把线程的“中断标记”设置为true,所以我们可以不断通过isInterrupted()来检测中断标记,从而在调用了interrupt()后终止线程,这也是通常我们对interrupt()的用法。
public interrupte()

Java中断机制

Java 中断机制介绍
java中的线程中断机制是一种协作机制。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。
Java提供了中断机制,Threaf类下有3个重要的方法
public void interrupt();//每个线程都有个boolean类型的中断状态。当使用Thread的interrupt()方法时,线程的中断状态会被设置为true。
public boolean isInterrupted();//判断线程是否被中断
public static boolean interrupted(); // 清除中断标志,并返回原状态
当一个线程中断另一个线程时,被中断的线程不一定要立即停止正在做的事情。相反,中断是礼貌地请求另一个线程在它愿意并且方便的时候停止它正在做的事情。interrupt方法,就是告诉线程,我需要中断你,该方法调用之后,线程并不会立刻终止,而是在合适的时机终止。什么时机呢?Java的处理判定机制为:
机制一:如果该线程处在可中断状态下,(例如Thread.sleep(), Thread.join()或 Object.wait()),那么该线程会立即被唤醒,同时会收到一个InterruptedException,如果是阻塞在io上,对应的资源会被关闭。
机制二:如果该线程处在不可中断状态下,即没有调用上述api,处于运行时的进程。那么java只是设置一下该线程的interrupt状态,其他事情都不会发生,如果该线程之后会调用行数阻塞API,那到时候线程会马会上跳出,并抛出InterruptedException,接下来的事情就跟第一种状况一致了。如果不会调用阻塞API,那么这个线程就会一直执行下去。在被中断线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过 Thread.isInterrupted()来读取,并且可以通过一个名为Thread.interrupted() 的操作读取和清除。
利用 中断机制 正确结束线程
综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:

public class InterruptedExample {

    public static void main(String[] args) throws Exception {
        InterruptedExample interruptedExample = new InterruptedExample();
        interruptedExample.start();
    }

    public void start() {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
        //当Thread 处于 sleep 后处于阻塞状态,收到中断请求会跑出InterruptedException异常
            Thread.sleep(3000);
            myThread.cancel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class MyThread extends Thread{

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                // 线程循环执行打印一些信息,使用isInterrupted判断线程是否被中断,若中断则结束线程
                    System.out.println("test");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // 阻塞状态下的线程抛出异常后则会被终止
                    System.out.println("interrupt");
                    // 抛出InterruptedException后中断标志被清除(中断标志 重新设置为false)
                    // 标准做法是再次调用interrupt恢复中断,正确情景下为true
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("stop");
        }

        public void cancel(){
        //对线程调用interrupt()方法,不会真正中断正在运行的线程,
        //只是发出一个请求,由线程在合适时候结束自己。
            interrupt();
        }
    }
}

Thread类实现案例

(1)继承Thread类,重写该类的run()方法,run方法的方法体代表了线程要完成的任务,因此run方法可称为执行体
(2)创建Thread子类的实例,即创建线程对象
(3)调用线程对象的start方法启动线程

package com.thread;
 
public class FirstThreadTest extends Thread{
    int i = 0;
    //重写run方法,run方法的方法体就是现场执行体
    public void run()
    {
        for(;i<100;i++){
        System.out.println(getName()+"  "+i);
        
        }
    }
    public static void main(String[] args)
    {
        for(int i = 0;i< 100;i++)
        {
            // 调用100次main主线程
            System.out.println(Thread.currentThread().getName()+"  : "+i);
            if(i==20)
            {
                // 当主线程调用到20时,执行100次子线程-1,子线程-2
                new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }
 
}

实现结果

main  : 18
main  : 19
main  : 20
Thread-0  0
Thread-0  1
main  : 21
Thread-0  2
Thread-1  0
main  : 22
main  : 23
Thread-1  1
Thread-0  3

二、Runnable 接口

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。


package com.thread;
 
public class RunnableThreadTest implements Runnable
{
 
    private int i;
    public void run()
    {
        for(i = 0;i <100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args)
    {
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20)
            {
                RunnableThreadTest rtt = new RunnableThreadTest();
                new Thread(rtt,"新线程1").start();
                new Thread(rtt,"新线程2").start();
            }
        }
 
    }
 
}

三、Callable 接口

通过Callable和FutureTask创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

package com.thread;
 
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
 
public class CallableThreadTest implements Callable<Integer>
{
 
    public static void main(String[] args)
    {
        // 创建Callable实现体的实例,使用FutureTask类包装Callable对象
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
            if(i==20)
            {
            //使用FutureTask对象作为Thread对象的target创建并启动新线程
                new Thread(ft,"有返回值的线程").start();
            }
        }
        try
        {
        //调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
            System.out.println("子线程的返回值:"+ft.get());
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        } catch (ExecutionException e)
        {
            e.printStackTrace();
        }
 
    }
 
    @Override
    public Integer call() throws Exception
    {
    // call 方法即为线程的执行体,并且拥有返回值
        int i = 0;
        for(;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
 
}

创建线程的三种方式及对比

相关文章

网友评论

    本文标题:基本线程类

    本文链接:https://www.haomeiwen.com/subject/nfeesktx.html