java多线程运行,保证线程安全的3种方式(Synchronized、Lock、AtomicInteger)
Jason.Meng / 2021-08-20 / 面试题 / 阅读量 82

1、前言:

在多线程运行,尤其是多线程共同操作一个变量时,会造成数据异常,线程不安全。下面这段代码就是个线程不安全的例子。

public class Run {
    private static int count = 1;
    private Runnable runnable = () -> {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " 运行 " + count++);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
 
    public static void main(String[] args) throws InterruptedException {
        Run r = new Run();
        new Thread(r.runnable, "线程A").start();
        new Thread(r.runnable, "线程B").start();
        Thread.sleep(6000);
        System.out.println("count值为:" + count);
    }
}

AB两个线程同时操作变量count自增,最后count值应该是21,但是在AB两个线程同一时间操作变量时,就会导致两个线程只自增一次,所以要保证同一时间只有一个线程操作自增的代码。

2、用Synchronized(锁)

public class Run {
    private static int count = 1;
    private Runnable runnable = () -> {
        for (int i = 0; i < 10; i++) {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + " 运行 " + count++);
            }
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
 
    public static void main(String[] args) throws InterruptedException {
        Run r = new Run();
        new Thread(r.runnable, "线程A").start();
        new Thread(r.runnable, "线程B").start();
        Thread.sleep(6000);
        System.out.println("count值为:" + count);
    }
}

Synchronized很简单,把需要线程安全的代码,放到Synchronized的花括号里面就行。

3、用Lock(锁)

public class Run {
    private static int count = 1;
    private Lock lock = new ReentrantLock();
    private Runnable runnable = () -> {
        for (int i = 0; i < 10; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 运行 " + count++);
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();   //释放锁
            }
        }
    };
 
    public static void main(String[] args) throws InterruptedException {
        Run r = new Run();
        new Thread(r.runnable, "线程A").start();
        new Thread(r.runnable, "线程B").start();
        Thread.sleep(6000);
        System.out.println("count值为:" + count);
    }
}

注意Lock lock一定要声明为成员变量,如果Lock lock是方法内的局部变量,每个线程执行该方法时都会保存一个副本,那么每个线程执行到lock.lock()处获取的是不同的锁,所以就不会对临界资源形成同步互斥访问。

3、用AtomicInteger(原子类)

public class Run {
    private static AtomicInteger count = new AtomicInteger(1);
    private Runnable runnable = () -> {
        for (int i = 0; i < 10; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + " 运行 " + count.getAndIncrement());
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
 
    public static void main(String[] args) throws InterruptedException {
        Run r = new Run();
        new Thread(r.runnable, "线程A").start();
        new Thread(r.runnable, "线程B").start();
        Thread.sleep(6000);
        System.out.println("count值为:" + count);
    }
}

用原子类的话,这里需要把变量声明为原子变量,自增时也要调用原子类的方法。用原子类是自带锁,保证线程安全同步,不用再附加其他锁。

4、Synchronized、Lock两种锁的区别

Synchronized是关键字,内置语言实现,Lock是接口。 Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。 Lock是可以中断锁,Synchronized是非中断锁,必须等待线程执行完成释放锁。 Lock可以使用读锁提高多线程读效率。

支付宝捐赠
请使用支付宝扫一扫进行捐赠
微信捐赠
请使用微信扫一扫进行赞赏