前言
什么是线程同步
线程同步: 即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态
常用锁
- 同步锁,使用关键字 synchronized
- 可重入锁 ReentrantLock
使用场景
java 编程中,多个线程,同时对一个对象进行操作,会引发出数据不一致的问题,也就是 高并发 场景,要确保数据的安全
例子说明
多用户购票场景
不安全例子
package com.wei.sync;
/**
* @author: wei
* @date 2022/5/4 16:59
* 说明:
**/
public class UnSafeBuyTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "用户1").start();
new Thread(ticket, "用户2").start();
new Thread(ticket, "用户3").start();
}
}
class Ticket implements Runnable {
int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
try {
while (flag) {
Thread.sleep(200);
buy();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到票No:" + ticketNums);
ticketNums--;
}
}
安全的改进方案
方式1:在buy()方法上,增加关键字 synchronized,表示 锁 Ticket2 对象的 buy() 方法
package com.wei.sync;
/**
* @author: wei
* @date 2022/5/4 16:59
* 说明:
**/
public class SafeBuyTicket {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(ticket, "用户1").start();
new Thread(ticket, "用户2").start();
new Thread(ticket, "用户3").start();
}
}
class Ticket2 implements Runnable {
int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
try {
while (flag) {
Thread.sleep(1000);
buy();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 方式1:使用 synchronized 锁 Ticket2 对象的 buy 方法
public synchronized void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到票No:" + ticketNums);
ticketNums--;
}
}
方式2:使用关键字 synchronized,括住buy()方法块代码,表示 锁 Ticket2 对象的 buy() 方法
package com.wei.sync;
/**
* @author: wei
* @date 2022/5/4 16:59
* 说明:
**/
public class SafeBuyTicket {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(ticket, "用户1").start();
new Thread(ticket, "用户2").start();
new Thread(ticket, "用户3").start();
}
}
class Ticket2 implements Runnable {
int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
try {
while (flag) {
Thread.sleep(200);
// this 表示当前对象,此处锁的是 Ticket2 这个对象的buy方法
synchronized (this) {
buy();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到票No:" + ticketNums);
ticketNums--;
}
}
方式3:使用可重入锁ReentrantLock,显式加锁与解锁代码块
package com.wei.sync;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: wei
* @date 2022/5/4 16:59
* 说明:
**/
public class SafeBuyTicket {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(ticket, "用户1").start();
new Thread(ticket, "用户2").start();
new Thread(ticket, "用户3").start();
}
}
class Ticket2 implements Runnable {
int ticketNums = 10;
boolean flag = true;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
while (flag) {
Thread.sleep(200);
// 方式3:使用可重入锁,显式加锁与解锁代码块
try {
lock.lock();
buy();
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到票No:" + ticketNums);
ticketNums--;
}
}
两个人同时取一个账户的钱
不安全例子
package com.wei.sync.account;
import java.util.concurrent.TimeUnit;
/**
* @author: wei
* @date 2022/5/4 17:37
* 说明:
**/
public class UnsafeDraw {
public static void main(String[] args) {
Account account = new Account(100);
Bank bank = new Bank(account, 50, "小王");
Bank bank2 = new Bank(account, 80, "小明");
bank.start();
bank2.start();
}
}
class Bank extends Thread {
Account account;
int draw;
public Bank(Account account, int draw, String name) {
super(name);
this.account = account;
this.draw = draw;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "取钱 " + draw + ",初始金额=" + account.initMoney);
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//synchronized (account) {
System.out.println(Thread.currentThread().getName() + "取钱 " + draw + ",再次确认初始金额=" + account.initMoney);
if (account.initMoney < this.draw) {
System.out.println(Thread.currentThread().getName() + "取钱 " + draw + ",余额不足");
return;
}
int nowMoney = account.initMoney - this.draw;
account.initMoney = nowMoney;
System.out.println(Thread.currentThread().getName() + "取完钱,剩余金额=" + nowMoney);
//}
}
}
class Account implements Runnable {
int initMoney;
public Account(int initMoney) {
this.initMoney = initMoney;
}
@Override
public void run() {
}
}
安全的改进方案
方式1:使用synchronized (account) 对账户进行加锁,不能写 synchronized (this) this 是对银行对象加锁,没用
package com.wei.sync.account;
import java.util.concurrent.TimeUnit;
/**
* @author: wei
* @date 2022/5/4 17:37
* 说明:
**/
public class UnsafeDraw {
public static void main(String[] args) {
Account account = new Account(100);
Bank bank = new Bank(account, 50, "小王");
Bank bank2 = new Bank(account, 80, "小明");
bank.start();
bank2.start();
}
}
class Bank extends Thread {
Account account;
int draw;
public Bank(Account account, int draw, String name) {
super(name);
this.account = account;
this.draw = draw;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "取钱 " + draw + ",初始金额=" + account.initMoney);
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 对账户 account 进行加锁,不能写 synchronized (this) this 是对银行对象加锁,没用
synchronized (account) {
System.out.println(Thread.currentThread().getName() + "取钱 " + draw + ",再次确认初始金额=" + account.initMoney);
if (account.initMoney < this.draw) {
System.out.println(Thread.currentThread().getName() + "取钱 " + draw + ",余额不足");
return;
}
int nowMoney = account.initMoney - this.draw;
account.initMoney = nowMoney;
System.out.println(Thread.currentThread().getName() + "取完钱,剩余金额=" + nowMoney);
}
}
}
class Account implements Runnable {
int initMoney;
public Account(int initMoney) {
this.initMoney = initMoney;
}
@Override
public void run() {
}
}
多线程往集合存放数据
不安全例子
资料上说是不安全的,但是~~~,我电脑没重现出来
package com.wei.sync;
import java.util.ArrayList;
import java.util.List;
/**
* @author: wei
* @date 2022/5/4 18:20
* 说明:
**/
public class array {
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
// 往list写数据
list.add(UUID.randomUUID().toString());
// println方法调用toString()方法,会遍历list对象,然后抛异常:java.util.ConcurrentModificationException
System.out.println(list);
}).start();
}
}
}
安全的改进方案
方式1:使用 CopyOnWriteArrayList
package com.wei.java.basic.sync.array;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author: wei
* @date 2022/5/4 18:20
* 说明:ArrayList 不安全
* 多个线程操作同一个对象,对数据又读又写,就会有并发修改数据异常
**/
public class array {
public static void main(String[] args) throws InterruptedException {
// ArrayList 不安全
//List list = new ArrayList<>();
// 推荐使用CopyOnWriteArrayList
/**
* 原理:
* 线程读取该list的数据时,会先 copy 一份副本到工作内存,然后读副本中的数据
* 其他线程往 list 中写入数据,不影响当前副本的数据,所以不会出现并发读写异常
*/
List list = new CopyOnWriteArrayList();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}).start();
}
}
}