JUC_1 线程的基础与interrupt

154 阅读4分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

引子

线程的历史--一部对于CPU性能压榨的历史。

什么是程序?进程?线程?纤程/协程?

  • 程序--是指可执行文件,比如QQ、微信。

  • 进程--程序运行起来就产生了进程,一个程序可以多次运行产生多个进程,进程是系统进行资源分配的基本单位。(静态概念)

  • 线程--一个进程里可以有多个任务并行执行,就产生了多个线程,线程是调度执行的基本单位,多个线程共享一个进程里面的资源。(动态概念)

线程方面的问题

1.单核CPU设定多线程是否有意义?

答:有意义。并不是所有的线程操作都是需要消耗CPU,有些可能在sleep,有些可能在进行IO操作。

2.工作线程数是不是设置的越大越好?

答:不是,因为线程之间的切换是需要消耗系统资源的。

3.工作线程数(线程池中线程数量)设多少合适?

答:测试环境通过性能分析工具profiler用来测算 等待时间 和 计算时间,生产环境可以使用Arthas。

1629078052.jpg

创建线程的5种方式

  1. 继承Thread类
    • new MyThread().start()
  2. 实现Runnable接口->run()(无返回值,好处是可以继承其它类)
    • new Thread(r).start()
  3. 实现Callable接口->call()(有返回值)
    • Future Callable and FutureTask
  4. 使用lambda表达式作为入参
    • new Thread(lamda).start()
  5. 线程池来创建Executors
    • ThreadPool 代码演示
package com.mashibing.juc.c_000_threadbasic;

import java.util.concurrent.*;

public class T02_HowToCreateThread {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }

    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }

    static class MyCall implements Callable<String> {
        @Override
        public String call() {
            System.out.println("Hello MyCall");
            return "success";
        }
    }

    //启动线程的5种方式
    public static void main(String[] args) throws Exception {
        new MyThread().start();
        new Thread(new MyRun()).start();
        new Thread(() -> {
            System.out.println("Hello Lambda!");
        }).start();

        FutureTask<String> task = new FutureTask<>(new MyCall());
        Thread t = new Thread(task);
        t.start();
        System.out.println(task.get());

        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(() -> {
            System.out.println("Hello ThreadPool");
        });

        Future<String> f = service.submit(new MyCall());
        String s = f.get();
        System.out.println(s);
        service.shutdown();
    }
}

JAVA的6中线程状态

  1. NEW :线程刚刚创建,还没有启动
  2. RUNNABLE :可运行状态,由线程调度器可以安排执行
    • 包括READY和RUNNING两种细分状态
  3. WAITING: 等待被唤醒
  4. TIMED WAITING: 隔一段时间后自动唤醒
  5. BLOCKED:被阻塞,正在等待锁
  6. TERMINATED:线程结束 1629083474.jpg

线程的打断(interrupt)

  • interrupt()
    • 打断某个线程(设置标志位)
    • 实例方法,设置线程中断标志(打扰一下,你该处理一下中断)
  • isInterrupted()
    • 查询某线程是否被打断过(查询标志位)
    • 实例方法,有没有人打扰我?
  • static interrupted()
    • 查询当前线程是否被打断过,并重置打断标志
    • 静态方法,有没有人打扰我(当前线程)?复位!
//Thread.java  
public void interrupt()  //t.interrupt() 打断t线程(设置t线程某给标志位f=true,并不是打断线程的运行)
public boolean isInterrupted() //t.isInterrupted() 查询打断标志位是否被设置(是不是曾经被打断过)
public static boolean interrupted()//Thread.interrupted() 查看“当前”线程是否被打断,如果被打断,恢复标志位

程序在sleep、wait、join的时候,如果此时设置了标志位interrupt(),会被唤醒,然后抛出InterruptedException异常,catch之后默认会把标志位重置为false

代码演示

/**
 * interrupt与sleep() wait() join()
 */
public static void main(String[] args) {
	Thread t = new Thread(() -> {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {//catch之后默认会把标志位重置为false
			System.out.println("Thread is interrupted!");
			System.out.println(Thread.currentThread().isInterrupted());
		}
	});

	t.start();
	SleepHelper.sleepSeconds(5);
	t.interrupt();
}

通常情况下,线程在竞争锁的过程中是不能被打断的

synchronized与interrupt()

interrupt()不能打断正在竞争锁的线程synchronized()interrupt()不能打断正在竞争锁的线程synchronized()

public class T09_Interrupt_and_sync {

    private static Object o = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (o) {
                SleepHelper.sleepSeconds(10);
            }
        });

        t1.start();

        SleepHelper.sleepSeconds(1);

        Thread t2 = new Thread(() -> {
            synchronized (o) {

            }
            System.out.println("t2 end!");
        });

        t2.start();

        SleepHelper.sleepSeconds(1);

        t2.interrupt();
    }
}

lock与interrupt()

interrupt()不能打断正在竞争锁的线程lock.lock()interrupt()不能打断正在竞争锁的线程lock.lock()

public class T10_Interrupt_and_lock {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println("t1 end!");
        });

        t1.start();

        SleepHelper.sleepSeconds(1);


        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
            } finally {
                lock.unlock();
            }
            System.out.println("t2 end!");
        });

        t2.start();

        SleepHelper.sleepSeconds(1);

        t2.interrupt();
    }
}

ReentrantLock的例外lockInterruptibly()

如果想打断正在竞争锁的线程,使用ReentrantLocklockInterruptibly()如果想打断正在竞争锁的线程,使用ReentrantLock的lockInterruptibly()

public class T11_Interrupt_and_lockInterruptibly {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                SleepHelper.sleepSeconds(10);
            } finally {
                lock.unlock();
            }
            System.out.println("t1 end!");
        });

        t1.start();

        SleepHelper.sleepSeconds(1);

        Thread t2 = new Thread(() -> {
            System.out.println("t2 start!");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println("t2 end!");
        });

        t2.start();

        SleepHelper.sleepSeconds(1);

        t2.interrupt();
    }
}

面试题:如何优雅的结束一个线程?

eg:上传一个大文件,正在处理费时的计算,如何优雅的结束这个线程?

  1. 自然结束(能自然结束就尽量自然结束)
  2. 终止:stop() 、暂停:suspend() 、恢复:resume()
  3. volatile标志
    • 不适合某些场景(比如还没有同步的时候,线程做了阻塞操作,没有办法循环回去)
    • 打断时间也不是特别精确,比如一个阻塞容器,容量为5的时候结束生产者,但是,由于volatile同步线程标志位的时间控制不是很精确,有可能生产者还继续生产一段儿时间
private static volatile boolean running = true;

public static void main(String[] args) {
    Thread t = new Thread(() -> {
        long i = 0L;
        while (running) {
            //wait recv accept
            i++;
        }

        System.out.println("end and i = " + i); //4168806262 
    });

    t.start();

    SleepHelper.sleepSeconds(1);

    running = false;
}
  1. interrupt() and isInterrupted、interrupted()(比较优雅)

    但也不能精准地控制结束的次数和时间

public static void main(String[] args) {
    Thread t = new Thread(() -> {
            while (!Thread.interrupted()) {
                    //sleep wait
            }
            System.out.println("t1 end!");
    });

    t.start();

    SleepHelper.sleepSeconds(1);

    t.interrupt();
}