😋线程不安全问题产生的原因:由于同一个进程的不同线程共享了同一块存储空间, 避免不了引起访问冲突的问题, 为了 保证数据的在访问时的正确性, 在访问时加入锁机制(synchronized), 当一个线程获得对象的🔒的时候, 此线程独占对象的资源, 其他线程必须等待此线程释放锁才能继续进行.
是用锁同时也会带来一些问题:
- 一个线程持有锁, 会导致其他所有需要这个锁的线程挂起.
- 会引发系统的调度延时, 或进行太多的上下文切换, 带来性能问题.
- 若一个优先级高的线程在等待一个优先级低的线程释放锁, 会引起性能倒置.
😋同步方法:synchronized关键字的两种用法:
- synchronized方法
public synchronized void mothod() {
// 此时synchronized是对this加🔒
// 如果把一个很多逻辑的方法声明为synchronized方法, 将会影响效率.
}
- synchronized块
synchronized(obj){
// obj: 需要🔒的对象, 也称之为同步监视器
}
- 只有当方法里面有修改对象数据的内容时才加锁(新增, 修改, 删除), 锁的太多,浪费资源.
💛线程不安全案例1: 几个人同时抢车票.
package com.smile.test.thread.unsafethread;
public class UnSafeBuyTickets {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets,"小明").start();
new Thread(buyTickets,"小红").start();
new Thread(buyTickets,"小黑").start();
}
}
class BuyTickets implements Runnable{
private int ticletsNum = 10;
private boolean flag = true;
// 线程不安全的做法
@Override
public void run() {
while(flag){
buy();
}
}
/*
线程安全的做法
@Override
public synchronized void run() {
while(flag){
buy();
}
}
*/
// 让线程停止
private void stop(){
this.flag = false;
}
// 买车票
private void buy(){
if (ticletsNum <= 0) {
System.out.println("卖完了");
stop();
return;
}
System.out.println(Thread.currentThread().getName()+"拿到"+ ticletsNum--);
}
}
/*
第一次:
小明拿到10
小明拿到9
小明拿到8
小明拿到7
小明拿到6
小明拿到5
小明拿到4
小明拿到3
小明拿到2
小明拿到1
卖完了
小红拿到0
卖完了
Process finished with exit code 0
第二次:
小明拿到10
小黑拿到8
小红拿到9
小黑拿到6
小明拿到7
小黑拿到4
小红拿到5
小黑拿到2
小明拿到3
卖完了
卖完了
小红拿到1
小明拿到0
Process finished with exit code 0
第三次:
小红拿到10
小红拿到9
小红拿到7
小红拿到6
小明拿到10
小红拿到5
小黑拿到8
小黑拿到2
小红拿到3
小明拿到4
卖完了
小明拿到0
卖完了
小黑拿到1
Process finished with exit code 0
*/
💛线程不安全案例2: 银行取钱.
package com.smile.test.thread.unsafethread;
public class GetBankMoney {
public static void main(String[] args) {
Account account = new Account("my Account", 100);
Bank me = new Bank(account, 50);
Bank my = new Bank(account, 100);
new Thread(me,"我自己").start();
new Thread(my,"我对象").start();
}
}
class Account{
// 账户
public String name;
// 余额
public int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
class Bank implements Runnable{
Account account; // 账号
int needMoney; // 取多少钱
public Bank(Account account, int needMoney) {
this.account = account;
this.needMoney = needMoney;
}
// 线程不安全的做法
@Override
public void run() {
if (account.money - needMoney < 0) {
System.out.println("余额不足");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - needMoney;
System.out.println(Thread.currentThread().getName() + "取出" + needMoney);
System.out.println(account.name + "余额:" + account.money);
}
/*
线程安全的做法
@Override
public void run() {
synchronized (account){
if (account.money - needMoney < 0) {
System.out.println("余额不足");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - needMoney;
System.out.println(Thread.currentThread().getName() + "取出" + needMoney);
System.out.println(account.name + "余额:" + account.money);
}
}
*/
}
/*
我自己取出50
我对象取出100
my Account余额:-50
my Account余额:-50
Process finished with exit code 0
*/
💛线程不安全案例3: ArrayList类---------输出为啥不是5000?
package com.smile.test.thread.unsafethread;
import java.util.ArrayList;
import java.util.List;
public class UnSafeArrayList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 5000; i++) {
new Thread(()->{
// 线程不安全
list.add(Thread.currentThread().getName());
/*
线程安全的做法
synchronized (list){
list.add(Thread.currentThread().getName());
}
*/
}).start();
}
System.out.println(list.size());
}
}
/*
这也是一种线程安全的做法
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 5000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
*/
/*
4579
Process finished with exit code 0
*/