Java之线程详解(二)——线程安全概述、线程安全问题发生的原因分析、synchronized锁

一、线程安全概述

  1. 什么是线程安全问题?

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

public class Thread01  implements  Runnable{
    //定义一个全局变量
    private static Integer count = 100;

    @Override
    public void run() {
        while (count >1){
            cal();
        }
    }

    private void cal(){
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count--;
        System.out.println(Thread.currentThread().getName()+"--"+count);
    }

    public static void main(String[] args) {
        Thread01 thread01 = new Thread01();
        Thread thread1 = new Thread(thread01);
        Thread thread2 = new Thread(thread01);
        thread1.start();
        thread2.start();
    }
}

运行后打印结果出现了线程安全问题,如下图:
在这里插入图片描述

  1. 多线程如何解决线程安全问题(多线程如何实现同步)

当多个线程共享同一个全局变量时,将可能会发生线程安全的代码上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。
(1)使用synchronized锁;
(2)使用Lock锁 ,需要自己实现锁的升级过程,底层是基于aqs实现;
(3)使用Threadlocal,需要注意内存泄漏的问题。
(4)原子类CAS 非阻塞式。

二、线程安全问题发生的原因分析

1、线程安全问题举例

public class SecurityDemo extends Thread{
    private static int sum=0;

    @Override
    public void run(){
        for(int i=0; i<10000; i++){
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SecurityDemo t1 = new SecurityDemo();
        SecurityDemo t2 = new SecurityDemo();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

运行结果为:
在这里插入图片描述

上面的代码运行后打印的sum结果正常情况下是等于20000的,因为发生了线程安全问题,所以大概率运行时是小于20000。那为何会发生线程安全问题呢?请看接下来的分析:

2、线程安全问题发生的原因分析

(1)查看字节码
把SecurityDemo.java 文件编译成 SecurityDemo.class 文件,再用cmd访问该SecurityDemo.class 文件目录,在该目录下输入javap -p -v SecurityDemo.class,得到反编译文件。
部分截图:

截图中常量sum描述为描述错误,是全局共享变量sum在这里插入图片描述

(2)从上下文角度分析当两个线程同时运行对全局变量sum进行自增加操作,可能会发生上下文切换的情况,当CPU执行第一个线程运行到 13:iadd (自增=+1)时,如果发生上下文切换,则CPU切换到第二个线程运行,第二个线程将sum=0改成了sum=1,CPU再切换到第一个线程继续运行 14:putstatic (截图中赋值给全局变量sum),全局变量sum的结果还是sum = 1,此时就是发生了线程安全问题。

在这里插入图片描述

三、synchronized锁的基本用法

  1. 修饰代码块
    修饰代码块,指定加锁对象,对指定对象加锁,执行该代码块前要获得指定对象的锁。
    eg:
public class Thread01 implements Runnable{
    private static Integer count = 100;

    @Override
    public void run() {
        while (count > 1) {
            test();
        }
    }

    private void test() {
        synchronized (this) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            System.out.println(Thread.currentThread().getName() + "," + count);
        }
    }

    public static void main(String[] args) {
        Thread01 thread01 = new Thread01();
        Thread thread1 = new Thread(thread01);
        Thread thread2 = new Thread(thread01);
        thread1.start();
        thread2.start();
    }
}
  1. 修饰实例方法
    修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁,在实例方法上默认加上synchronized 默认使用this锁。
    eg:
public class Thread02 implements Runnable {
    private static Integer count = 100;

    @Override
    public void run() {
        while (count > 1) {
            test();
        }
    }

    private synchronized void test() {
        try {
            Thread.sleep(10);
        } catch (Exception e) {

        }
        count--;
        System.out.println(Thread.currentThread().getName() + "," + count);
    }
    public static void main(String[] args) {
        Thread02 thread02 = new Thread02();
        Thread thread1 = new Thread(thread02);
        Thread thread2 = new Thread(thread02);
        thread1.start();
        thread2.start();
    }
}
  1. 修饰静态方法
    修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁,默认使用当前类的 类名.class 锁。
    eg:
public class Thread03 implements Runnable{
    private static Integer count = 100;
    private static String lock = "lock";

    @Override
    public void run() {
        while (count > 1) {
            test();
        }
    }

    private static void test() {
        synchronized (Thread03.class) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            System.out.println(Thread.currentThread().getName() + "," + count);
        }
    }

    public static void main(String[] args) {
        Thread03 Thread031 = new Thread03();
        Thread03 Thread032 = new Thread03();
        Thread thread1 = new Thread(Thread031);
        Thread thread2 = new Thread(Thread032);
        thread1.start();
        thread2.start();
    }
}
  1. synchronized死锁问题
    在使用synchronized时需要注意 synchronized锁嵌套的问题,避免死锁的问题发生。
    eg:
public class ThreadDeadlock implements Runnable{
    private int count = 1;
    private String lock = "lock";

    @Override
    public void run() {
        while (true) {
            count++;
            if (count % 2 == 0) {
                // 线程1需要先获取到自定义对象的lock锁,执行test1方法需要再获取this锁;线程2需要先获取this锁,执行test2方法再获取lock锁
                synchronized (lock) {
                    test1();
                }
            } else {
                synchronized (this) {
                    test2();
                }
            }
        }
    }

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

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

    public static void main(String[] args) {
        ThreadDeadlock threadDeadlock = new ThreadDeadlock();
        Thread thread1 = new Thread(threadDeadlock);
        Thread thread2 = new Thread(threadDeadlock);
        thread1.start();
        thread2.start();
    }
}
  1. springmvc 接口中使用
    Spring MVC 的Controller默认是单例的,需要注意线程安全问题。
    eg:
@RestController
public class ThreadService {
    private int count = 0;

    @RequestMapping("/test")
    public synchronized String count() {
        try {
            System.out.println(">count<" + count++);
			Thread.sleep(3000);
        } catch (Exception e) {
			e.printStackTrace();
        }
        return "count";
    }
}
  1. 多线程线程之间通讯

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

注意:wait()、notify()和notifyAll()方法要与synchronized一起使用。

eg:

public class Thread02 {
    private  Object objectLock = new Object();

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

    public void print() throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (objectLock){
                    System.out.println(Thread.currentThread().getName()+"---1---");
                    try {
                        objectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"---2---");
                }
            }
        }).start();

        Thread.sleep(3000);
        synchronized (objectLock){
            objectLock.notify();
        }
    }
}
  1. 多线程通讯实现生产者与消费者
    eg:
public class Thread03 {

    class Student {
        public String name;
        public char sex;
        public boolean flag = true;//true:输入,false:输出
    }

    class InputThread extends Thread {
        private Student student;

        public InputThread(Student student) {
            this.student = student;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (student) {
                    if (!student.flag) {
                        try {
                            student.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (count == 0) {
                        student.name = "毛毛";
                        student.sex = '男';
                    } else {
                        student.name = "天天";
                        student.sex = '女';
                    }
                    count = (count + 1) % 2;
                    student.flag = false;
                    student.notify();
                }
            }
        }
    }

    class PrintThread extends Thread {
        private Student student;

        public PrintThread(Student student) {
            this.student = student;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (student) {
                    if (student.flag) {
                        try {
                            student.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("姓名:" + student.name + ",性别:" + student.sex);
                    student.flag = true;
                    student.notify();
                }
            }
        }
    }

    public void print() {
        Student student = new Student();
        InputThread inputThread = new InputThread(student);
        PrintThread printThread = new PrintThread(student);
        inputThread.start();
        printThread.start();
    }

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

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吟诗作对歌一曲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值