线程不安全问题
取钱问题
- 账户余额是100,两个人都要取100,两个人在取之前都获取到余额是100,可以继续往下取,第一个人取完了,余额变为0,第二个人继续取,余额变成负数;或者两个人都获取到100,在取的时候也是100,使得两个人都取过之后最新的余额为0
public class BuyTicket {
public static void main(String[] args) {
People lily = new People("lily", 0);
People benjen = new People("benjen", 0);
Account wedding = new Account("wedding",10 );
new bank(lily,wedding).start();
new bank(benjen,wedding).start();
}
}
class Account{
String account;
int balance;
public Account(String account, int balance) {
this.account = account;
this.balance = balance;
}
}
class People{
String name;
int money;
public People(String name, int money) {
this.name = name;
this.money = money;
}
}
class bank extends Thread{
People people;
Account account;
public bank(People people, Account account) {
this.people = people;
this.account = account;
}
public void draw(int i){
if (account.balance<i){
System.out.println("balance is not enough");
return;
}
account.balance=account.balance-i;
people.money=i+people.money;
System.out.println(people.name+" now has "+people.money);
System.out.println(account.account+ "now has "+account.balance);
System.out.println("________________");
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
draw(1);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
买票问题
- 多个线程同时卖票,同时获取到票为10张,还可以卖,第一个线程卖出去了,第二个线程再卖一张就导致了多卖票
public class SellTickets {
public static void main(String[] args) {
Station station = new Station();
Thread t1 = new Thread(station);
Thread t2 = new Thread(station);
Thread t3 = new Thread(station);
t1.start();
t2.start();
t3.start();
}
}
class Station implements Runnable{
int num=0;
@Override
public void run() {
while (num<11){
System.out.println("sell "+(++num)+" tickets");
}
}
}
- 数组的不安全问题,这篇文章写得比较清楚:https://segmentfault.com/a/1190000023807751
- ArrayList的默认大小是10,当大小为9时,两个线程同时添加元素,第一个添加完后,数组已经满了,第二个再添加导致越界
- 两个数组同时在同一个位置添加元素,然后依次对size加1,导致一个元素被覆盖,后一个元素为null
- 连个数组同时在同一个位置添加元素,获取到相同的size并加1,使得size最终只加了一个1,使得数组大小小于预期
- CopyOnWriteArrayList不存在这个问题
public class ListUnSafe {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
strings.add(Thread.currentThread().getName());
try {
Thread.sleep(900);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(strings.size());
}
}
synchronized关键字
- 多个线程访问甚至修改同一个对象,这种现象叫并发;这种情况需要线程同步,即让同时访问的线程进入等待池等待资源被正在访问的线程释放。
- 可以加入synchronized关键字来对资源进行加锁,下面这篇笔记讲得比较清楚
https://www.cnblogs.com/mengdd/archive/2013/02/16/2913806.html
1 修饰一个普通方法,是对象锁,同一个对象中所有被synchronized修饰的普通方法使用的均是同一把对象锁,等同于synchronized(this){代码块};当使用thread来创建线程时,由于this不是同一个对象,方法锁会无效
2 修饰一个代码块,也是对象锁。括号内可以填任意对象。若两个代码块括号内的对象相同,则上的是同一把对象锁,无法被同一个对象访问;若括号内的对象不同,可以被同一个对象使用。(若括号内的对象是string类型,并且这个string对象会在访问过程中被重新赋值,说明这个对象一直在变,无法保证一直是同一把锁,这里涉及到string的内存问题,可以参考:https://isudox.com/2016/06/22/memory-model-of-string-in-java-language/)
3 修饰一个静态方法,使用的是类锁,所有的对象都无法同时访问上了类锁的方法。类锁和对象锁不冲突,即使是同一个对象,也可以在访问类锁方法的时候访问对象锁。
ReentrantLock
- 好处是可以主动解锁
- 但是只能对代码块加锁
生产者消费者问题
- 这篇文章写得比较清楚
- notify和signal都只会随机唤醒一个线程,可能唤醒的不是目标线程,被唤醒的目标线程又重新wait,使得所有线程wait,导致死锁。
- 可以通过notifyAll和signalAll简单粗暴地唤醒所有的线程。
- 更好的方法是建立针对性的condition 来针对性地唤醒线程
https://www.jianshu.com/p/d3214bd34a2d
死锁
两个线程都占用一部分资源,并且都在等待对方释放资源,产生死锁
public class DeadLock {
public static void main(String[] args) {
eat eat = new eat();
drink drink = new drink();
new table(0,eat,drink).start();
new table(1,eat,drink).start();
}
}
class eat{}
class drink{}
class table extends Thread{
int choice;
eat eat;
drink drink;
public table(int choice, com.citi.threadtest.eat eat, com.citi.threadtest.drink drink) {
this.choice = choice;
this.eat = eat;
this.drink = drink;
}
@Override
public void run() {
if (choice==0){
synchronized (eat){
System.out.println("0-eat");
synchronized (drink){
System.out.println("0-drink");
}
}
}else if (choice==1){
synchronized (drink){
System.out.println("1-drink");
synchronized (eat){
System.out.println("1-eat");
}
}
}
}
}