线程同步
介绍
多个线程操作同一个资源
线程同步
现实生活中我们会遇到“同—个资源,多个人都想使用”的问题,比如食堂排队打饭,每个人都想吃饭,最天然的解決办法就是:排队,一个个来。
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个线程同时访问此对象时,线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
队列和锁
线程同步:
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题。为了保证在方法中访问的数据是正确,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,就独占这个对象,属于独占资源,其他线程必须等待这个线程使用后释放对象的排它锁后才可访问这个对象。存在以下问题:
-
一个线程持有锁会导致其他所有需要此锁的线程挂起;
-
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
-
如果一个优先级高的线程等待个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
不安全的线程案例
不安全买票
package com.gcbeen.thread;
// 不安全买票
public class TestUnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "张三").start();
new Thread(buyTicket, "李四").start();
new Thread(buyTicket, "王五").start();
}
}
class BuyTicket implements Runnable {
// 票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
// 买票
while (flag) {
try {
buy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 买票
private void buy() {
// 判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
// 延迟
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName()
+ "拿到" + ticketNums--);
}
}
// 李四拿到10
// 张三拿到9
// 王五拿到8
// 王五拿到7
// 李四拿到7
// 张三拿到7
// 李四拿到6
// 王五拿到5
// 张三拿到4
// 李四拿到3
// 张三拿到1
// 王五拿到2
// 李四拿到0
不安全取钱
package com.gcbeen.thread;
public class TestUnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "养老基金");
Drawing drawing = new Drawing(account, 60, "夸克");
Drawing same = new Drawing(account, 60, "same");
drawing.start();
same.start();
}
}
class Account {
int money; // 余额
String cardName; // 卡名
public Account(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
class Drawing extends Thread {
Account account; // 账户
int drawingMoney; // 取余额
int nowMoney; // 个人手里的钱
public Drawing(Account account, int drawingMoney, String name) {
// super(name) = 父类构造方法(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(100); // 放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内金额 = 余额-个人手里的钱
account.money = account.money - drawingMoney;
// 个人手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.cardName + "余额为:" + account.money);
// this.getName()==Thread.currentThread().getName()
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
// 养老基金余额为:40
// 养老基金余额为:40
// same手里的钱:60
// 夸克手里的钱:60
线程不安全的集合
package com.gcbeen.thread;
import java.util.ArrayList;
import java.util.List;
// 线程不安全的集合
public class TestUnsafeList2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(Thread.currentThread().getName());
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(Thread.currentThread().getName());
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(Thread.currentThread().getName());
}
}).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
// 可能输出情况
// 情况一
// 29148
// 情况二
// 数组越界异常
// Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 14053
// at java.util.ArrayList.add(ArrayList.java:465)
// at com.gcbeen.thread.TestUnsafeList2.lambda$main$0(TestUnsafeList2.java:14)
// at java.lang.Thread.run(Thread.java:748)
// 29294
标签:account,同步,Thread,int,线程,new,public
From: https://www.cnblogs.com/gcbeen/p/16743653.html