多线程练习

做以下练习的基本步骤:

  1. 定义共享变量(若要创建多个对象使用静态);
  2. 写循环(while)
    1. 有时候不写while,比如说抢红包每条线程只能抢一次,就不用while循环
  3. 同步代码块
  4. 判断(已经到末尾)
  5. 再判断(没有到末尾)

重点1:要知道共享变量是定义在javaBean内,还是定义在main方法中(创建对象时传递过来)
重点2:要清楚while循环的结束条件是什么,


1.

image.png
SelTickets类:

public class SellTickets extends Thread{

    private static int tickets=1;


    @Override
    public void run() {
        //1循环
        while(true){
            //2同步代码块确保线程安全
            synchronized (SellTickets.class){
                //3判断
                if (tickets==1000){
                    break;
                }else {
                    //让当前线程睡30毫秒
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    System.out.println(Thread.currentThread().getName()+":正在卖第"+tickets+"张票");
                    tickets++;
                }
            }
        }
    }
}

main:

public class Test {
    public static void main(String[] args) {
        //创建两个线程
        SellTickets s1=new SellTickets();
        SellTickets s2=new SellTickets();
        //设名字
        s1.setName("窗口一");
        s2.setName("窗口二");
        //开始
        s1.start();
        s2.start();

    }
}

2.

image.png

public class MyRun implements Runnable{
    //第二种方式实现多线程,测试类中MyRunable只创建一次,所以不需要加static
    private  int gift=100;


    @Override
    public void run() {
        //1.循环
        while(true){
            //2.同步代码块确保线程安全
            synchronized (MyRun.class){
                //让当线程小睡一会
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //3.判断
                if (gift<10){
                    break;
                }else {
                    gift--;
                    System.out.println(Thread.currentThread().getName()+"正在送,还剩:"+gift+"份");
                }
            }
        }
    }
}

main

public class Test {
    public static void main(String[] args) {

        //线程任务
        MyRun m=new MyRun();
        //创建线程
        Thread t1=new Thread(m);
        Thread t2=new Thread(m);
        //设置名字
        t1.setName("小王");
        t2.setName("小李");
        //开始
        t1.start();
        t2.start();
    }
}

image.png

3

image.png
使用第三种方法

public class MyRunable implements Runnable {
//第二种方式实现多线程,测试类中MyRunable只创建一次,所以不需要加static
    private int number = 1;

    @Override
    public void run() {
        //循环
        while (true) {

            //同步代码块确保线程安全
            synchronized (MyCall.class) {
                //判断
                if (number <= 100) {
                    if (number % 2 == 1) {
                        System.out.println(Thread.currentThread().getName() + "打印数字" + number);
                    }
                    number++;
                } else {
                    break;
                }
            }
        }
    }
}

main

public class Test {
    public static void main(String[] args) {

        MyRunable mc=new MyRunable();

        //创建线程传入mc参数
        Thread t1=new Thread(mc);
        Thread t2=new Thread(mc);

        t1.setName("线程一");
        t2.setName("线程二");

        t1.start();
        t2.start();
    }
}

4.

.image.png
改进前:

public class Grab extends Thread {
    //使用第一种方法,可能创建多个线程使用静态修饰
    private static double money = 100;
    //红包个数
    static int count = 3;
    //最小的中奖金额
    static final double MIN = 0.01;

    @Override
    public void run() {
         //循环舍弃:抢红包一人只能抢一次
        //同步代码块保证线程安全
        synchronized (Grab.class) {
            if (count == 0) {
                //如果没红包了就说明没抢到
                System.out.println(getName() + "没抢到红包");
            } else {
                //还有红包
                //定义一个变量表示中奖的金额
                double prize = 0;
                if (count == 1) {
                    //表示是最后一个红包,抽取剩下的钱即可
                    prize = money;
                } else {
                    //第一个和第二个红包
                    Random r = new Random();
                    //要知道第一个红包最多抽到99.98元 (因为100要分成3个包)
                    //100-(count-1)*min,在这个范围内抽
                    //若第一个红包最多抽到99.98元,则第二个红包最多抽到0.01元 (因为100要分成3个包)
                    //100-99.98-(count-1)*min,在这个范围内抽
                    prize = r.nextDouble(money - (count - 1) * MIN);
                    //又因为抽红包有可能抽到比0.01还小的数,所以强制改为0.01
                    if (prize < MIN) {
                        prize = MIN;
                    }
                }
                System.out.println(getName() + "抢到了" + prize);
                //更改红包数
                count--;
                //更改money
                money -= prize;
            }
        }
    }
}

main

public class Test {
    public static void main(String[] args) {

        //创建5个线程
        Grab g1=new Grab();
        Grab g2=new Grab();
        Grab g3=new Grab();
        Grab g4=new Grab();
        Grab g5=new Grab();

        //给线程设置名字
        g1.setName("小A");
        g2.setName("小B");
        g3.setName("小C");
        g4.setName("小D");
        g5.setName("小E");

        //启动线程
        g1.start();
        g2.start();
        g3.start();
        g4.start();
        g5.start();
    }
}

image.png
发现有问题:小数位并没有保留两位,不贴近现实

改进后:

public class Grab2 extends Thread {
    //使用第一种方法,可能创建多个线程使用静态修饰
    private static BigDecimal money = BigDecimal.valueOf(100);
    //红包个数
    static int count = 3;
    //最小的中奖金额
    static final BigDecimal MIN = BigDecimal.valueOf(0.01);

    @Override
    public void run() {
        //循环舍弃:抢红包一人只能抢一次
        //同步代码块确保线程安全
        synchronized (Grab2.class) {
            if (count == 0) {
                //如果没红包了就说明没抢到
                System.out.println(getName() + "没抢到红包");
            } else {
                //还有红包
                //定义一个变量表示中奖的金额
                BigDecimal prize;
                if (count == 1) {
                    //表示是最后一个红包,抽取剩下的钱即可
                    prize = money;
                } else {
                    //第一个和第二个红包
                    Random r = new Random();
                    //要知道第一个红包最多抽到99.98元 (因为100要分成3个包)
                    //100-(count-1)*min,在这个范围内抽
                    //若第一个红包最多抽到99.98元,则第二个红包最多抽到0.01元 (因为100要分成3个包)
                    //100-99.98-(count-1)*min,在这个范围内抽
                    double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
                    prize = BigDecimal.valueOf(r.nextDouble(bounds));
                    //又因为抽红包有可能抽到比0.01还小的数,所以
                }
                //四舍五入
                prize = prize.setScale(2, RoundingMode.HALF_UP);
                System.out.println(getName() + "抢到了" + prize);
                //更改红包数
                count--;
                //更改money
                money=money.subtract(prize);
            }
        }
    }
}

main

public class Test {
    public static void main(String[] args) {


        Grab2 g1=new Grab2();
        Grab2 g2=new Grab2();
        Grab2 g3=new Grab2();
        Grab2 g4=new Grab2();
        Grab2 g5=new Grab2();

        //给线程设置名字
        g1.setName("小A");
        g2.setName("小B");
        g3.setName("小C");
        g4.setName("小D");
        g5.setName("小E");

        //启动线程
        g1.start();
        g2.start();
        g3.start();
        g4.start();
        g5.start();


    }
}

image.png

5.

image.png
这里的难点是这个奖池应该定义在哪, javabean的成员位置还是?

在Java中,如果你在main方法中创建了两个JavaBean对象,并且在构造这两个对象时都传递了同一个List集合的引用,那么这两个JavaBean对象将操作同一个集合。这意味着对任何一个对象通过该集合进行的增删改查操作,都会影响到另一个对象通过该集合看到的数据。
具体见:**基本数据类型和引用数据类型的特性

public class Prize implements Runnable {
        
    ArrayList<Integer> list;

    //构造,从创建对象时传递一个集合过来
    public Prize(ArrayList<Integer> list) {
        this.list = list;
    }


    @Override
    public void run() {
        //循环
        while (true) {
            //同步代码块,确保线程安全
            synchronized (Prize.class) {
                if (list.isEmpty()) {
                    break;
                }
                //随机获取集合元素----抽奖
                Collections.shuffle(list);
                Integer num = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "又产生了一个" + num + "元的大奖");
            }
            
            //让当前线程睡一会,给别的线程机会
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }


        }
    }
}

main

public class Test {
    public static void main(String[] args) {
        //创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);

        //传入参数
        Prize t = new Prize(list);
        //创建线程并传入t
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);

        //设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        //启动线程
        t1.start();
        t2.start();
    }
}

image.png

6.

image.png

public class Prize implements Runnable {

    ArrayList<Integer> list;

    //构造,从main方法传递一个集合过来
    public Prize(ArrayList<Integer> list) {
        this.list = list;
    }

    //用两个变量记录 两个抽奖箱产生中奖次数
    int count1 = 0;
    int count2 = 0;
    //用两个集合存储奖项
    ArrayList<Integer> list1 = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    

    @Override
    public void run() {
        //循环
        while (true) {
            //同步代码块,确保线程安全
            synchronized (Prize.class) {
                
                if (list.isEmpty()) {
                    //当奖箱全部获取完了就开始打印
                     if ("抽奖箱1".equals(Thread.currentThread().getName())) {
                         //求最大值
                        Integer max = Collections.max(list1);
                        System.out.println("抽奖箱1" + list1+":"+count1+"个奖项,最大值是"+max);
                    } else {
                         //求最大值
                        Integer max = Collections.max(list2);
                        System.out.println("抽奖箱2" + list2+":"+count2+"个奖项,最大值是"+max);
                    }
                    break;
                    
                } else {
                    //list大小不是0,继续抽奖
                    //随机获取元素
                    Collections.shuffle(list);
                    Integer prize = list.remove(0);
                    
                    if ("抽奖箱1".equals(Thread.currentThread().getName())) {
                        //添加到list1
                        list1.add(prize);
                        //个数++
                        count1++;
                    } else {
                        //添加到list2
                        list2.add(prize);
                        //个数++
                        count2++;
                    }
                }
                
            }
            //让当前线程睡一会,给别的线程机会
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            
        }
    
    }
}

main

public class Test {
    public static void main(String[] args) {
        //创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);

       
        Prize t = new Prize(list);
         //创建线程
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);

        //设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        //启动线程
        t1.start();
        t2.start();

    }
}

image.png

这种方法的弊端:当有非常多的抽奖箱时(线程),不可能一个个创建集合来存储中奖项
改进:在run方法内定义一个box集合,创建多个线程时会调用run方法来创建线程对应的集合

public class Prize implements Runnable {

    ArrayList<Integer> list;

    //构造,从main方法传递一个集合过来
    public Prize(ArrayList<Integer> list) {
        this.list = list;
    }

    //用两个变量记录 两个抽奖箱产生中奖次数
    int count1 = 0;
    int count2 = 0;
    

    @Override
    public void run() {
        //在run方法内创建一个集合,创建多少个线程就会创建多少个相应的集合
        ArrayList<Integer>box=new ArrayList<>();
        //循环
        while (true) {
            //同步代码块,确保线程安全
            synchronized (Prize.class) {
                if (list.isEmpty()) {
                    //当奖箱全部获取完了就开始打印
                    if ("抽奖箱1".equals(Thread.currentThread().getName())) {
                        int max = Collections.max(box);
                        System.out.println("抽奖箱1" + box + ":" + count1 + "个奖项,最大值是" + max);
                    } else {
                        int max = Collections.max(box);
                        System.out.println("抽奖箱2" + box + ":" + count2 + "个奖项,最大值是" + max);
                    }
                    break;

                } else {
                        //继续抽奖(打乱获取第一个)
                    Collections.shuffle(list);
                    Integer prize = list.remove(0);

                    if ("抽奖箱1".equals(Thread.currentThread().getName())) {
                        //放入box
                        box.add(prize);
                        //奖项数
                        count1++;
                    } else {
                        //放入box
                        box.add(prize);
                        //奖项数
                        count2++;
                    }

                }
            }
            //让当前线程睡一会,给别的线程机会
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

image.png

为什么可以这样改进?
内存图:
image.png

7.

需求:最后一句
image.png
思路:
首先代码一定是写在while循环之后的,因为抽奖已经结束
首先要知道抽奖箱1和2的奖项的最大值,我们可以使用第三种方式创建多线程,因为call方法有返回值

public class Prize implements Callable<Integer> {
    //定义一个变量
    ArrayList<Integer> list;

    //构造,从main方法传递一个集合过来
    public Prize(ArrayList<Integer> list) {
        this.list = list;
    }

    //用两个变量记录 两个抽奖箱产生中奖次数
    int count1 = 0;
    int count2 = 0;


    @Override
    public Integer call() throws Exception {
        在run方法内创建一个集合,创建多少个线程就会创建多少个相应的集合
        //线程1 //线程2
        ArrayList<Integer> box = new ArrayList<>();
        //循环
        while (true) {
            //同步代码块,确保线程安全
            synchronized (Prize.class) {
                if (list.isEmpty()) {
                    //当奖箱全部获取完了就开始打印
                    if ("抽奖箱1".equals(Thread.currentThread().getName())) {
                        int max = Collections.max(box);
                        System.out.println("抽奖箱1" + box + ":" + count1 + "个奖项,最大值是" + max);
                    } else {
                        int max = Collections.max(box);
                        System.out.println("抽奖箱2" + box + ":" + count2 + "个奖项,最大值是" + max);
                    }
                    break;

                } else {
                    //继续抽奖
                    Collections.shuffle(list);
                    Integer prize = list.remove(0);

                    if ("抽奖箱1".equals(Thread.currentThread().getName())) {
                        box.add(prize);
                        count1++;
                    } else {
                        box.add(prize);
                        count2++;
                    }
                }
            }
            //让当前线程睡一会,给别的线程机会
            Thread.sleep(10);
        }
        ----while循环外
        //抽奖结束,循环结束,返回box中的最大奖项
        //要知道线程一或二的box中的奖项有可能没有,要特别考虑
        if (box.isEmpty()){
            return null;
        }else {
            return Collections.max(box);
        }
        
    }
}

main、

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);

        //创建多线程要运行的参数对象
        Prize t = new Prize(list);


        //*这里创建俩个FutureTask对象,因为你不知道的是javabean返回的最大值是线程1还是2的,所以创建两个分别管理线程  
        //线程一的任务管理器,接收返回值
        FutureTask<Integer> ft1=new FutureTask<>(t);
        //线程二的任务管理器,接受返回值
        FutureTask<Integer> ft2=new FutureTask<>(t);

        
        //创建线程1、2
        Thread t1=new Thread(ft1);
        Thread t2=new Thread(ft2);

        //设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        //启动线程
        t1.start();
        t2.start();

        //调用get方法获取返回值
        //线程1的最大值
        Integer i1 = ft1.get();
        //线程2的最大值
        Integer i2 = ft2.get();
        //打印
        System.out.println(i1+" "+i2);

        if (i1>i2){
            System.out.println("在此次抽奖过程中,抽奖箱1产生了最大奖项,该奖项金额为800");
        }else {
            System.out.println("在此次抽奖过程中,抽奖箱2产生了最大奖项,该奖项金额为800");
        }


    }
}

image.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成果、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值