• 线程和进程相关说明


    线程上下文切换:会存储上一个执行线程的状态,比如栈帧信息,程序计数器信息等,切换会耗费计算机资源的,所以并不是线程越多,执行效率就越高,如果线程数大于CPU的核心数,切换就会越频繁

    线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法,需要保存线程状态

    创建线程

    • 重写Thread的run()方法
    • 将线程和线程要执行的东西分离,使用接口Runnable
    • 用Future附加更多功能

    Callable和Runnable

    • Runnable,run()方法是void,所以没有任何返回结果
    • Callable位于java.util.concurrent包,call()方法能返回结果
    • 这两者是没有比较的,根本就是不同的东西,Runnable是线程执行的内容,Callalbe只是说明该线程最后会返回数据,而返回的数据需要用Future去获取
    FutureTask<Integer> future = new FutureTask<Integer>(callable);
    // FutureTask是实现Runnable接口的,只是又实现了Future接口
    new Thread(future, "future").start();
    
    future.get(); // 会返回future线程执行完毕后返回的结果,是一个阻塞方法,可以用另一个线程去获取
    
    
    // 下面是一个例子说明Runnable和Callable, Future的使用
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            log.debug("Runnable执行操作,普通线程");
        }
    };
    
    new Thread(runnable, "normal_thread").start();
    
    Callable<Integer> callable = new Callable<Integer>() {
        public Integer call() throws Exception {
            log.debug("Callable标记操作,返回数据线程");
            sleep(2);
            return new Random().nextInt(100);
        }
    };
    
    // FutureTask implements Runnable, Future<V>
    FutureTask<Integer> future = new FutureTask<Integer>(callable);
    new Thread(future, "callable_thread").start();
    
    new Thread(()->{
        try {
            log.debug("得到线程结果" + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }, "future_thread").start();
    
    

    可以理解为,Callable和Future只是增强了Runnable接口,Callable标记这个线程会有返回,而Future就是去接收这个返回

    常见方法

    • start()方法,启动线程
    • run()方法,启动的线程执行的操作,无返回的Runnable和又返回的Callable,Future最后都执行的是Runnable的run()方法
    • join()方法,等待线程运行结束,哪个线程调用join,哪个等待哪个线程结束,带参数的等待,线程还没结束,则继续后面的代码,如果线程提前结束,则join会提前结束
    Thread t1 = new Thread(() -> {
        log.debug("t1 start");
        // sleep(1); // join(),等待结束
        // sleep(4); // join(2000),等待2s,之后继续执行
        sleep(3);  // join(7000),等待7s,但线程提前结束,join()也结束
        log.debug("t1 end");
    }, "t1_thread");
    
    Thread t2 = new Thread(() -> {
        log.debug("t2 start");
    
        try {
            // t1.join();  // 等待t1线程执行结束,结束之后才会继续执行后面的代码
            // t1.join(2000); // 等待t1线程2s,2s之后继续执行后面的代码,不管t1是否执行完成,都会继续后面的代码
            t1.join(7000); // 等待t1线程7s,但线程提前结束,则join()也会提前结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        log.debug("t2 end");
    }, "t2_thread");
    t1.start();
    t2.start();
    
    • interrupt(),标记打断状态

    • static interrupted(),判断当前线程是否被打断,会清除打断标记

    • isInterrupted(),判断当前线程是否被打断,不会清除打断标记

    • yield(),让出线程的使用权

    打断线程

    调用该方法并不会打断线程,只是会标记该线程是否打断

    • 打断阻塞的线程:调用sleep,wait,join的线程会进入阻塞状态,打断阻塞的线程,会清除打断标记,也就是isInterupted为false,会抛出异常(InterruptedException)
    • 打断正常的线程,清除标记设置为true
    • 打断park线程,LockSupport.park()会让线程进入park线程,打断标记为true时,调用LockSupport.park()不会进入park状态
    Thread t1 = new Thread(() -> {
        log.debug("t1 thread is running..");
        while(true){
    
        }
    }, "t1");
    
    t1.start();
    t1.interrupt();  // 如果线程没有启动(未调用start()方法),打断标记为false
    log.debug("t1 thread interrupt normal: " + t1.isInterrupted()); // true
    
    Thread t2 = new Thread(() -> {
        log.debug("t2 thread is running..");
        while(true){
            sleep(3);
            log.debug("t2 running");
        }
    }, "t2");
    
    t2.start();
    t2.interrupt();
    log.debug("t2 thread interrupt with no sleep: " + t2.isInterrupted()); // true,t2线程启动后打断,但t2线程还没进入sleep就打断,所以返回false
    
    Thread t3 = new Thread(() -> {
        log.debug("t3 thread is running..");
        while(true){
            sleep(3);
            log.debug("t3 running");
        }
    }, "t3");
    
    t3.start();
    sleep(1);  // 保证t3线程进入sleep
    t3.interrupt();
    log.debug("t3 thread interrupt with real sleep: " + t3.isInterrupted()); // false,t3线程已经进入sleep,所以打断标记为false
    
    Thread t4 = new Thread(()->{
        log.debug("t4 thread execute");
    }, "t4");
    // t4.start();
    sleep(1);
    t4.interrupt();
    log.debug("t4 thread has terminated: " + t4.isInterrupted()); // false,还未启动,所以是 false
    

    虽然调用了interrupt()方法,t1,t2,t3线程依然会继续执行,该方法做的事仅仅是设置打断标记,设置成功与否还要分情况,并不会真正停止线程,而是要求线程自己去判断是否需要停止,这么做的理由和stop()方法一样,如果是外部线程能终止一个线程,可能导致终止的线程锁死共享资源永远不会释放,所以只是设置打断标记,然后由线程自己去监控是否终止线程,就是两阶段终止模式。

    线程中的两阶段终止模式

    stop()会真正的终止线程,如果此线程锁住了共享资源,则永远不会释放锁了,所以最好不要使用这个API,使用两阶段终止模式,让线程自己终止线程。

    Thread t1 = new Thread(() -> {
        while (true){
            if(Thread.currentThread().isInterrupted()){// 执行阶段
                log.debug("执行收尾工作,释放资源等操作");
                break;  // 由线程自己来结束,而不是外部线程,就是避免资源锁死的情况
            }
            log.debug("模拟线程的各种操作");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 保证打断标记设置成功
                e.printStackTrace();
            }
        }
    }, "t1");
    
    t1.start();
    sleep(3);
    t1.interrupt();  // 准备阶段,标记线程被打断,但不是真正终止线程
    

    线程状态

    • 操作系统的概念:初始,可运行,运行,阻塞,终止,操作系统将线程分为这5个状态
    • java中的状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,java中定义了6中状态(java.lang.Thread.State)
      • NEW,创建了线程,但未启动
      • RUNNABLE,其他状态以外的状态,比如等待IO,在操作系统看属于阻塞,在java看属于RUNNABLE状态
      • BLOCKED,等待锁的状态
      • WAITING,等待其他线程,比如join,park,wait
      • TIMED_WAITING,等待其他线程,只是有时间限制,比如sleep,join(1000),wait(1000)
      • TERMINATED,线程结束
        经过测试,线程处于RUNNABLE和BLOCKED状态的时候,执行interrupt()方法会将设置打断标记为true,其他时候不会设置

    守护线程

    没有其他线程运行的时候,守护线程就会结束。

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (; ; ) {
                log.debug("t1 daemon thread execute");
                sleep(1);
            }
        }, "t1");
        t1.setDaemon(true);
        t1.start();
    
        Thread t2 = new Thread(()->{
            sleep(1);
            log.debug("t2 thread finish");
        }, "t2");
        t2.start();
    }
    

    虽然t1线程是死循环,但它是一个守护线程,当t2线程结束的时候,t1也会结束。

    synchronized使用对象锁保证临界区内代码的原子性

    能锁住对象,只能是对象,也就是在堆中有空间。synchronized修饰方法时,锁住的是this对象。

    面向对象的改进

    线程安全的类。共享资源的对象化。

    @Slf4j
    public class ThreadSafeTest {
        private static int lockCount = 0;
    
        public static void main(String[] args) {
            useLockCount();
            useLockObject();
        }
    
        private static void useLockCount() {
            Thread incr = new Thread(() -> {
                for (int i = 0; i < 10000; i++) {
                    synchronized (ThreadSafeTest.class) {
                        lockCount++;
                    }
                }
            }, "incr");
    
            Thread decr = new Thread(() -> {
                for (int i = 0; i < 10000; i++) {
                    synchronized (ThreadSafeTest.class) {
                        lockCount--;
                    }
                }
            }, "decr");
    
            decr.start();
            incr.start();
    
            try {
                incr.join();
                decr.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            log.debug("lockCount is: " + lockCount); // 一定为0,使用锁保证资源的安全
        }
    
    
        // 两个线程同时访问lockCount,需要保证安全,可以将lockCount用对象的形式实现
        private static void useLockObject() {
            Room room = new Room();
            Thread incr = new Thread(() -> {
                for (int i = 0; i < 100000; i++) {
                    room.incr();
                }
            }, "incr");
    
            Thread decr = new Thread(() -> {
                for (int i = 0; i < 100000; i++) {
                    room.desc();
                }
            }, "decr");
    
            decr.start();
            incr.start();
    
            try {
                incr.join();
                decr.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            log.debug("useLockObject is: " + room.getCount()); // 一定为0,使用锁保证资源的安全
        }
    
        // 加锁的行为对象化
        // lockCount的对象化
        public static class Room {
            private static int count = 0;
    
            public synchronized void incr() {
                count++;
            }
    
            public synchronized void desc() {
                count--;
            }
    
            public synchronized int getCount() {
                return count;
            }
        }
    }
    

    线程安全的类就是这么提出来的,像String, StringBuffer, Vector, Hashtable就是线程安全的类。

  • 相关阅读:
    Numpy 里线性代数函数
    lateral view 使用方法
    Numpy 基础函数
    Numpy 基础操作
    pandas 基础操作记录学习
    pandas向左移动非空单元格
    供应商自动记账
    SAP Smartforms 参数配置
    SAP FPM 相关包 APB_FPM_CORE
    SAP BPC 清除CUBE 中的数据
  • 原文地址:https://www.cnblogs.com/catelina/p/15392313.html
Copyright © 2020-2023  润新知