【多线程】知识点总结(二)线程状态 线程安全问题 可重入锁ReentrantLock 与 内存锁synchronized

线程状态

1 打印线程的所有状态

private static void printState() {
    for(Thread.State item:Thread.State.values())
        System.out.println(item);
}
NEW
RUNNABLE
BLOCKED
WAITING 等待
TIMED_WAITING 超时等待 有明确结束时间
TERMINATED

2 线程状态:

  1. new:新建状态,当线程被创建,但未启动(start()之前
  2. runnable:运行状态
    1. 运行:得到时间片运行中状态
    2. 就绪:未得到时间片就绪状态 (保存了上下文
  3. blocked:阻塞状态,如果遇到锁线程就会变为阻塞状态,等到另一个线程释放锁
  4. waiting:休眠 等待状态(无明确等待时间
  5. timed_waiting:休眠 等待状态(有明确结束时间
  6. terminated:销毁状态,线程结束之后会变成此状态

3 获取线程状态:

public static void main(String[] args) throws InterruptedException {
        //printState();
        Thread t1=new Thread(()->{
            System.out.println("当前线程状态2:"+Thread.currentThread().getState());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("当前线程状态:"+t1.getState());
        t1.start();
        //让主线程休眠1秒
        Thread.sleep(1000);
        System.out.println("当前线程状态3:"+t1.getState());
        //等子线程执行完
        t1.join();
        System.out.println("当前线程状态4:"+t1.getState());
    }
线程安全

1 线程安全问题:多线程执行环境下,程序执行结果和预期不符;

2 导致线程不安全的原因:

  1. 抢占式执行(狼多肉少

  2. 多个线程同时修改同一个变量

  static class Counter{
        private int number=0;
        private int MAX_COUNT=0;
        public Counter(int MAX_COUNT){
            this.MAX_COUNT=MAX_COUNT;
        }
        //++方法
        public void increment(){
            for (int i = 0; i < MAX_COUNT; i++) {
                number++;
            }
        }
        public void decrement(){
            for (int i = 0; i < MAX_COUNT; i++) {
                number--;

            }
        }
        public int getNumber(){
            return number;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter c=new Counter(100000);
        Thread th=new Thread(()->{
            c.increment();
        });
        Thread th1=new Thread(()->{
            c.decrement();
        });
        // c.increment();单线程时++——没问题
        // c.decrement();

        //启动多线程进行执行
        th.start();
        th1.start();
        //等待两个线程执行完
        th.join();
        th1.join();
        System.out.println("最终结果"+ c.getNumber());
    }
最终结果8100
static class Counter{
        private int number=0;
        private int MAX_COUNT=0;
        public Counter(int MAX_COUNT){
            this.MAX_COUNT=MAX_COUNT;
        }

        //++方法
        public int increment(){
            int tep=0;
            for (int i = 0; i < MAX_COUNT; i++) {
//                number++;
                tep++;
            }
            return tep;
        }
        public int decrement(){
            int tep=0;
            for (int i = 0; i < MAX_COUNT; i++) {
//                number--;
                tep--;
            }
            return tep;
        }
        public int getNumber(){
            return number;
        }
    }
    //全局变量接收线程执行结果
    static int n1=0;
    static int n2=0;
    public static void main(String[] args) throws InterruptedException {
        Counter c=new Counter(100000);
        Thread th=new Thread(()->{
          n1 = c.increment();
        });
//        th.start(); th.join();
        Thread th1=new Thread(()->{
            n2=c.decrement();
        });
//        c.increment();
//        c.decrement();
        th.start();
        th1.start();
        th.join();
        th1.join();
//        System.out.println("最终结果"+ c.getNumber());
        System.out.println("最终结果"+ (n1+n2));
    }
}
0
  1. 操作是非原子性操作(tep++ 1.查询当前tep的值 2.tep-1操作 3.刷新tep的最新值

请添加图片描述

  1. 内存可见性问题
package ThreadSafe;

import java.time.LocalDateTime;

/**
 * 内存可见性问题
 */
public class ThreadSafe1 {
    private static volatile boolean flag=true;

    public static void main(String[] args) {
        //创建子线程
        Thread t1=new Thread(()->{
            System.out.println("线程1:开始执行"+ LocalDateTime.now());
            while(flag){
            }
            System.out.println("线程1:执行结束"+LocalDateTime.now());
        });
        t1.start();
        Thread t2=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2修改flag=false"+LocalDateTime.now());
            flag=false;
        });
        t2.start();
    }
}
/**
 * 线程1:开始执行2022-04-03T10:40:54.170
 * 线程2修改flag=false2022-04-03T10:40:55.126
 */

请添加图片描述

java内存模型(jmm):⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果.

  1. 线程之间的共享变量存在 主内存 (Main Memory).

  2. 每⼀个线程都有⾃⼰的 “⼯作内存” (Working Memory) .(内存不可见)当线程要读取⼀个共享变量的时候, 会先把变量从主内存拷⻉到⼯作内存, 再从⼯作内存读取数据.

  3. 当线程要修改⼀个共享变量的时候, 也会先修改⼯作内存中的副本, 再同步回主内存.

  4. 指令重排序:编译或运行指令重排序(”jvm自作优化“

    编译器优化的本质是调整代码的执⾏顺序,在单线程下没问题,但在多线程下容易出现混乱,从⽽造成
    线程安全问题。
    请添加图片描述

解决线程安全问题的手段:

1 使用Volatile解决内存可见性问题和指令重排序问题
在这里插入图片描述

  1. 代码在写⼊ volatile 修饰的变量的时候:

    改变线程⼯作内存中volatile变量副本的值,将改变后的副本的值从⼯作内存刷新到主内存

  2. 代码在读取 volatile 修饰的变量的时候:

    从主内存中读取volatile变量的最新值到线程的⼯作内存中,从⼯作内存中读取volatile变量的副本

  3. 缺点:
    volatile 虽然可以解决内存可⻅性和指令重排序的问题,但是解决不了原⼦性问题,因此对于 ++ 和 –
    操作的线程⾮安全问题依然解决不了

2 使用锁是java中解决线程安全问题最主要的手段:

  1. 内存锁:synchronized

  2. 可重入锁:ReentrantLock

  3. synchronized基本用法:

    1 修饰静态方法:

public class TreadDemo_synchronized {
    private static int number=0;
    static class Counter{
        private static int MAX_COUNT=200000;
        //++方法
        public synchronized static void increment(){
            for (int i = 0; i < MAX_COUNT; i++) {
                number++;
            }
        }
        public synchronized static void decrement(){
            for (int i = 0; i < MAX_COUNT; i++) {
                number--;
            }
        }
        public static int getNumber(){
            return number;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread th=new Thread(()->{
            Counter.increment();
        });
        th.start();
        Thread th1=new Thread(()->{
            Counter.decrement();
        });
        th1.start();
        th.join();th1.join();
        System.out.println("最终结果"+ number);
    }
}

​ 2 修饰普通方法:

package ThreadSafe;

public class TreadDemo_synchronized2 {
    private static int number=0;
    static class Counter{
        private int MAX_COUNT=0;
        public Counter(int MAX_COUNT){
            this.MAX_COUNT=MAX_COUNT;
        }

        //++方法
        public synchronized void increment(){
            for (int i = 0; i < MAX_COUNT; i++) {
                number++;
            }
        }
        public synchronized  void decrement(){
            for (int i = 0; i < MAX_COUNT; i++) {
                number--;
            }
        }
        public  int getNumber(){
            return number;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter c=new Counter(100000);

        Thread th=new Thread(()->{
            c.increment();
        });
        th.start();
        Thread th1=new Thread(()->{
            c.decrement();
        });
        th1.start();
        th.join();th1.join();
        System.out.println("最终结果"+ number);
    }

}
  1. 3 修饰代码块:

    1. this 修饰类:synchronized类名.class)

    2. this 修饰类实例:synchronized(this)
      注意事项:对于同一个业务的多个线程加锁对象,一定要是同一个对象加同一把锁

  2. syntronized特性:

    1 互斥性(排他性

    synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也
    执⾏到同⼀个对象 synchronized 就会阻塞等待.

    1. 进⼊ synchronized 修饰的代码块, 相当于 加锁
    2. 退出 synchronized 修饰的代码块, 相当于 解锁

    2 刷新内存(内存可见性问题

    3 synchronized 的⼯作过程:

    1. 获得互斥锁
    2. 从主内存拷⻉变量的最新副本到⼯作的内存
    3. 执⾏代码
    4. 将更改后的共享变量的值刷新到主内存
    5. 释放互斥锁

    4 可重入:synchronized⽤的锁是存在Java对象头⾥的,里面有是否加锁的标志,以及拥有当前?的id

    ​ a. 查询当前对象头是否加锁 b. 判断隐藏对象头中的线程id是否等于当前线程的id

    public class ThreadDemo_synchronized5 {
        public static void main(String[] args) {
            synchronized( ThreadDemo_synchronized5.class){
                System.out.println("当前主线程获得了?");
    a b: (true) synchronized( ThreadDemo_synchronized5.class){
                    System.out.println("当前主线程再次获得?");
                }
            }
        }
    }
    

3 synchronized实现原理:保证任何时候只有一个线程能够执行指定区域的代码

  1. jvm层面依靠监视器Monitor实现(使用了一个对象的隐藏对象头 里面的 两个a,b属性实现的)

    1 jdk1.6之前使用比较少(默认重量级锁实现,所以性能较差)

    2 jdk1.6优化了

    1. 无锁(没有线程)->偏向锁(一个线程)->轻量级锁(少量线程)->重量级锁(较多线程 锁升级)
  2. 从操作系统的层面实现,基于操作系统的互斥锁(Mutax)

4 公平锁和非公平锁的区别:

  1. 公平锁一定要执行的步骤:1)上一个线程释放线程后执行唤醒操作 2)自旋后最前面阻塞休眠的线程从阻塞状态切换为运行状态;

  2. 非公平锁:来得早不如来得巧
    5 ReentrantLock

  3. Lock的实现步骤:

// 1.创建锁对象
Lock lock=new ReentrantLock();
// 2.加锁操作
lock.lock();
try{
    //业务代码(可能会非常复杂 -> 导致异常)
    System.out.println("你好! ReentrantLock");
}finally {
    // 3.释放锁
    lock.unlock();
}
  1. Lock注意事项:

    1 释放锁的unlock() 操作一定要放在finally里面,若没有放在这里可能会导致永久占用资源问题

    2 加锁操作lock() 一定要放在try之前,或者try首行

    1. 未加锁却执行了释放锁的操作
    2. 释放锁的错误信息会覆盖掉业务代码的报错信息,从而增加调试代码的复杂度
  2. 指定Lock类型:

    1. 非公平锁:默认会创建非公平锁,性能高
    2. 公平锁:new ReentrantLock(true)

2 synchronized VS Lock

  1. Lock 粒度可以更⼩
  2. Lock 更灵活。有更多方法比如:tryLock();
  3. 锁类型不同:Lock默认是非公平锁,但可以指定为公平锁;synchronized只能为公平锁
  4. 调用lock方法和synchronized方法线程等待状态不同;lock -》waiting synchronized-》blocked
  5. Lock 需要⼿动操作锁,⽽ Synchronized 是 JVM ⾃动操作的
  6. Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块

版权声明:本文为qq_54184658原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。