线程基础(1)

概念

运行程序会创建一个进程。是OS调度的最小单元是线程(轻量级进程)。

普通的java程序包含的线程:

  • 11:Monitor Ctrl-Break //监听中断信号
  • 5:Attach Listener //获取内存dump,线程dump
  • 4:Signal Dispatcher //z将信号分给jvm的线程
  • 3:Finalizer //调用对象的finalizer 方法
  • 2:Reference Handler //清除Reference
  • 1:main //程序的主入口
public class ShowMainThread1 {

    /**
     * 这里启了一个线程,main 线程,但是后台jvm 其实一共启了 6 个线程
     *
     * @param args
     */
    public static void main(String[] args) {
        //java虚拟机的线程管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //获取线程信息的方法
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId() + ":" + threadInfo.getThreadName());
        }
    }


}

为什么要用线程?

  • 充分利用多处理核心;
  • 更快的响应时间(用户订单的场景,发送邮件等部分可由其他线程执行)

启动线程和退出线程

创建线程的方法

extends Thread
implements Runnable
启动线程:threadl类的start()

退出线程:

  • run()方法执行完成;
  • 抛出一个未处理的异常导致线程的提前结束
public class HowStartThread2 {

    /**
     * 申明一个线程
     */
    private static class TestThread extends Thread {
        @Override
        public void run() {
            System.out.println("TestThread is runing");

        }
    }


    /**
     * 申明一个线程
     */
    private static class TestRunable implements Runnable {

        @Override
        public void run() {
            System.out.println("TestRunable is runing");
        }
    }


    public static void main(String[] args) {
        Thread t1 = new TestThread();
        Thread t2 = new Thread(new TestRunable());
        t1.start();
        t2.start();

    }

}

取消和中断

不安全的取消

  • 单独使用一个取消标志位.来取消线程是不安全的
  • Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁或者数据不一致

如何安全的终止线程

使用线程的中断 :
interrupt(),在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。
isInterrupted(),用来判断当前线程的中断状态(true or false)。
interrupted()是个Thread的static方法,用来将中断标志位复位为false

由上面的中断机制可知Java里是没有抢占式任务(强制关闭线程),只有协作式任务。

为何要用中断,线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。

// 安全的中断线程
public class SafeInterrupt4 implements Runnable {
    /**
     * 线程中断标志位
     */
    private volatile boolean on = true;
    private long i = 0;

    @Override
    public void run() {

        // isInterrupted 线程检查自己的中断标志位,判断线程是否已被发送过中断请求
        while (on && !Thread.currentThread().isInterrupted()) {
            i++;
            try {
                System.out.println("线程休眠:===>>" + i);
                
                 //抛出中断异常的阻塞方法,抛出异常后,中断标志位改成false
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();//重新设置一下
                e.printStackTrace();
            }
        }
        System.out.println("TestRunable is runing :" + i);
    }

    public void cancel() {
        on = false;
        // interrupt 中断线程,本质是将线程的中断标志位设为 true
        Thread.currentThread().interrupt();
        System.out.println("设置线程执行结束标志位");
    }

    public static void main(String[] args) throws InterruptedException {
        SafeInterrupt4 safeInterrupt4 = new SafeInterrupt4();

        Thread thread = new Thread(safeInterrupt4);

        thread.start();

        for (int i = 0; i < 100; i++) {
            if (i == 80) {
                safeInterrupt4.cancel();
                System.out.println("结束线程=========>>" + i);
            }
        }
    }
}

处理不可中断的阻塞

IO通信 inputstream read/write等阻塞方法,不会理会中断,而关闭底层的套接字socket.close()会抛出socketException
NIO: selector.select()会阻塞,调用selector的wakeup和close方法会抛出ClosedSelectorException
死锁状态不响应中断的请求,这个必须重启程序,修改错误。

如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?

覆盖线程的interrupt方法,在处理套接字异常时,再在 finally 方法中 用super.interrupt()自行中断线程

线程关态

  • 新创建 线程被创建,但是没有调用start方法
  • 可运行(RUNNABLE) 运行状态,由cpu决定是不是正在运行
  • 被阻塞(BLOCKING) 阻塞,线程被阻塞于锁
  • 等待/计时等待(WAITING) 等待某些条件成熟
  • 被终止 线程执行完毕

线程的优先级

成员变量priority控制优先级,范围1-10之间,数字越高优先级越高,缺省为5,创建线程时setPriotity()可以设置优先级,不要指望他发挥作用。

Daemon线程

守护型线程(如GC线程),程序里没有非Daemon线程时,java程序就会退出。一般用不上,也不建议我们平时开发时使用,因为Try/Finally里的代码不一定执行的。

常用方法深入理解

run()和start()
run就是一个普通的方法,跟其他类的实例方法没有任何区别。

Sleep
不会释放锁,所以我们在用sleep时,要把sleep放在同步代码块的外面。

yield()
当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中,不会释放锁。

  • yield是一个静态的原生(native)方法
  • yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
  • yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
  • 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

wait()和 notify()/notiyfAll()
调用以前,当前线程必须要持有锁,调用了wait() notify()/notiyfAll()会释放锁。

等待通知机制:
线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程(比如线程A)
notify() 唤醒一个线程,唤醒哪一个完全看cpu的心情(谨慎使用)
notiyfAll() 所有在对象O上wait的线程全部唤醒(应该用notiyfAll())

参考:

原文地址:https://www.cnblogs.com/zhangjianbin/p/10093228.html