• 并发编程基础


    线程基础

    线程的三种创建方法

    继承Thread类.java不支持多继承,子类不能继承其他类

    实现runnable接口:还可以继承其他类

    实现callable接口:有返回值


    线程相关JVM语句

    cmder 常用语句记录:

    软件:cmder 就是个cmd,就是个命令行,不过她是支持复制粘贴的

    jcmd:

    发送诊断命令请求到正在运行的Java虚拟机(JVM)。它必须和JVM运行在同一台机器上,并且与启动JVM用户具有相同的组权限。

    jcmd 31275 Thread.print -l # 打印线程栈
    jcmd 31275 VM.command_line # 打印启动命令及参数
    jcmd 31275 GC.heap_dump /data/31275.dump # dump heap
    jcmd 31275 GC.class_histogram #查看类的统计信息
    jcmd 31275 VM.system_properties #查看系统属性内容
    jcmd 31275 VM.uptime #查看虚拟机启动时间
    jcmd 31275 PerfCounter.print #查看性能统计
    

    jps -lvm 查看所有在本机上运行java的进程

    image-20211225172751678

    常用解析:

    image-20211225172729846

    jstack

    jstack -l 9552

    打印出所有的线程堆栈

    一般第一个就是销毁线程,没有daemon 守护线程标记,是一个用户线程,是等待所有用户线程执行完毕,用来销毁java虚拟机的线程

    image-20211225170949595

    join()

    注意:

    join方法为Thread类直接提供的方法,而wait和notify为Object类中的方法

    等待线程执行终止之后,继续执行,比如

    Thread t1 = new Thread();
    t1.start();
    t1.join(1000);//毫秒时间
    System.out.println("t1's state:"+t1.getState());
    

    就是先将t1线程start,有join,就阻塞主线程

    这里是主线程阻塞1秒,过了这个时间就继续执行后面的代码

    join()和join(1000)的区别

    thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

    比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

    t.join(); //调用join方法,等待线程t执行完毕
    t.join(1000); //等待 t 线程,等待时间是1000毫秒。

    源码解析:

    /**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    

    方法解释:

    最多等待 ms 毫秒让该线程死亡。 超时为 0 意味着永远等待。 此实现使用以 this.isAlive 为条件的 this.wait 调用循环。 当线程终止时,调用 this.notifyAll 方法。 建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。

    底层调用isAlive()方法,

    Tests if this thread is alive. A thread is alive if it has been started and has not yet died.
    Returns:
    true if this thread is alive; false otherwise.
    测试此线程是否存活。 如果线程已启动且尚未死亡,则该线程处于活动状态。
    返回:
    如果此线程还活着,则为真; 否则为假。 
    

    总结:也就是如果t.join()就和wait类似,主线程要等待这个t线程结束才能执行下去,底层调用object的wait()方法

    join(xxx)表示最多等待xxx毫秒,如果在这个时间内线程执行完毕,则立即执行join后面的代码。


    sleep()

    不释放锁——原子性

    Thread类中的一个静态方法,暂时让出执行权,不参与CPU调度,但是。时间到了就进入到就绪状态,一旦获取到CPU时间片,则继续执行。

    是一个native static方法

    可以用中断异常

    t1.interrupt();


    yield()—假装客气

    不释放锁——原子性

    知识点:Thread类中的静态native方法;让出剩余的时间片,本身进入就绪状态,CPU再次调度还可能调度到本线程。

    易混淆知识点:sleep是在一段时间内进入阻塞状态,cpu不会调度它。而yield是让出执行权,本身还处于就绪状态,cpu还可能立即调度它,可以不理会它


    wait()/notify-object

    释放锁

    notify不释放锁

    有且仅有wait会释放锁

    wait一般与synchronize+notify连用,因为会释放锁


    interrupt()

    线程中断

    知识点:线程中断是线程间的一种协作模式,通过设置线程中断标志来实现,线程根据这个标志来自行处理。

    public void interrupt() 中断标志设置为true

    public boolean isInterrupted() 获取中断标记

    public static boolean interrupted() 将中断标记取反,会改变当前线程的中断标记状态

    ​ 获取当前线程的状态:容易混淆!,在主函数中就是获取main线程

    会导致sleep和join线程抛出中断异常


    线程安全问题原理

    可见性和原子性

    1.线程安全的概念:当多个线程访问某一个类、对象或方法时,这个类、对象或方法都能表现出与单

    线程执行时一致的行为,那么这个类、对象或方法就是线程安全的。

    2.线程安全问题都是由全局变量及静态变量引起的。

    3.若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程

    安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。


    synchronized关键字

    原理就是保证原子性,维护可见性,维护秩序。

    Synchronized的作用是加锁,所有的synchronized方法都会顺序执行,(这里只占用CPU的顺序)。

    Synchronized方法执行方式:

    – 首先尝试获得锁

    – 如果获得锁,则执行Synchronized的方法体内容。

    – 如果无法获得锁则等待,并且不断的尝试去获得锁,一旦锁被释放,则多个线程会同时去尝

    试获得所,造成锁竞争问题。

    锁竞争问题,在高并发、线程数量高时会引起CPU占用居高不下,或者直接宕机。


    对象锁和类锁--区别

    Synchronized作用在非静态方法上代表的对象锁,一个对象一个锁,多个对象之间不会发生锁竞争。

    Synchronized作用在静态方法上则升级为类锁,所有对象共享一把锁,存在锁竞争。

    比如

    User user1 = new User();
    User user2 = new User();
    synchronized(use1)
    synchronized(use2)
    锁是不同的对象,不同对象之间吧刽发生锁竞争
    
    但是如果   static synchronize  是类锁的话就会发生竞争 
    

    原理是类锁的话,静态变量和静态方法是存储在方法区中的,访问的是同一个数据

    但是对象锁而言,对象是存储在堆中的,比如上述的user1和user2都在堆中是不同的对象,不是同一个资源,所以不存在锁竞争

    一个类里面如果方法上有synchronize表示当前的锁是当前类对象


    对象锁的同步和异步

    同步:必须等待方法执行完毕,才能向下执行,共享资源访问的时候,为了保证线程安全,必须同步。

    异步:不用等待其他方法执行完毕,即可立即执行,例如Ajax异步。

    对象锁只针对synchronized修饰的方法生效、 对象中的所有synchronized方法都会同步执行、而非

    synchronized方法异步执行

    避免误区:类中有两个synchronized方法,两个线程分别调用两个方法,相互之间也需要竞争锁,

    因为两个方法从属于一个对象,而我们是在对象上加锁

    脏读问题

    多个线程访问同一个资源,在一个线程修改数据的过程中,有另外的线程来读取数据,就会引起脏

    读的产生。

    为了避免脏读我们一定要保证数据修改操作的原子性、并且对读取操作也要进行同步控制

    锁重入

    同一个线程得到了一个对象的锁之后,再次请求此对象时可以再次获得该对象的锁。

    父子类可以锁重入

    public class DemoThread05{
       
       public synchronized void run1(){
          System.out.println(Thread.currentThread().getName()+">run1...");
          //调用同类中的synchronized方法不会引起死锁
          run2();
       }
       
       public synchronized void run2(){
          System.out.println(Thread.currentThread().getName()+">run2...");
       }
       
       public static void main(String[] args) {
          final DemoThread05 demoThread05 = new DemoThread05();
          Thread thread = new Thread(new Runnable() {
             @Override
             public void run() {
                demoThread05.run1();
             }
          });
          thread.start();
       }
    }
    

    抛异常和锁的关系

    一个线程在获得锁之后执行操作,发生错误抛出异常,则自动释放锁

    1、可以利用抛出异常,主动释放锁

    2、程序异常时防止资源被死锁、无法释放

    3、异常释放锁可能导致数据不一致

    synchronized代码块

    可以达到更细粒度的控制

    当前对象锁

    类锁

    任意对象锁

    总结:

    ​ 同类型锁之间互斥,不同类型的锁之间互不干扰

    不要在线程内修改对象锁的引用

    不能修改锁的指向地址。如果是对象,修改对象里的值是不会引起锁失效的

    并发和死锁

    是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现

    象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永

    远在互相等待的进程称为死锁进程。

    线程间的通讯

    每个线程都是独立运行的个体,线程通讯能让多个线程之间协同工作

    Object类中的wait/notify方法可以 实现线程间通讯

    Wait/notify必须与synchronized一同使用

    Wait释放锁、notify不释放锁

    notify与notifyAll的区别

    notify本身不释放锁

    Notify只会通知一个wait中的线程,并把锁给他,不会产生锁竞争问题,但是该线程处理完毕之后

    必须再次notify或notifyAll,完成类似链式的操作。

    NotifyAll会通知所有wait中的线程,会产生锁竞争问题

    notify的通知依赖底层JVM的实现,这里notify依赖的是native方法,找到源码,目前是先进先出,就是最早wait()的先被唤醒。

    synchronized的wait和notify是位于ObjectMonitor.cpp中

    这里实际上是将_WaitSet中的第一个元素进行出队操作

    原来hotspot对notofy()的实现并不是我们以为的随机唤醒, 而是“先进先出”的顺序唤醒!`

    实战:实现阻塞式线程安全队列

    使用synchronized、wait、notify实现带阻塞的线程安全队列

    功能描述:

    功能1:在队列元素满的时候,put阻塞,在队列元素空的时候,get阻塞

    功能2 :线程安全

    代码:DemoThread20

    视频中的代码 仍然有bug,不过我已经改正了

    while (this.list.size() == this.maxSize) {
        lock.wait();
    }
    

    问题就是wait中的线程被唤醒时和新线程竞争的时候破坏了数据的原子性,因为集合是非原子性的

    守护线程和用户线程

    线程分类:daemon线程(守护线程)、user线程(用户线程)

    易混淆知识点:main函数所在的线程就是一个用户线程

    重要知识点1:最后一个user线程结束时,JVM会正常退出,不管是否有守护线程正在运行。反过来说:只要有一个用户线程还没结束,JVM进程就不会结束。

    重要知识点2:父线程结束后,子线程还可以继续存活,子线程的生命周期不受父线程的影响

    shutdown和kill的区别

    shutdown会将所有正在执行的请求执行完毕,而kill不会,尤其是kill -9

    线程上下文切换

    CPU采用时间片轮巡的策略,线程切换的过程就是上下文切换

    当前线程使用完时间片后就会进入就绪状态,让出CPU执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。

    当发生上下文切换的时候需要保存执行现场,待下次执行时进行恢复。

    所以频繁的、大量的上下文切换会造成一定资源开销

    image-20211225171848279

    验证

    shutdown会将所有controller正在执行的请求执行完毕,而kill不会

  • 相关阅读:
    [Java]用于将链表变成字符串并在元素之间插入分隔符的有用函数“String.join”
    Sql语法树示例 select username, ismale from userinfo where age > 20 and level > 5 and 1 = 1
    [Java]一段尚未雕琢的分词代码
    day44_Oracle学习笔记_03
    day43_Oracle学习笔记_02
    WinXP系统中的Oracle数据库如何以管理员身份登录
    Oracle 10G安装指导
    20个Linux服务器性能调优技巧
    Linux 上使用 Gmail SMTP 服务器发送邮件通知
    Netdata Linux下性能实时监测工具
  • 原文地址:https://www.cnblogs.com/yslu/p/16250478.html
Copyright © 2020-2023  润新知