17.java基础--多线程编程1



命名要求:
	1. 类名,接口名,枚举名,注解名使用大驼峰
	2. 变量名,方法名,包名均使用小驼峰
	3. 常量名全大写,多个单词下划线分割
	4. 名字要见名知意,如果不知道对应的英文,可以使用拼音代替。不可使用无意义字符
  
代码规范:
	格式要良好,使用IDEA格式化缩进(快捷键:Ctrl+Alt+L)
  
答题规范:
	1. 每道题完整代码请贴入对应题目中的代码区。
    2. 如果有运行结果的,请把代码运行结果放到文档中

【简答题】

  1. 请简述多线程中影响线程安全的原因是什么?

    当多个线程同时读写共享资源的时候,如果出现了数据的脏乱的现象,就表明出现了线程安全问题。
    出现线程安全问题的本质:多个线程对共享数据进行了写操作,造成多个线程数据不同步,造成数据错乱。
    
  2. 请描述死锁的现象及产生的原因,需要如何避免?

    在多线程程序中,使用了多把锁(嵌套锁),造成线程之间相互等待.程序不往下走了。
    
    尽量量不使用嵌套的同步代码块
    在嵌套同步代码块中,避免使用不同的锁对象
    
  3. 请简要描述线程的六种状态

    1. 新建状态    //创建Thread对象
    2. 可运行      //调用start()
    3. 终止状态    //run方法执行结束
    4. 锁阻塞     //未获取到锁
    5. 无限等待   //wait()
    6. 计时等待   //sleep(毫秒)、wait(毫秒)
    

【synchronized】

题目1

请编写程序,不使用任何同步技术,模拟三个窗口同时卖100张票的情况,运行并打印结果,观察到错误的数据,并解释出现错误的原因。

public class Demo01 {
    public static void main(String[] args) {
        //卖票任务
        TicketTask ticketTask = new TicketTask();//定义一个任务,让三个线程完成

        Thread t1 = new Thread(ticketTask,"【窗口1】");
        Thread t2 = new Thread(ticketTask,"【窗口2】");
        Thread t3 = new Thread(ticketTask,"【窗口3】");

        //执行
        t1.start();
        t2.start();
        t3.start();

    }
}

class TicketTask implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {

        while (true) {
            if (tickets > 0) {

                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();

                System.out.println(name + ":" + tickets);
                tickets--;//卖完一张减1

            } else {
                break;
            }
        }

    }
}

题目2

请使用“同步代码块”改写题目一的程序,保证运行结果的正确。

使用同步代码块解决代码:

public class Demo01 {
    public static void main(String[] args) {
        //卖票任务
        TicketTask ticketTask = new TicketTask();//定义一个任务,让三个线程完成

        Thread t1 = new Thread(ticketTask, "【窗口1】");
        Thread t2 = new Thread(ticketTask, "【窗口2】");
        Thread t3 = new Thread(ticketTask, "【窗口3】");
        //执行
        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketTask implements Runnable {
    private int tickets = 100;

    Object lockObj = new Object();  

    @Override
    public void run() {
        
        while (true) {
            //synchronized (lockObj) {//保证锁对象要多个线程使用相同的对象
            synchronized ("lock") {//保证锁对象要多个线程使用相同的对象
                if (tickets > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String name = Thread.currentThread().getName();

                    System.out.println(name + ":" + tickets);
                    tickets--;//卖完一张减1

                } else {
                    break;
                }
            }
        }
    }
}

题目3

​ 请使用“同步方法”改写题目一的程序,保证运行结果的正确。

使用同步方法代码如下:


class TicketTask implements Runnable {
    private static int tickets = 100;

    @Override
    public void run() {//this是调用run方法的对象,this 就是创建的任务对象ticketTask

        while (true) {
            //sellTicket();//非静态同步方法
            sellTicket2();//静态同步方法

            if (tickets <= 0) {
                break;
            }
        }
    }

    /**
     * 同步方法
     */
    private synchronized void sellTicket() { // 锁对象是:this
        if (tickets > 0) {

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();

            System.out.println(name + ":" + tickets);
            tickets--;//卖完一张减1

        }
    }


    /**
     * 同步方法
     */
    private static synchronized void sellTicket2() { //类名.class  字节码对象
        if (tickets > 0) {

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();

            System.out.println(name + ":" + tickets);
            tickets--;//卖完一张减1

        }
    }
}


public class Demo01 {
    public static void main(String[] args) {
        //卖票任务
        TicketTask ticketTask = new TicketTask();//定义一个任务,让三个线程完成

        Thread t1 = new Thread(ticketTask, "【窗口1】");
        Thread t2 = new Thread(ticketTask, "【窗口2】");
        Thread t3 = new Thread(ticketTask, "【窗口3】");
        //执行
        t1.start();// 当线程开启,JVM就会开启线程调用run方法
        t2.start();// 当线程开启,JVM就会开启线程调用run方法
        t3.start();// 当线程开启,JVM就会开启线程调用run方法
    }
}

题目4

​ 请使用“Lock锁”改写题目一的程序,保证运行结果的正确

使用如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo01 {
    public static void main(String[] args) {
        //卖票任务
        TicketTask ticketTask = new TicketTask();//定义一个任务,让三个线程完成

        Thread t1 = new Thread(ticketTask, "【窗口1】");
        Thread t2 = new Thread(ticketTask, "【窗口2】");
        Thread t3 = new Thread(ticketTask, "【窗口3】");

        //执行
        t1.start();
        t2.start();
        t3.start();

    }
}

class TicketTask implements Runnable {
    private int tickets = 100;
    
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {

        while (true) {
           
            lock.lock();
            try {
                if (tickets > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String name = Thread.currentThread().getName();

                    System.out.println(name + ":" + tickets);
                    tickets--;//卖完一张减1

                } else {
                    break;
                }
            }finally {
                lock.unlock(); //保证该代码必须一定执行
            }
        }
    }
}

题目5

  1. 某公司组织年会,会议入场时有两个入口 ,在入场时每位员工都能获取一张双色球彩票,假设公司有100个员工, 利用多线程模拟年会入场过程,并分别统计每个入口入场的人数,以及每个员工拿到的彩票的号码。

    线程运行后打印 格式如下:

    编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是:[17, 24, 29, 30, 31, 32, 07] 
    编号为: 1 的员工 从前门 入场! 拿到的双色球彩票号码是:[06, 11, 14, 22, 29, 32, 15] 
    //..... 
    从后门入场的员工总共: 13 位员工 
    从前门入场的员工总共: 87 位员工
    
  2. 题目中用到的产生双色球的工具类已经写好,可以直接使用:

    import java.util.Arrays;
    import java.util.Random;
    //工具类
    public class DoubleColorBallUtil{
        // 产生双色球的代码
        public static String create() {
            String[] red ={"01","02","03","04","05","06","07","08","09","10", "11","12","13","14","15","16","17","18","19","20","21","22","23", "24","25","26","27","28","29","30","31","32","33"};
            //创建蓝球
            String[] blue = "01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16".split(",");
            boolean[] used = new boolean[red.length];
            Random r = new Random();
            
            String[] all = new String[7];
            for(int i = 0;i<6;i++) {
                int idx;
                do {
                    idx = r.nextInt(red.length);//0‐32
                } while (used[idx]);//如果使用了继续找下一个
                used[idx] = true;//标记使用了
                all[i] = red[idx];//取出一个未使用的红球
            }
            all[all.length-1] = blue[r.nextInt(blue.length)];
            Arrays.sort(all);
            return Arrays.toString(all);
        }
    }
    
    

训练提示

1、自定义类实现Runnable接口,定义变量int number = 100;模拟100个人。

2、定义测试类,启动两个线程分别模拟:前门、后门。

答案:

public class LuckDraw implements Runnable {
   // 员工人数
   private int number = 100;

   public void run() {
       //编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是:[17, 24, 29, 30, 31, 32, 07] 
       
       
      // 获得线程的名字
      String name = Thread.currentThread().getName();
       
      // 定义变量统计人数
      int count = 0;
      
      // 开始进场抽奖,
      while (true) {
              
         synchronized (this) {    
            // 首先判断number,大于0才能抽奖
            if (number > 0) {
               // 使用工具类生成一个彩票号码给这个员工
               String dc = DoubleColorBallUtil.create();
                
                
               // 输出抽中的彩票号
               System.out.println("编号为: " + number + " 的员工 从"+name+"入场! 拿到的双色球彩票号码是: " + dc);
               // 进入一个员工,少一个员工
               number--;
                
               // 计数加一
               count ++;
                
               // 休眠
               try {
                  Thread.sleep(10);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
                
            } else {
               // 抽奖完毕,大于前后门入场的员工人数
               // 从后门入场的员工总共: 13 位员工
               // 从前门入场的员工总共: 87 位员工
               System.out.println("从" + name + "入场的员工总共: " + count + " 位员工");
               break;
            }
         }
      }
   }
}

public class Demo01 {
    public static void main(String[] args) {
        // 创建抽奖任务,实现了Runnable接口
        LuckDraw ld = new LuckDraw();
        
        // 在主线程中开启两个线程,表示前门和后门
        // 传入实现Runnable接口的实现类对象
        Thread t1 = new Thread( ld , "前门");
        Thread t2 = new Thread( ld , "后门");
        // 开启两个线程
        t1.start();
        t2.start();
    }
}

打印结果:

编号为: 100的员工 从前门入场! 拿到的双色球彩票号码是: [03, 05, 07, 09, 13, 20, 22]
编号为: 99 的员工 从后门入场! 拿到的双色球彩票号码是: [04, 08, 16, 18, 19, 23, 25]
编号为: 98 的员工 从后门入场! 拿到的双色球彩票号码是: [02, 07, 09, 14, 29, 30, 31]
编号为: 97 的员工 从后门入场! 拿到的双色球彩票号码是: [01, 03, 06, 09, 16, 23, 30]
   ...........
编号为: 5 的员工 从后门入场! 拿到的双色球彩票号码是: [05, 08, 12, 19, 20, 29, 32]
编号为: 4 的员工 从后门入场! 拿到的双色球彩票号码是: [05, 08, 15, 24, 26, 27, 31]
编号为: 3 的员工 从后门入场! 拿到的双色球彩票号码是: [08, 08, 13, 20, 27, 28, 30]
编号为: 2 的员工 从后门入场! 拿到的双色球彩票号码是: [01, 04, 07, 10, 21, 28, 30]
编号为: 1 的员工 从后门入场! 拿到的双色球彩票号码是: [10, 14, 15, 20, 22, 32, 33]
从后门入场的员工总共: 76位员工
从前门入场的员工总共: 24位员工

【线程通讯】

题目6

请将课堂代码包子铺卖包子案例及等待唤醒案例,重新实现下

包子铺卖包子:

定义一个集合,包子铺线程完成生产包子,包子添加到集合中;吃货线程完成购买包子,包子从集合中移除。
1. 当包子没有时,吃货线程等待.
2. 包子铺线程生产包子,并通知吃货线程(解除吃货的等待状态)

【生产者】

包子师傅的线程

public class BaoZiPu extends Thread{
    private List<String> list ;
    public BaoZiPu(String name,ArrayList<String> list){
        super(name);
        this.list = list;
    }
    @Override
    public void run() {
        	int i = 0; 
            while(true){
                    //list作为锁对象
                    synchronized (list){
                        if(list.size()>0){
                            //存元素的线程进入到等待状态
                            try {
                                list.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //如果线程没进入到等待状态 说明集合中没有元素
                        //向集合中添加元素
                        list.add("包子"+i++);
                        System.out.println(list);
                        //集合中已经有元素了 唤醒获取元素的线程
                        list.notify();
                    }
                }
            }
    }
}

【消费者】

吃货线程

public class ChiHuo extends Thread {

    private List<String> list ;//成员变量
    public ChiHuo(String name,ArrayList<String> list){
        super(name);
        this.list = list;//给成员变量赋值
    }

    @Override
    public void run() {
 			//为了能看到效果 写个死循环
                while(true){
                    //由于使用的同一个集合 list作为锁对象
                    synchronized (list){
                        //如果集合中没有元素 获取元素的线程进入到等待状态
                        if(list.size()==0){
                            try {
                                list.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        //如果集合中有元素 则获取元素的线程获取元素(删除)
                        list.remove(0);
                        //打印集合 集合中没有元素了
                        System.out.println(list);
                        //集合中已经没有元素 则唤醒添加元素的线程 向集合中添加元素
                        list.notify();
                    }
                }
            }
    }
}

测试代码

public class Demo {
    public static void main(String[] args) {
        //等待唤醒案例
        List<String> list = new ArrayList<>();//共享数据
               
         // 创建线程对象  参:1:线程名称, 参数2:共享数据       
         BaoZiPu bzp = new BaoZiPu("包子铺",list);
        
         //参数1:线程名称   参数2:共享数据
         ChiHuo ch = new ChiHuo("吃货",list);
        // 开启线程
        bzp.start();
        ch.start();
    }
}

盘子中最多可以存储100个包子:

public class Producer extends Thread {
    private ArrayList<String> dish;//盘子,包子铺和吃货都要一样的对象

    private int count = 0;

    public Producer(ArrayList<String> dish) {
        this.dish = dish;
    }

    @Override
    public void run() {

        while (true) {//不断的做
            
            synchronized (dish) 
            {
                if (dish.size() >= 100) {
                    //装不下
                    System.out.println("师傅休息一会儿,盘子满了~~");

                    try {
                        dish.wait();//释放锁,进入无限等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
                //包子可以继续做,盘子还有空间
                count++;
                //模拟做包子时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                dish.add(count + "");
                
                System.out.println("【师傅】做了包子" + count + " ,盘子当前有包子数量:" + dish.size());
                dish.notify();//通知吃货去吃包子
            }

        }
    }
}



public class Consumer extends Thread {
    private ArrayList<String> dish;//盘子

    public Consumer(ArrayList<String> dish) {
        this.dish = dish;
    }

    @Override
    public void run() {

        while (true) {
                  
            synchronized (dish) 
            {
                if (dish.isEmpty()) {
                    //没有包子
                    System.out.println("没包子,吃货休息会儿~~~");
                    try {
                        dish.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //模拟吃包子时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //有包子
                String baozi = dish.remove(0);

                System.out.println("【吃货】吃了包子"+baozi+",盘子中还剩包子数量:"+dish.size());
                
                dish.notify();//通知师傅做包子
            }
        }

    }
}




public class Demo01 {
    public static void main(String[] args) {
        //盘子
        ArrayList<String> dish = new ArrayList<>();
        //创建生产者
        new Producer(dish).start();
        //创建消费者
        new Consumer(dish).start();
    }
}

【线程池】

题目

请按以下步骤编写程序:

  1. 定义一个类,实现Callable接口。此线程可以计算1–100的所有数字的累加和。

  2. 定义测试类,和main()方法,使用线程池启动线程,并获取计算结果,并将结果打印到控制台。

答案:

class Calculator implements Callable<Integer> {
 @Override
 public Integer call() throws Exception {
     int sum = 0;
     for (int i = 1; i <= 100; i++) {
         sum += i;
     }
     return sum;
 }
}


public class Demo06 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
     //1、创建线程池
     ExecutorService threadPool = Executors.newFixedThreadPool(3);
     //提交任务
     Future<Integer> f = threadPool.submit(new Calculator());
     //获取结果
     Integer sum = f.get();
     System.out.println("sum = " + sum);
 }
}

结果:

sum=4950
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值