多线程运行,保证线程安全的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可以使用读锁提高多线程读效率。