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

1 线程状态

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());
    }
2 线程安全

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());
    }
结果应该是0
最终结果8100

换种方式 使用全局变量接受结果后 结果为0

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 线程从主内存读出最新的值Value
    2 放到线程的虚拟机栈的栈帧中的操作栈先压入,再弹出,供CPU计算+1,计算完再压入再弹出
    3 线程新值只写回到内存中

请添加图片描述

  1. 内存可见性问题

是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够立即看到发生的状态变化。 由于线程之间的交互都发生在主内存中,但对于变量的修改又发生在自己的工作内存中,经常会造成读写共享变量的错误,我们也叫可见性错误。

使用 volatile解决内存可见性问题(也可解决指令重排序问题

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
 */

JMM Java Memory Model (JAVA 内存模型 ):
是描述线程之间如何通过内存来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。其⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果.

Java内存模型的抽象示意图如下
请添加图片描述

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

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

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

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

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

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

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

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

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

4 使用锁是java中解决线程安全问题最主要的手段:
  1. 内存锁:synchronized

  2. 可重入锁:ReentrantLock

5 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);
    }

}

3 修饰代码块:

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

  2. 修饰类实例:synchronized(this)

public class ThreadSynchronized3 {
    private static int number = 0;
 
    static class Counter {
        private static final int count = 100000;
 
        // 自定义锁对象
        final Object myLock = new Object();
 
        // ++方法
        public void increase() {
            for (int i = 0; i < count; i++) {
                synchronized (myLock) {
                    number++;
                }
            }
        }
 
        // --方法
        public void desc() {
            for (int i = 0; i < count; i++) {
                synchronized (myLock) {
                    number--;
                }
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread thread1 = new Thread(counter::increase);
        thread1.start();
        Thread thread2 = new Thread(counter::desc);
        thread2.start();
 
        thread1.join();
        thread2.join();
        System.out.println("最终结果:" + number);
    }
}

注意事项:对于同一个业务的多个线程加锁对象,一定要是同一个对象加同一把锁

public class ThreadSynchronized {
    private static final int count = 100000;
    static int num = 0;
 
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        Object obj2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (obj) {
                for (int i = 0; i < count; i++) {
                    num++;
                }
            }
        }, "线程1");
        t1.start();
        Thread t2 = new Thread(() -> {
            synchronized (obj2) {
                for (int i = 0; i < count; i++) {
                    num--;
                }
            }
        }, "线程2");
        t2.start();
        t1.join();
        t2.join();
        System.out.println("最终执⾏结果:" + num);
    }
}
6 syntronized特性:

1 互斥性(排他性

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

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

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

3 synchronized 的⼯作过程:

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

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

  1. 查询当前对象头是否加锁
  2. 判断隐藏对象头中的线程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("当前主线程再次获得🔒");
             }
         }
     }
 }
7 synchronized实现原理:

保证任何时候只有一个线程能够执行指定区域的代码

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

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

  2. jdk1.6优化了
    无锁(没有线程)——>偏向锁(一个线程)——>轻量级锁(少量线程)——>重量级锁(较多线程 锁升级)

2 从操作系统的层面实现,基于操作系统的互斥锁(Mutax)

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

1 公平锁一定要执行的步骤:

  1. 上一个线程释放线程后执行唤醒操作
  2. 自旋后 最前面阻塞休眠的线程 从阻塞状态切换为运行状态

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

9 ReentrantLock

1 Lock的实现步骤:

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

2 Lock注意事项:

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

  2. 加锁操作lock() 一定要放在try之前,或者try首行
    1. 未加锁却执行了释放锁的操作
    2. 释放锁的错误信息会覆盖掉业务代码的报错信息,从而增加调试代码的复杂度

  3. 指定Lock类型:

    1. 非公平锁:默认会创建非公平锁,性能高 new ReentrantLock()
    2. 公平锁:new ReentrantLock(true)
10 synchronized VS Lock
  1. Lock 粒度可以更⼩ ,更灵活。有更多方法比如:tryLock();
  2. 锁类型不同:Lock默认是非公平锁,但可以指定为公平锁;synchronized只能为公平锁
  3. 调用lock方法和synchronized方法线程等待状态不同;lock -》waiting synchronized-》blocked
  4. Lock 需要⼿动操作锁,⽽ Synchronized 是 JVM ⾃动操作的
  5. Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值