Java线程 - 详解(1)

一,创建线程

方法一:继承Thread类

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("线程1");
    }
}

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();//start()方法启动线程

        //和上面的方法一样,只不过使用匿名内部类实现
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                System.out.println("线程2");
            }
        };
        thread1.start();

    }
}

方法二:实现Runnable接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程1");
    }
}
public class Test {
    public static void main(String[] args) {

        Thread thread = new Thread(new MyRunnable());
        thread.start();
        
        //匿名内部类实现
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
            }
        });
        thread2.start();

        //lambda表达式实现
        Thread thread3 = new Thread(() -> System.out.println("线程3"));
        thread3.start();

    }
}

二,Thread类及常见方法

2.1 构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable  target) 使用Runnable对象创建线程对象
Thread(String  name) 创建线程对象并命名
Thread(Runnable  target,String  name) 使用Runnable对象创建线程对象,并命名
Thread(ThreadGroup  group,Runnable  target) 线程可以被用来分组管理,分好的组即为线程组(了解即可)

2.2 获取 Thread 的常见属性

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()
  • ID是线程的唯一标识,ID是JAVA分配的,不会出现重复的,与上篇博客中PCB结构中的pid不是同一个东西。
  • 名称是方便各种调试工具使用。
  • 后台线程不会影响线程的结束,前台线程会影响线程的结束,一般线程默认为前台线程,还有一个注意点——JVM会在一个进程的所有后台进程结束后,才会结束运行。
  • 是否存活,简单理解就是 run 方法是否运行结束。
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            System.out.println("线程开始");
            try {
                Thread.sleep(1000);//休眠线程 xxx ms
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"1号线程");
        thread.setDaemon(true);//设置为后台线程
        thread.start();
        System.out.println(thread.getName() + " " + thread.isAlive());
        Thread.sleep(3000);
        System.out.println("线程结束");
        System.out.println(thread.isAlive());
    }
}

 

2.3 start()  与  run() 的区别

作用功能:

  1. start()方法内部是会调用系统的API,在系统内核创建一个线程
  2. run()方法只是描述线程具体实现的任务(会在start创建好之后会自动被调用)

运行结果:

  1. start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。
  2. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次

 2.4 中断一个线程

在Java中,终止/销毁一个线程的做法比较单一,就是尽快让 run 方法执行结束。

方法一: 在代码中手动创建一个标志位,来作为 run 的执行结束条件,比如在很多线程中,执行时间长往往是写了一个循环。

public class Demo {
    private static boolean isQuit = false;//通过类属性来控制
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!isQuit){
                System.out.println("666");
            }
        });
        thread.start();
        Thread.sleep(1);
        isQuit = true;
    }
}

 注:这里的 isQuit 不能是局部变量,因为如果是局部变量就不满足 lambda表达式中的变量捕获语法。

但是上面的方法有两个缺点:1. 需要手动创建变量 2. 当线程内部在 sleep 的时候,主线程修改变量时,新线程内部不能及时响应,所以Java提供了另一种解决方法:

方法二:使用 interupt() 和 isInterrupted()

方法 说明
public void interrupt() 中断对象关联的线程,如果线程阻塞,则以异常方式通知,否则设置标志位
public static boolean interrupted() 判断当前线程中的标志位是否设置,调用后清除标志位
public boolean isInterrupted() 判断对象关联的线程的标志位是否设置,调用后不清除标志
public class Demo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            // Thread.currentThread()作用是得到当前的线程
            // isInterrupted() 判断标识符是否为false
            //或者 while(!Thread.interrupted()) 效果一样
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("线程工作中!");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();//打印异常
               }
           }
        });
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();//将标识符设置为true
    }
}

但是报错后,该线程没有中断,而是继续执行,这是为什么呢?

这是因为当我们将 标识符设为true 时,正好线程在sleep,会触发sleep内部的一个异常,从而会将线程从sleep中唤醒,但是在sleep抛出异常的同时,它会自动删除刚才设置的 标志位,这将会使 interrupt 这一操作好像没有被执行。

为什么Java会这样设定呢? 因为 Java 是希望当 线程收到 "中断" 信号时,它能自由决定接下来要怎么处理,比如:我们可以在打印报错后 接着写一些代码 :

public class Demo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            // Thread.currentThread()作用是得到当前的线程
            // isInterrupted() 判断标识符是否为false
            //或者 while(!Thread.interrupted()) 效果一样
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("线程工作中!");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();//打印异常
                   System.out.println("还需要完成的操作...");//2.
                   break;//1.直接退出
               }
           }
        });
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();//将标识符设置为true
    }
}

 注:

  1. 如果没有sleep,就不会出现上述情况,线程会直接退出!!!
  2. 我们不建议使用 Thread.interrupted() 这种做法,因为该方法是静态方法,意味着所有的线程共用一个标识符,而我们使用的线程肯定不止一个,如果使用该方法,就乱套了!!!

2.5 等待一个线程 - join()

作用:让一个线程等待另一个线程执行结束后,在继续执行。本质上是控制线程结束的顺序。

方法 作用
public void join() 等待线程结束
public void join(long millis) 等待线程结束,但是最多等待 millis 毫秒
public void join(long millis, int nanos) 同上,但是精度更高
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("线程工作中!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        System.out.println("main线程开始等待");
        thread.join();
        System.out.println("等待结束");
    }
}

 注:

  1. 要分清哪个线程是等待的,哪个线程是被等待的
  2. join() 会出现 "死等" 的情况,我们一般不会使用,建议使用有参数的,可以自定义等待时间

2.6 其他 

方法 作用
public static Thread currentThread() 返回当前线程对象的引用
public static void sleep(long millis) throws interruptedException 当前线程休眠millis毫秒
public static void sleep(long millis, int nanos) throws interruptedException 同上,精度更高

三,线程的状态

  •  NEW:  Thread 对象已经有了,start 方法还没有调用
  • TERMINATED:  Thread 对象还在,内核中的线程已经没了
  • RUNNABLE:  就绪状态 (线程已经在 CPU 上运行 / 线程正在排队等待去 CPU 上运行)
  • TIME_WAITING:  阻塞,由于 sleep 这种固定时间的方式产生阻塞
  • WAITING:  阻塞,由于 wait 这种不固定时间的方式产生阻塞
  • BLOCKED:  阻塞,由于锁竞争导致的阻塞
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        // getState() 获得当前线程的状态
        System.out.println(thread.getState());
        thread.start();
        for (int i = 0; i < 3; i++) {
            System.out.println(thread.getState());
            Thread.sleep(200);
        }
        thread.join();
        System.out.println(thread.getState());
    }
}

 

猜你喜欢

转载自blog.csdn.net/m0_74859835/article/details/132472070