什么是线程安全问题

多线程同时对同一个全局变量做写的操作,可能会受到其他
线程的干扰,就会发生线程安全性问题。
全局变量----java内存结构
什么是写操作------修改

当多个线程共享同一个全局变量,做写的操作时,可能会受到其他的线程干扰,发生线程
安全问题。

线程安全问题模拟

package com.gtf.xc;

public class ThreadCount implements Runnable {
    private int count = 100;

    /**
     * 保证线程一直在运行状态  死循环控制
     */
    @Override
    public void run() {

        while (true) {
            if (count > 1) {
                try {
                    //运行状态-休眠状态
                 Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + ":" + count);
            }
//            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }

    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        new Thread(threadCount, "线程1").start();
        new Thread(threadCount, "线程2").start();
    }
}

如何解决线程安全的问题

核心思想:上锁 分布式锁

在同一个jvm中,多个线程需要竞争锁的资源,最终只能够有一个线程
能够获取到锁,多个线程同时抢同一把锁,谁(线程)能够获取到锁,
谁就可以执行到该代码,如果没有获取锁成功 中间需要经历锁的升级过程
如果一致没有获取到锁则会一直阻塞等待。
如果线程A获取锁成功 但是线程A一直不释放锁
线程B一直获取不到锁,则会一直阻塞等待。

代码从那一块需要上锁?-----可能会发生线程安全性问题的代码需要上锁。
Juc并发编程 锁 重入锁 悲观锁 乐观锁 公平锁 非公平锁

核心思想:当多个线程共享同一个全局变量时,将可能会发生线程安全的代码
上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。

  1. 1.使用synchronized锁,JDK1.6开始 锁的升级过程 juc 18-25
  2. 2.使用Lock锁 ,需要自己实现锁的升级过程。底层是基于aqs实现
  3. 3.使用Threadlocal,需要注意内存泄漏的问题。
  4. 4.原子类 CAS 非阻塞式

synchronized锁的基本用法

Synchronized(对象锁)
{
 需要保证线程安全的代码
}
public class ThreadCount implements Runnable {
    private int count = 100;

    /**
     * 保证线程一直在运行状态  死循环控制
     */
    @Override
//    public synchronized void run() {
    public  void run() {
        while (true) {
            if (count > 1) {
                try {
                    //运行状态-休眠状态
                 Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (this) {
                    count--;
                }
                System.out.println(Thread.currentThread().getName() + ":" + count);
            }
//            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
synchronized锁中死锁问题

线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁
线程2 先获取this锁,          进入到b方法需要自定义对象的lock锁

线程1 线程2 是在同时执行
线程1 线程2
先获取到自定义对象的lock锁 先获取this锁
需要线程2已经持有的this锁 线程1已经持有自定义对象的lock锁

package com.gtf.xc;

public class ThreadCount implements Runnable {
    private int count = 1;
    private String lock = "lock";

    @Override
    public void run() {
        while (true) {
            count++;
            if (count % 2 == 0) {
                // 线程1需要获取 lock 在获取 a方法this锁
                // 线程2需要获取this 锁在 获取B方法lock锁
                synchronized (lock) {
                    a();
                }
            } else {
                synchronized (this) {
                    b();
                }
            }
        }
    }
    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + ",a方法...");
    }

     public void b() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ",b方法...");
        }
    }

    public static void main(String[] args) {
        ThreadCount threadCount11 = new ThreadCount();
        ThreadCount threadCount2 = new ThreadCount();
        new Thread(threadCount11, "线程1").start();
        new Thread(threadCount2, "线程2").start();
    }

}

synchronized 死锁诊断工具
D:\path\jdk\jdk8\bin\jconsole.exe
1652927984088

springmvc 接口中使用

需要注意:
Spring MVC Controller默认是单例的 需要注意线程安全问题
单例的原因有二:
1、为了性能。
2、不需要多例。
@Scope(value = “prototype”) 设置为多例子。

@RestController
@Slf4j
//@Scope(value = "prototype")
public class CountService {

    private int count = 0;

    @RequestMapping("/count")
    public synchronized String count() {
        try {
            log.info(">count<" + count++);
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
        } catch (Exception e) {

        }
        return "count";
    }
}

多线程线程之间通讯

等待/通知机制

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:
1.notify() :通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁
2.notifyAll():通知所有等待在该对象的线程
3.wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。

Exception in thread “Thread-0” java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.mayikt.thread.days02.Thread03.run(Thread03.java:16)
注意:wait,notify和notifyAll要与synchronized一起使用

wait/notify/notifyAll在Object类中

因为我们在使用synchronized锁 对象锁可以是任意对象,所以wait/notify/notifyAll需要放在Object类中。

wait/notify/简单的用法

public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        new Test01().print();
    }
    public void print() throws InterruptedException {
        synchronized (this) {
        /**
         * this.wait(); 释放资源 同时当前线程会阻塞
         */
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        this.wait();// 释放资源 同时当前线程会阻塞
                        Thread.sleep(1000);
                        this.notify();  //唤醒当前线程
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            ).start();
        }
    }
}

多线程通讯实现生产者与消费者

public class Thread04 {
    class Res {
        /**
         * 姓名
         */
        private String userName;
        /**
         * 性别
         */
        private char sex;
        /**
         * 标记
         */
        private boolean flag = false;
    }

    class InputThread extends Thread {
        private Res res;

        public InputThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    //flag = false  写入输入 flag = true 则不能写入数据 只能读取数据
                    try {
                        // 如果flag = true 则不能写入数据 只能读取数据 同时释放锁!
                        if (res.flag) {
                            res.wait();
                        }
                    } catch (Exception e) {

                    }
                    if (count == 0) {
                        this.res.userName = "余胜军";
                        this.res.sex = '男';
                    } else {
                        this.res.userName = "小薇";
                        this.res.sex = '女';
                    }
                    res.flag = true;
                    res.notify();
                }

                count = (count + 1) % 2;
            }
        }
    }

    class OutThread extends Thread {
        private Res res;

        public OutThread(Res res) {
            this.res = res;
        }


        @Override
        public void run() {
            while (true) {
                synchronized (res) {
                    try {
                        if (!res.flag) {
                            res.wait();
                        }
                    } catch (Exception e) {

                    }
                    System.out.println(res.userName + "," + res.sex);
                    res.flag = false;
                    res.notify();
                }
            }
        }
    }


    public static void main(String[] args) {
        new Thread04().print();
    }

    public void print() {
        Res res = new Res();
        InputThread inputThread = new InputThread(res);
        OutThread outThread = new OutThread(res);
        inputThread.start();
        outThread.start();
    }
}

/**

  • flag 默认值==false
  • flag false 输入线程 输入值 输出线程 先拿到锁 释放锁
  • flag true 输出线程 输出值
    */
    public boolean flag = false;

Join的底层原理如何实现

Join底层原理是基于wait封装的,唤醒的代码在jvm Hotspot 源码中 当
jvm在关闭线程之前会检测线阻塞在t1线程对象上的线程,然后执行notfyAll(),这样t2就被唤醒了。