【JavaEE】多线程安全问题-死锁等相关问题

目录

一、线程安全

二、synchronized关键字--监视器锁

三、死锁问题

四、内存可见性


一、线程安全

(1)线程安全的概念:如果多线程环境下代码运⾏的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是 线程安全的。

(2)线程不安全的原因

        <1>线程抢占原理:线程调度是随机的,线程在调度的时候没有绝对优先的情况。

        <2>多个线程同时修改一个变量时,不是所有操作都是原子性。因为线程抢占的原因,所以自己修改的时候别人也能修改。⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能 是错误的。

        例如下面代码的预期和是20000,但由于多线程安全问题,最终结果随机分布在20000以下。

public class Demo9 {
    public static int count = 0;
    public static Object object = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1= new Thread(()->{
            for (int i = 0; i < 10000 ; i++) {
                count++;
            }
        });
        Thread t2= new Thread(()->{
            for (int i = 0; i < 10000 ; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

二、synchronized关键字--监视器锁

        为了解决补分多线程安全问题,我们需要用到synchronized关键字来解决,进⼊synchronized修饰的代码块,相当于加锁,退出synchronized修饰的代码块,相当于解锁。synchronized会起到互斥效果,某个线程执⾏到某个对象的synchronized中时,其他线程如果也执⾏ 到同⼀个对象synchronized就会阻塞等待.

        例如下方,在for循环里面使用synchronized关键字,即可避免这种多线程安全问题。

public class Demo7 {
    public static int count = 0;
    public static Object object = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1= new Thread(()->{
            for (int i = 0; i < 10000 ; i++) {
                synchronized(object){
                    count++;
                }
            }
        });
        Thread t2= new Thread(()->{
            for (int i = 0; i < 10000 ; i++) {
                synchronized(object){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

        synchronized多种用法:

                <1>修饰代码块,明确指定锁哪个对象。

public class SynchronizedDemo {
    private Object locker = new Object();
 
    public void method()  { 
         synchronized (locker) {
 
        }
    }
}

                <2>直接修饰普通⽅法,锁的SynchronizedDemo对象,也就是锁的this。

public class SynchronizedDemo {
    public synchronized void methond() {
    }
}

                <3>修饰静态⽅法,锁的是“类对象”,静态方法没有this。

public class SynchronizedDemo {
    public synchronized static void method() {
    }
}
三、死锁问题

(1)死锁的两个必要条件

        <1>锁是互斥的

        <2>锁是不可被抢占的

(2)可重入锁:

        对于可重入锁来说,发现加锁的线程已经被这个锁,锁上了,并不会真正的进行任何的上锁操作,也不会“阻塞操作”,而是直接放行,往下执行代码

(3)两个线程两把锁:

        <1>线程1拿到A锁之后,不释放A锁的前提下,去拿B锁,线程1拿到A锁的同时,线程2拿到B锁之后,不释放B锁的前提下,去拿A锁,就会出现“死锁”的情况。如下图

public class Demo6 {
    public static Object object1 = new Object();
    public static Object object2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            synchronized (object1){
                System.out.println("t1上object1锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object2){
                    System.out.println("t1上object2锁");
                }
                System.out.println("t1线程结束");
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (object2){
                System.out.println("t2上object2锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object1){
                    System.out.println("t2上object1锁");
                }
                System.out.println("t2线程结束");
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

    }
}

        

        <2>此时如果线程1拿到A锁之后,先释放A锁再去拿B锁,同时线程2拿到B锁之后,先释放B锁再去拿A锁,此时“死锁”就解决了。如下图

public class Demo10 {
    public static Object object1 = new Object();
    public static Object object2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            synchronized (object1){
                System.out.println("t1上object1锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (object2){
                System.out.println("t1上object2锁");
            }
            System.out.println("t1线程结束");
        });
        Thread t2 = new Thread(()->{
            synchronized (object2){
                System.out.println("t2上object2锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (object1){
                System.out.println("t2上object1锁");
            }
            System.out.println("t2线程结束");
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

    }
}

(4)N个线程M个锁:

        涉及多个N个线程M个锁的时候,不构成循环等待是非常重要的,我们需要给每一个锁上一个编号,在所有线程加锁的时候,必须现针对编号小的锁 加锁,后针对编号大的锁加锁。

        如下图,所有线程都是先加1锁再加2锁,没有出现“死锁”状态。

public class Demo11 {
    public static Object object1 = new Object();
    public static Object object2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            synchronized (object1){
                System.out.println("t1上object1锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (object2){
                System.out.println("t1上object2锁");
            }
            System.out.println("t1线程结束");
        });
        Thread t2 = new Thread(()->{
            synchronized (object1){
                System.out.println("t2上object1锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (object2){
                System.out.println("t2上object2锁");
            }
            System.out.println("t2线程结束");
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

    }
}

四、内存可见性

        多线程运行时,由于JVM自动优化之后,会出现只读取CPU缓存区里面的数据的情况。例如在两个线程一个读取一个修改有两种情况。

        下图是由于JVM自动优化之后而造成的死循环。

import java.awt.desktop.SystemEventListener;
import java.util.Scanner;
import static java.lang.System.in;

public class Demo12 {
    public static int n = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while(n == 0){

            }
            System.out.println("n的值修改成功");
        });
        Thread t2 = new Thread(()->{
            Scanner scanner  =  new Scanner(in);
            System.out.println("请输入一个整数");
            n = scanner.nextInt();
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

        volatile主要用于阻止JVM自动优化某个变量,volatile修饰之后的变量叫做"易变变量",volatile会强制读取内存数据,速度慢了,但是更精准了,在双线程一读一修改的时候就不会出现死循环现象。

import java.awt.desktop.SystemEventListener;
import java.util.Scanner;
import static java.lang.System.in;

public class Demo12 {
    public static volatile int n = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while(n == 0){

            }
            System.out.println("n的值修改成功");
        });
        Thread t2 = new Thread(()->{
            Scanner scanner  =  new Scanner(in);
            System.out.println("请输入一个整数");
            n = scanner.nextInt();
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值