欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多线程运行,保证线程安全的3种方式(Synchronized、Lock、AtomicInteger)

程序员文章站 2022-05-16 18:49:28
...

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可以使用读锁提高多线程读效率。
相关标签: 程序开发