并发:同一个对象被多个线程同时操作(抢票)
每个对象都有一把锁,sleep不会释放锁
线程同步:多个线程操作同一个资源
线程同步的条件:队列和锁(安全性高,但会损失性能)
例子:队列(排队上厕所)和锁(为保证安全,一次只能有一个上厕所)
同一进程的多个线程共享同一块存储空间,使用方便,但会带来访问冲突,锁机制(synchronized)可保证数据在方法中被访问时的正确性。当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁,此时会存在问题
synchronized关键字两种用法
不安全的买票案例
//线程不安全
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station, "学生").start();
new Thread(station, "老师").start();
new Thread(station, "公务员").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 100;
boolean flag = true; //停止方式
//买票
@Override
public void run() {
//买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketNums <= 0){
flag = false;
return;
}
//模拟延时
Thread.sleep(1000);
//买票
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
同一张票会被多人抢到,说明线程是不安全的
添加同步锁后
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(ticketNums <= 0){
flag = false;
return;
}
同一张票只能被一个人拿到
不安全的取款案例
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100,"教育基金");
Drawing me = new Drawing(account, 80, "我");
Drawing you = new Drawing(account, 70, "你");
me.start();
you.start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account; //账户
int drawingMoney; //取出
int nowMoney; //现有
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断是否有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足");
return;
}
//放大问题发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 取出
account.money = account.money - drawingMoney;
//现有
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额:" + account.money);
System.out.println(this.getName() + "现有:" + nowMoney);
}
}
银行账户出现负数,当两个线程同时看到账户余额足够取的时候,都将自己需要的数字拿到了自己的内存里,此时线程是不安全的
为账户account添加同步锁后
synchronized (account) {
//判断是否有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足");
return;
}
//放大问题发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 取出
account.money = account.money - drawingMoney;
//现有
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额:" + account.money);
System.out.println(this.getName() + "现有:" + nowMoney);
}
线程不安全的集合
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
10000个线程没有被全部添加,在添加过程中,会出现集合的某个位置同时添加两个线程的情况,后来的线程被覆盖掉了,少的这8个其实是被覆盖掉了
synchronized (list) {
list.add(Thread.currentThread().getName());
}
扩展
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}