线程不安全问题
日常生活中我们会经常碰到在不同的平台上买各种票的问题,例如在App、线下售票窗口等购买火车票、电影票。这里面就存在着线程安全的问题,因为当多个线程访问同一个资源时,会导致数据出错,例如甲和乙两人同时看中了一张票,甲先点击购买,此时线程直接转到乙去执行,乙点击购买,并锁定票,但甲还不知道此时就会发生数据错误。
为了避免发生这样的错误,我们通过两种方式来解决线程不安全的问题。
使用同步代码块
- 格式:
synchronized(任意对象){
线程可能出现不安全的代码
}
-
任意对象:就是我们的锁对象
-
执行: 一个线程拿到锁之后,会进入到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队。等着执行的线程执行完毕,出了同步代码块,相当于释放锁了,等待的线程才能抢到锁,才能进入到同步代码块中执行。
public class MyTicket implements Runnable{
int ticket = 100;//定义100张票
Object obj = new Object();//任意new一个对象
@Override
public void run() {
while(true){
synchronized (obj){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "张三");
Thread t2 = new Thread(myTicket, "李四");
t1.start();
t2.start();
}
}
没有再出现抢票的情况。
使用同步方法
同步方法有两类,一种是非静态的,一种是静态的。
非静态同步方法
格式:
修饰符 synchronized 返回值类型 方法名(参数){
方法体
return 结果
}
可以通过同步代码块的方式了解到默认锁是this
public class MyTicket implements Runnable{
int ticket = 100;//定义100张票
@Override
public void run() {
while(true){
method01();
method02()
}
}
public synchronized void method(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
public void method02(){
synchronized(this){
System.out.println(this+"..........");
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
静态同步方法
格式:
修饰符 static synchronized 返回值类型 方法名(参数){
方法体
return 结果
}
可以通过同步代码块的方式了解到默认锁是class对象
public class MyTicket implements Runnable{
static int ticket = 100;//定义100张票
@Override
public void run() {
method01();
method02();
}
}
public static synchronized void method01(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
public static void method02(){
synchronized(MyTicket.class){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
使用Lock锁
Lock是一个接口,实现类是ReentrantLock。
通过两个方法:Lock() 获取锁、unlock() 释放锁
public class MyTicket implements Runnable{
int ticket = 100;//定义100张票
Lock lock = new ReentrantLock(); //创建Lock对象
@Override
public void run() {
while(true){
try {
lock.lock(); //获取锁
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();//释放锁
}
}
}
}
public class Test {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "张三");
Thread t2 = new Thread(myTicket, "李四");
t1.start();
t2.start();
}
}
通过synchronized实现同步代码块或方法和通过Lock解决线程不安全的区别
synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放锁对象。而Lock是通过两个方法控制需要被同步的代码,更灵活。