java并发编程需要注意的问题

并发编程是java知识体系中必需要掌握的一块内容,相比Java中其他知识点的学习门槛较高,要想在项目中熟练应用,需要注意以下问题。

原子性

指程序在执行一系列操作时,这些操作要么全部执行成功,要么全部执行失败;

举例:

public class AtomicTest {

    private Integer val = 0;

    public Integer getValue() {
        return val;
    }

    public void increment() {
        ++val;
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicTest atomicTest = new AtomicTest();

        new Thread(() -> {
            atomicTest.increment();
        }, "threat-1").start();

        new Thread(() -> {
            atomicTest.increment();
        }, "threat-2").start();

        Thread.sleep(1000);
        System.out.println("val=" + atomicTest.getValue());
    }

}

上面代码中输入的val有可能是1,也有可能是2,因为在java中++val不是原子操作,等价于:

temp = val;

temp = temp+1;

val = temp;

要想实现原子性可以使用JUC包下的AtomicInteger等原子类.

死锁

指两个及以上线程在争夺多个资源而相互等待;

举例:

public class DeadLockTest {

    static Object resource1 = new Object();
    static Object resource2 = new Object();

    public static void main(String[] args) {
        //线程1
        new Thread(()->{
            try {
                synchronized (resource1) {
                    System.out.println(Thread.currentThread().getName() + "get resource1");
                    Thread.sleep(1000);
                    synchronized (resource2) {
                        System.out.println(Thread.currentThread().getName() + "get resource2");
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        },"threat-1").start();


        //线程2
        new Thread(()->{
            try {
                synchronized (resource2) {
                    System.out.println(Thread.currentThread().getName() + "get resource2");
                    Thread.sleep(1000);
                    synchronized (resource1) {
                        System.out.println(Thread.currentThread().getName() + "get resource1");
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        },"threat-2").start();

    }
}

如上图代码,有两个资源resource1, resource2, 线程1先获取resource1,sleep 1秒后再尝试获取resource2, 线程2则反之。 则线程1和线程2将死锁。

资源竞争

当一个共享资源没有加任何同步措施,被多个线程同时读写的时候,这多个线程处理数据竞争状态。

举例

    private static int a = 0;

    public static void main(String[] args) {
        new Thread(()->{
            a = 1; //线程1修改a的值
        },"threat-1").start();

        new Thread(()->{
            if(a==0) {
                System.out.println("a="+a); //线程2读取a的值输出
            }
        },"threat-2").start();
    }

如上代码所示,线程1修改a的值为1,线程2判断如果a=0,则输出a的值;那么线程2输出的a的值究竟是多少呢?答案是不确定的,有可能是0,也有可能是1,也有可能不输出;

有3种可能:

1)当线程1先执行完,然后线程2才开始执行,这个时候不会输出, 将线程2 sleep1秒可以模拟这种情况:

    private static int a = 0;

    public static void main(String[] args) throws Exception{
        new Thread(()->{
            a = 1; //线程1修改a的值
        },"threat-1").start();

        new Thread(()->{
            Thread.sleep(1000); //线程2先sleep 1秒

            if(a==0) {
                System.out.println("a="+a); //线程2读取a的值输出
            }
        },"threat-2").start();
    }

2)当线程2先执行,然后线程1才开始执行,输出为0;将线程1 sleep 1秒可以模拟这种情况:

    private static int a = 0;

    public static void main(String[] args) throws Exception{
        new Thread(()->{
        Thread.sleep(1000); //线程1先sleep 1秒

            a = 1; //线程1修改a的值
        },"threat-1").start();

        new Thread(()->{
            if(a==0) {
                System.out.println("a="+a); //线程2读取a的值输出
            }
        },"threat-2").start();
    }

3)线程1先sleep 1秒,让线程2执行 a==0的判断,然后线程2 sleep 2秒,线程1接着执行 a=1的写操作, 之后线程2输出a. 则输出为1;

    private static int a = 0;

    public static void main(String[] args) throws Exception{
        new Thread(()->{
        Thread.sleep(1000); //线程1先sleep 1秒, 让线程2执行a==0的判断

            a = 1; //线程1修改a的值
        },"threat-1").start();

        new Thread(()->{
            if(a==0) {

                Thread.sleep(2000); //线程2先sleep 2秒, 让线程1修改a的值
                System.out.println("a="+a); //线程2读取a的值输出
            }
        },"threat-2").start();
    }

内存可见性

每个线程都有自己独立的工作内存,里面保存了该线程使用到的变量的副本(主内存中该变量的一份拷贝),如下图所示。

线程对共享变量的所有操作都必须在自己的工作内存中进行,不能从主内存中读写;而且不同线程之间无法直接访问其它线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。线程1对共享变量的修改要想被线程2及时看到,必须要经过如下两个步骤:
1. 把工作内存1中更新过的共享变量刷新到主内存中;
2. 把内存中最新的共享变量的值更新到工作内存2中
Java语言层面支持的可见性实现方式有两种:
1. synchronized
2. volatile

指令重排序

java内存模型允许编译器和处理器对指令进行重排序以提高程序运行性能。指令重排序在单线程下不会有问题,当多线程运行时需要特别注意。

先看示例1:

int a=1; //1
int b=2; //2
int c=a+b; //3

指令重排序后行1和行2的执行顺序是不确定的,因为它们不相关,但是行3一定会在行1和行2先执行之后再执行, 因为行3依赖于a、b;

示例2:



boolean flag =false;
int a=0;

//线程1
a=1;
flag = true;

//线程2
if(flag) {
    System.out.println(a);
}

指令重排序后有可能先执行了线程1的flag=true;再执行a=1; 然后线程2判断了flag=true后输出a,则有可能输出还是0;

如果想避免指令重排序引起的这种问题,可以在flag前面加上修改词valitale, 这样a=1就会先执行;

猜你喜欢

转载自blog.csdn.net/kangbin825/article/details/106892264