线程的创建、线程的基础方法(详细)

文章详细介绍了Java中创建线程的五种方式,包括继承Thread类、实现Runnable接口、匿名内部类以及使用lambda表达式。同时,讨论了线程中断的两种策略,通过共享标志和调用interrupt()方法,以及join方法用于线程同步和Thread.currentThread()获取当前线程实例。最后提到了线程休眠的sleep方法。

目录

一、线程的创建

1. 继承 Thread 类

2. 实现 Runnable 接口

3. 匿名内部类(继承 Thread 类和实现 Runnable 接口)

4. lambda 表达式

5. 整体小结

二、Thread 类

1. Thread 的常见构造方法

2. Thread 的常见属性

三、线程中断

1. 通过共享的标记来进行沟通中断(手动设置标志位)

2. 调用 interrupt() 方法通知:isinterrupted

 四、线程等待:join

 五、获取线程的实例:currentThread

 六、线程休眠:sleep


一、线程的创建

     线程的创建方法有 5 种:

  • ①继承Thread 类;
  • ②实现 Runnable 接口;
  • ③匿名内部类,继承Thread 类;
  • ④ 匿名内部类,实现 Runnable 接口;
  • ⑤ lambda 表达式。

1. 继承 Thread 类

   创建子类,继承自 Thread 类,并且重写 run 方法。

   run 方法:描述了这个线程内部要执行哪些代码。

注:需要调用start方法,才真正在系统中创建了线程,在真正开始执行上面的 run 中的操作。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("111");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

     输出结果:

2. 实现 Runnable 接口

class MyRunnable implements  Runnable {
    @Override
    public void run() {
        System.out.println("222");
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

     输出结果:

3. 匿名内部类(继承 Thread 类和实现 Runnable 接口)

public class Demo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println("1111");
            }
        };

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("2222");
            }
        });

        t1.start();
        t2.start();
    }
}

     输出结果:

     也可能会出现如下情况,因为这里用到了多线程,t1 线程和 t2 线程是并发运行的,所以谁先输出是不确定的。 

4. lambda 表达式

public class Demo4 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()-> {
            System.out.println("111");
        });

        t1.start();
    }
}

     输出结果:

5. 整体小结

public class Demo1 {
    //1.继承 Thread 类,重写 run 方法
    class MyThread extends Thread{
        public void run(){
            System.out.println("111");
        }
    }
    //2.实现 Runnable 接口,重写 run 方法
    class MyRunnacble implements Runnable{
        @Override
        public void run() {
            System.out.println("222");
        }
    }
    //3.匿名内部类,继承 Thread 类
    public static void Demo3() {
        Thread thread = new Thread(){
            public void run() {
                System.out.println("333");
            }
        };
    }
    //4.匿名内部类,实现 Runnable 接口
    public static void Demo4() {
        Thread thread = new Thread(new Runnable(){
            public void run() {
                System.out.println("444");
            }
        });
    }
    //5.lambda 表达式
    public static void Demo5() {
        Thread thread = new Thread(() -> {
            System.out.println("555");
        });
    }
}

strat 和 run 的区别:

   run 方法只是一个普通的方法,描述了任务的内容。在main线程里调用 run,并没有创建新的线程。

   start 方法是一个特殊的方法,其内部会在系统中创建线程。

二、Thread 类

1. Thread 的常见构造方法

方法说明

Thread()

创建线程对象
Thread( Runnable target )使用 Runnable 对象创建线程对象

Thread( String name )

创建线程对象,并命名
Thread( Runnable target, String name)使用 Runnable 对象创建线程对象,并命名

2. Thread 的常见属性

属性获取方法
IDgetID ( )
名称getName ( )
状态getState ( )
优先级getPriority ( )
是否有后台线程isDaemon ( )
是否存活isAlive ( )
是否被中断isInterrupted ( )

三、线程中断

   核心思路是让线程的 run 方法执行完。特殊情况:main 线程来说,main 方法执行完,线程就完了。

有两种方法:

1. 通过共享的标记来进行沟通中断(手动设置标志位)

   自己手动设置一个标志位,注意需要给标志位加上 volatile 关键字(确保线程安全)。

public class Demo3 {
    //自己设置一个标志位:isQuit 是否退出,false 不退出, true 退出
    //因为 main 线程修改的 isQuit 和 t 线程判定的 isQuit 是同一个值。
    private static volatile boolean isQuit = false; //加上 volatile 确保线程安全

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!isQuit) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        //当标志位 isQuit 为 true 时,就退出循环,进一步 run 结束,再进一步线程t 结束
        //先让 t 线程执行一会
        Thread.sleep(5000);

        isQuit = true;
        System.out.println("终止 t 线程");
    }
}

2. 调用 interrupt() 方法通知:isinterrupted

   使用 Thread 中内置的标志位来进行判定。可以通过以下两种方法来进行获取标志位,进行判定。

  • Thread.interrupted() :静态的方法
  • Thread.currentThread().isinterrupted() :实例方法,currentThread 能够获取当先线程的实例。

注意: interrupted() 和 isnterrupted() 的区别:

① interrupted() 这个方法判定的标志位是 Thread 的 static 成员。(一个程序中只有一个标志位)

② isinterrupted() 这个方法判定的标志位是 Thread 的普通成员。每个线程实例都有自己的标志位。 经常使用。

   如要让线程中断:

  • t.interrupt() :将线程 t 中断。

注意:使用该方法后,可能产生两种情况:

① 如果 t 线程处于 就绪状态,就是设置线程的标志位为 true;

② 如果 t 线程处于 阻塞状态(sleep休眠了),就会触发一个InterruptException异常,从而导致 线程 从阻塞状态 被唤醒。

因此,在 sleep 中,不能单纯在 catch 中打印日志。还要加上 break。 

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //判断线程中内置的标志位(是否中断位)为 true 还是 false
            //为 true : 说明 是中断,循环应该结束
            //为 false:说明 没有中断,循环应该继续
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //当触发异常后,立即退出循环。
                    break;
                }
            }
        });
        t.start();

        //在主线程中,调用interrupt方法,来中断这个线程
        Thread.sleep(5000);  //休眠 5 s

        //t.interrupt 的意思是 让 t 线程被中断
        t.interrupt();

    }
}

 四、线程等待:join

   多个线程之间的调度顺序是不确定的,有时需要控制线程的执行顺序,要控制线程之间的顺序,其中一种方法就是线程等待。

   线程等待,主要是控制线程结束的先后顺序。

  • join 方法:哪个线程调用的 join ,哪个线程就会阻塞等待,等到对应线程执行完毕为止(对应线程的 run 执行完)。

   在如下代码中,调用 join 方法的线程是 main 线程,是针对 t 线程对象调用的,此时就是让 main 等待 t 。 调用 join 后,main 线程就会进入阻塞状态(暂时无法在cpu上执行),即不往下继续执行了。main 线程必须等到 t 线程执行完毕才能恢复成 就绪状态。即 控制 t 线程先结束,main 线程后结束

注意:

join 操作默认情况下,是死等,不合理。因此还可以执行等待时间,最长等待多久,等不到就不等了。

  • t.join(1000):main 线程等待 t 线程 1000 ms(1s)。

 

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        //在主线程中进行等待,使用 join 方法,等待 t 线程执行完。
        //t.join();
        
        //如果 10s 之内,t 线程结束了,此时 join 直接返回
        //如果 10s 之后,t 线程还没结束,此时 join 也直接返回,不等了
        t.join(10000);
        

        for (int i = 0; i < 5; i ++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

 五、获取线程的实例:currentThread

  • Thrad.currentThread() :获取当先线程的引用(实例)

   哪个线程调用的 currentThread ,就获取到的是哪个线程的实例。

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            public void run() {
                //调用 currentThread() 的是线程 t,因此返回的 name 是 t 线程的 name:Thread-0
                //特殊情况:这里也可以写成 this.getName()。
                System.out.println(Thread.currentThread().getName());
            }
        };
        t.start();
        t.join();
        //调用 currentThrad() 的是 main 线程,因此返回的是 main 线程的 name:main
        System.out.println(Thread.currentThread().getName());
    }
}

 六、线程休眠:sleep

   进程是通过 PCB 进行描述(狭义描述),通过双向链表组织起来的。当进程中有多个线程时,此时每个线程都有一个PCB(在Linux 中,进程和线程也没有明显区分,线程被称为轻量级进程),一个进程就对应了一组 PCB。PCB 上有一个字段 tgroupId,同一个进程中的 tgroupId 是相同的。

   每个线程的 PCB 中状态 有 就绪状态 和 阻塞状态。因此在进程中,有两个队列(均由双向链表将每个线程的 PCB 连接起来):就绪队列 和 阻塞队列。

   当某线程调用 sleep 时,这个线程对应的 PCB 就会进入 阻塞队列。而操作系统调度线程时,只从就绪队列中挑选 PCB 到 CPU 上运行,而阻塞队列中的 PCB 就只能干等。

   当睡眠时间到了,系统才会把该 PCB 从 阻塞队列 中拿到 就绪队列 中去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值