欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《JAVA300集》多线程_并发基础案例-day21

程序员文章站 2022-07-10 17:06:24
...

目录

一、并发与线程同步

二、锁机制

抢票案例

取钱案例

抢票案例优化版本

影院购票案例

抢票案例-面向对象思想

 三、并发容器


一、并发与线程同步

并发:同一个对象的多个线程同时操作。

并发有可能引发线程不安全的问题。

线程同步是一种等待机制,用于解决线程不安全问题。

当多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候就可以通过线程同步方法。多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用

二、锁机制

为了保证数据在多线程访问中的正确性,在访问时加入了锁机制(synchronized),当一个线程获得对象的排它锁,就可以独占资源,其他线程必须等待,这个线程使用完成后释放锁。

使用锁机制有以下问题:(锁会导致性能下降

《JAVA300集》多线程_并发基础案例-day21

 使用方法: synchronized方法和synchronized块。

每个对象对应一把锁

难点:如何合适的用锁,使得锁不多也不少。同时兼顾安全和性能。

抢票案例

package Thread;

/*
*线程安全:在并发时保证数据的正确性,效率尽可能高
* synchronized
* 同步方法
*  */
public class SynTest01 {
    public static void main(String[] args) {
        SafeWeb12306 web = new SafeWeb12306();
        new Thread(web,"小黄").start();
        new Thread(web,"小王").start();
        new Thread(web,"小蒋").start();
    }
}

class SafeWeb12306 implements Runnable{
    private int ticketNums = 10;
    private boolean flag = true;

    @Override
    public void run() {
        while(flag){
            // 模拟抢票延时
            try{
                Thread.sleep(200);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            test();
        }
    }

    // 锁方法里面跟对象相关的资源,使得只能同时被1个线程访问
    public synchronized void test(){
        if(ticketNums<=0){
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"抢到了"+ticketNums--+"号票");
    }
}

运行结果:《JAVA300集》多线程_并发基础案例-day21

取钱案例

// 账户
public class Account {
    int money;
    String name;
    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }
}

错误示例-

//模拟取钱(错误示例)
public class SynTest02 {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚礼金");
        SafeDrawing you = new SafeDrawing(account,80,"bad的你");
        SafeDrawing wife = new SafeDrawing(account,90,"happy的小蒋");
        you.start();
        wife.start();
    }
}

class SafeDrawing extends Thread{
    Account account;  //取钱的账户
    int drawingMoney; //取出的钱
    int packetTotal; //口袋里的钱

    public SafeDrawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        test();
    }

    //锁这个方法没有用,这个方法类似于取款机,应该去锁账户,因为是账户的对象
    public synchronized void test(){
        if(account.money-drawingMoney<0){
            return;
        }
        try{
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        account.money -= drawingMoney;
        packetTotal += drawingMoney;
        System.out.println(this.getName()+"--账户 "+account.money);
        System.out.println(this.getName()+"--口袋的钱 "+packetTotal);
    }
}

锁方法,是锁方法的对象,方法的默认对象是this

 正确示例-

同步块- synchronized(obj){},obj称为同步监视器。  obj推荐使用共享资源。

package Thread;

//模拟取钱(正确示例)
public class SynBlockTest01 {
    public static void main(String[] args) {
        //账户
        Account account = new Account(1000,"结婚礼金");
        SynDrawing you = new SynDrawing(account,80,"bad的你");
        SynDrawing wife = new SynDrawing(account,90,"happy的小蒋");
        you.start();
        wife.start();
    }
}

class SynDrawing extends Thread{
    Account account;  //取钱的账户
    int drawingMoney; //取出的钱
    int packetTotal; //口袋里的钱

    public SynDrawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        test();
    }

    //目标锁定账户
    public void test(){
        //这个if可以提升代码执行效率
        if(account.money<=0){
            return;
        }
        synchronized (account) {
            if (account.money - drawingMoney < 0) {
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= drawingMoney;
            packetTotal += drawingMoney;
            System.out.println(this.getName() + "--账户 " + account.money);
            System.out.println(this.getName() + "--口袋的钱 " + packetTotal);
        }
    }
}

运行结果:《JAVA300集》多线程_并发基础案例-day21

同步块可以精准地锁定目标对象。(这个例子中是account)

抢票案例优化版本

/*
 *线程安全:在并发时保证数据的正确性,效率尽可能高
 * synchronized
 * 同步块
 *  */
public class SynBlockTest03 {
    public static void main(String[] args) {
        SynWeb12306 web = new SynWeb12306();
        new Thread(web,"小黄").start();
        new Thread(web,"小王").start();
        new Thread(web,"小蒋").start();
    }
}

class SynWeb12306 implements Runnable{
    private int ticketNums = 10;
    private boolean flag = true;

    @Override
    public void run() {
        while(flag){
            // 模拟抢票延时
            try{
                Thread.sleep(200);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            //test();
            //falseTest();
            goodTest();
        }
    }

    public void test() {
        //synchronized只能锁一个资源
        synchronized (this) {
            if (ticketNums <= 0) {
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "抢到了" + ticketNums-- + "号票");
        }
    }

    //漏锁了一个对象,导致线程依旧不安全
    public void falseTest() {
        synchronized ((Integer)ticketNums) {
            if (ticketNums <= 0) {
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "抢到了" + ticketNums-- + "号票");
        }
    }

    //线程安全:尽可能锁定合理的范围,这样可以提升性能
    public void goodTest() {
        //双重检测,主要考虑的是临界值的问题
        if (ticketNums <= 0) { //考虑的是没有票的情况
            flag = false;
            return;
        }
        synchronized (this) {
            if (ticketNums <= 0) { //考虑的是最后1张票
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "抢到了" + ticketNums-- + "号票");
        }
    }
}

影院购票案例

package Thread;

import java.util.ArrayList;
import java.util.List;

public class HappyCinema2 {
    public static void main(String[] args) {
        //影院可用的位置
        List<Integer> avaliable = new ArrayList<Integer>();
        avaliable.add(1);
        avaliable.add(2);
        avaliable.add(3);
        avaliable.add(4);

        //顾客需要的位置
        List<Integer> seats1 = new ArrayList<Integer>();
        seats1.add(1);
        seats1.add(2);
        List<Integer> seats2 = new ArrayList<Integer>();
        seats2.add(3);
        seats2.add(5);

        HCinema c = new HCinema(avaliable,"万达影院");
        new Thread(new HappyCustomer(c,seats1),"小张和小机智").start();
        new Thread(new HappyCustomer(c,seats2),"小赵").start();
        System.out.println("欢迎光临!");
    }
}

//顾客
class HappyCustomer implements Runnable{
    HCinema hinema;
    List<Integer> seats;

    public HappyCustomer(HCinema hinema, List<Integer> seats) {
        this.hinema = hinema;
        this.seats = seats;
    }

    @Override
    public void run() {
        synchronized (hinema){
            System.out.println(Thread.currentThread().getName()+" -想要购买的位置为:"+seats);
            boolean flag = hinema.bookTickets(seats);
            if(flag){
                System.out.println("出票成功 "+ Thread.currentThread().getName()+" -购买的位置为:"+seats+"\n");
            }else{
                System.out.println("出票失败 "+ Thread.currentThread().getName()+" -购买失败,没位置了");
            }
        }
    }
}

//影院
class HCinema{
    List<Integer> avaliable; //可用的位置
    String name;

    public HCinema(List<Integer> avaliable, String name) {
        this.avaliable = avaliable;
        this.name = name;
    }

    //购票
    public boolean bookTickets(List<Integer> seats){
        System.out.println(this.name+"当前可用位置为: " + avaliable);
        //两个容器的比较
        List<Integer> copy = new ArrayList<Integer>();
        copy.addAll(avaliable);
        //相减
        copy.removeAll(seats);
        //判断是否成功
        if(avaliable.size()-copy.size()!=seats.size()){
            return false;
        }else{
            avaliable = copy;
            return true;
        }
    }
}

运行结果:《JAVA300集》多线程_并发基础案例-day21

抢票案例-面向对象思想

public class Happy12306 {
    public static void main(String[] args) {
        Web123066 w = new Web123066(2,"铁路局");
        new Passenger(w,"小张",2).start();
        new Passenger(w,"小机智",2).start();

    }
}

//乘客 (继承,作为Thread的子代理)
class Passenger extends Thread{
    int seats;

    public Passenger(Runnable target, String name, int seats) {
        super(target,name);
        this.seats = seats;
    }
}

//火车票网
class Web123066 implements Runnable{
    int  avaliable; //可用的位置
    String name;

    public Web123066(int avaliable, String name) {
        this.avaliable = avaliable;
        this.name = name;
    }

    @Override
    public void run() {
        Passenger p = (Passenger)Thread.currentThread(); //把当前线程强转为当前子类
        boolean flag = this.bookTickets(p.seats);
        if(flag){
            System.out.println("出票成功 "+ Thread.currentThread().getName()+" -购买位置数量为:"+p.seats);
        }else{
            System.out.println("出票失败 "+ Thread.currentThread().getName()+" -购买失败,没位置了");
        }
    }

    //购票
    public synchronized boolean bookTickets(int seats){
        //购票延迟
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.name+"当前可用位置为: " + avaliable);
        if(seats>avaliable){
            return false;
        }

        avaliable -=seats;
        return true;
    }
}

运行结果:《JAVA300集》多线程_并发基础案例-day21 

Passenger p = (Passenger)Thread.currentThread(); //把当前线程强转为当前子类

 三、并发容器

java中有自带的并发容器,名为util.concurrent

import java.util.concurrent.*

import java.util.concurrent.CopyOnWriteArrayList;
/*
 * 并发安全操作容器
 * */
public class SynContainer {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for(int i=0;i<1000;i++){
            new Thread(()-> list.add(Thread.currentThread().getName())).start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}