本文主要是自己学习多线程时,所做的笔记,参考的是b站狂神说Java多线程视频讲解。
目录如下:
文章目录
线程同步
由于同一进程的多个线程共享同一块存储空间,可能造成多个线程同时需要操作同一个资源,引发冲突问题,比如多个用户同时抢一张票,可能会造成大家同时抢到这张票的问题,而实际上该票应该只能被一个人抢到。
为了确保数据在方法中被访问时的正确性,可通过队列和锁来解决。
***队列:***多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。
***锁:***在访问时加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
队列+锁方式存在的问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引发性能问题;
如果一个优先级高的线程等待一个优先级第的线程释放锁会导致优先级倒置,引发性能问题;
一、三大线程不安全案例
1.买票案例
/**
* @author zyy
* @create 2021-10-29 19:49
*
* 不安全买票
*/
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 =10;
boolean flag=true; //外部停止方式
@Override
public void run() {
while(flag){
buy();
}
}
private void buy(){
//判断是否有票
if(this.ticketNums<=0){
this.flag=false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+this.ticketNums--);
}
}
2.不安全取钱
/**
* @author zyy
* @create 2021-10-29 20:03
*
* 不安全取钱
* 两个人去银行存钱,账户
*/
public class UnSafeBank {
public static void main(String[] args) {
Account account=new Account(100,"家庭基金");
Drawing mother=new Drawing(account,50,"妈妈");
Drawing father=new Drawing(account,100,"爸爸");
mother.start();
father.start();
}
}
//账户
class Account{
private int money; //余额
private String name; //账户名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public int getMoney() {
return money;
}
public String getName() {
return name;
}
public void setMoney(int money) {
this.money = money;
}
public void setName(String name) {
this.name = name;
}
}
class Drawing extends Thread{
private Account account; //账户
private int drawingMoney; //取走的金额
private int nowMoney; //总共取出的金额
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
public void run(){
if( (this.account.getMoney()-drawingMoney) <0){
System.out.println("钱不够,无法取出");
}
this.nowMoney+=drawingMoney;
//放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.setMoney(this.account.getMoney()-drawingMoney);
System.out.println(account.getName()+"余额为:"+account.getMoney());
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
3.线程不安全
import java.util.ArrayList;
import java.util.List;
/**
* @author zyy
* @create 2021-10-29 20:40
*
* 线程不安全的集合
* 原因:可能多个线程同时操作,然后覆盖,造成了集合大小比我们想象中的小
*/
public class UnSafeList implements Runnable{
private static List<String> list =new ArrayList<String>();
public static void main(String[] args) throws InterruptedException {
UnSafeList unSafeList =new UnSafeList();
for (int i = 0; i < 100000; i++) {
new Thread(unSafeList).start();
}
Thread.sleep(5000);
System.out.println(list.size());
}
@Override
public void run() {
list.add(Thread.currentThread().getName());
}
}
二、同步方法及同步块
1.同步方法 (synchronized方法)
public synchronized void method(int args){}
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
==缺陷:==若将一个大的方法申明为synchronized将会影响效率。
举例:在前面提到的不安全买票的buy方法上加上synchronized,锁住this
private synchronized void buy(){
//判断是否有票
if(this.ticketNums<=0){
this.flag=false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+this.ticketNums--);
}
但是在前面提到的不安全取钱的例子中,使用同步方法锁住取钱的run方法,却会发现不管用。
因为在该例子中,synchronized锁住的this是Drawing,而我们实际操作的是account。因此这种情况我们需要通过同步块解决。
2. 同步块 synchronized(obj) {}
obj称之为 同步监视器
obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class.
在上面提到的不安全取钱例子中,用同步块解决方法如下:
public class SafeBank {
public static void main(String[] args) {
Account account=new Account(100,"家庭基金");
Drawing mother=new Drawing(account,50,"妈妈");
Drawing father=new Drawing(account,100,"爸爸");
mother.start();
father.start();
}
}
//账户
class Account{
private int money; //余额
private String name; //账户名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
public int getMoney() {
return money;
}
public String getName() {
return name;
}
public void setMoney(int money) {
this.money = money;
}
public void setName(String name) {
this.name = name;
}
}
class Drawing extends Thread{
private Account account; //账户
private int drawingMoney; //取走的金额
private int nowMoney; //总共取出的金额
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
public void run(){
synchronized (account){
if( (this.account.getMoney()-drawingMoney) <0){
System.out.println("钱不够,无法取出");
return;
}
this.nowMoney+=drawingMoney;
//放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.setMoney(this.account.getMoney()-drawingMoney);
System.out.println(account.getName()+"余额为:"+account.getMoney());
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
}