zoukankan      html  css  js  c++  java
  • [并发编程] -- 线程篇

    线程

    • 1. 简介

    • 1)定义

    • 现代操作系统在运行一个程序时,会为其创建一个进程。例如,启动一个Java程序,操作系统就会创建一个Java进程。现代操作系统调度的最小单元是线程,也叫轻量级进程(LightWeightProcess),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上快速切换,让使读者感觉到这些线程在同时执行。

    • 2) 那么为何要使用多线程

    • 更多的处理器核心

    • 更快的响应

    • 更好的编程模型

    • 3)优先级

    • 由于是操作系统给线程分配时间片的处理方式。那么便可以通过优先级设置处理的先后,确保处理器不会被独占。

    • 4)状态


    • 状态变换图

    • 2. 启动与终止

    • 1)启动与中断

    • 启动:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。

    • 中断:即是线程的一个标识位属性,通过调用该线程的interrupt()方法对其进行中断操作。也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位,当抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,即此时调用isInterrupted()方法将会返回false。

    • 2)安全地终止线程(优雅)

    • 通过设置一个boolean变量控制

       public class Shutdown {
            public static void main(String[] args) throws Exception {
                Runner one = new Runner();
                Thread countThread = new Thread(one, "CountThread");
                countThread.start();
                // 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
                TimeUnit.SECONDS.sleep(1);
                countThread.interrupt();
                Runner two = new Runner();
                countThread = new Thread(two, "CountThread");
                countThread.start();
                // 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为falseer结束
                TimeUnit.SECONDS.sleep(1);
                two.cancel();
            }
    
            private static class Runner implements Runnable {
                private long i;
                private volatile boolean on = true;
    
                @Override
                public void run() {
                    while (on && !Thread.currentThread().isInterrupted()) {
                        i++;
                    }
                    System.out.println("Count i = " + i);
                }
    
                public void cancel() {
                    on = false;
                }
            }
        }
    

    • 3. 线程间通信

    • 1)volatile和synchronized

    • java在多个线程访问一个对象或对象的成员变量,每个线程拥有其拷贝,将其放入各自的缓存中,这样可以加速程序的执行,故线程看到的变量并不一定是最新的。假如需要最新的,此时需要通过volatile通知线程间从共享内存中获取,并刷新回各自的工作内存,即是保证了对所有线程对变量访问的可见性。
    • 关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
      • 同步块的实现使用了monitorentermonitorexit指令。
      • 同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。

    本质:对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。



    • 任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。

      • 2)等待/通知机制

    等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

    • 解决两个问题
      • 确保及时性
      • 降低开销
    • 方法
      • notify: 通知一个对象上等待的线程,使其从wait方法返回,而返回的前提是线程获取到了对象的锁。
      • notifyAll:通知所有等待在该对象上的线程。
      • wait: 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait方法后,会释放对象的锁。
      • wait(long): 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
      • wait(long,int): 对于超时时间更细粒度的控制,可以达到纳秒。
    • 使用注意事项
      • 使用wait()notify()notifyAll()时需要先对调用对象加锁。
      • 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
      • notify()notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
      • notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED
      • wait()方法返回的前提是获得了调用对象的锁。

    上图中,WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。


    • 3)Thread.join()

    定义:如果一个线程A执行了thread.join()语句,当前线程A等待thread线程终止之后才从thread.join()返回。

    • 4)ThreadLocal

    • ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。
    • ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
      • 每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。
      • 当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
      • 当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。
    • 采用ThreadLocal 根本就没有竞争。

    内存泄露:
    实际上 ThreadLocalMap中使用的key为ThreadLocal的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来ThreadLocalMap中使用这个ThreadLocal的key也会被清理掉。但是,value是强引用,不会被清理,这样一来就会出现key为null的value。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录。如果说会出现内存泄漏,那只有在出现了key为null的记录后,没有手动调用remove()方法,并且之后也不再调用get()、set()、remove()方法的情况下。

    明明可以靠才华吃饭,非要靠脸~
  • 相关阅读:
    K3/Cloud点按钮打开单据,列表,动态表单,简单账表和直接Sql报表示例
    K3/Cloud点击按钮打开第三方URL
    List排序
    ClientScriptManager与ScriptManager向客户端注册脚本的区别
    NameValueCollection详解
    Delegate。。
    asp.net gridview ...
    asp json
    合并两个rs结果输出
    asp 数组
  • 原文地址:https://www.cnblogs.com/lycsmzl/p/13213588.html
Copyright © 2011-2022 走看看