java多线程学习03(文章留有悬念,等待日后复盘)

导读:本篇文章讲解 java多线程学习03(文章留有悬念,等待日后复盘),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

首先说一下自己做这个创建四个进程的一个小问题:

题目要求说的是:需要你创建四个进程,两个进程为.wait两个进程为.notify,然后我仿照创建了以后是这样子的:

package 任务十__多线程;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-22 21:20
 */
public class Test1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁1") {
                    System.out.println("t1 start");
                    try {
   // t1 释放锁
                        "锁1".wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 end");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁2") {
                    System.out.println("t2 start");
                    try {
                 // 通知 t1 进入等待队列

                        "锁2".wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t2 end");
                }
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁3") {
                    System.out.println("t3 start");
                    try {
                        // 通知 t3 进入等待队列

                        "锁3".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t3 end");
                }
            }
        });


        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁4") {
                    System.out.println("t4 start");
                    try {
                        // 通知 t3 进入等待队列

                        "锁4".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t4 end");
                }
            }
        });

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


}

输出结果是这样:请添加图片描述

我们可以看到为什么t1和t2没有返回值呢?

我开始好奇,感觉因该是

请添加图片描述

这个地方出了问题,后来通过询问同学得知:锁是需要钥匙和它对应的,对于一个锁在被wait以后,他需要一个notify来去“救”原来这个wait,而这个notify必须是前面.wait的notify,也就是我们在synchronized上锁以后,会给他传入一个名字:“锁1”,这样的一个字符串,

请添加图片描述

倘若后面是.wait,那么后续线程中必须有一个同样的synchronized(“锁1”)然后去执行.notify,否则将无法释放锁1的结束语句。

举一个例子,我写了六个进程分别对应锁123451*,前两个是.wait,后四个是.notify,然后按照原理就是,notify的代码不需要“钥匙”去解他所以可以直接输出开始和结束,线程1和2只有1有“钥匙”:也就是第六个线程的.notify,第二个线程的.wait将永远无法输出他的结束:

请添加图片描述

由图可知确实是这样,1345都有开始结束,只有2不可以结束!

然后我们回归正题去写这个题:(就是让每一个.wait锁都有自己的钥匙)

package 任务十__多线程;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-22 21:20
 */
public class Test1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁1") {
                    System.out.println("t1 start");
                    try {
   // t1 释放锁
                        "锁1".wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 end");
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁2") {
                    System.out.println("t2 start");
                    try {
                 // 通知 t1 进入等待队列

                        "锁2".wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t2 end");
                }
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁1") {
                    System.out.println("t3 start");
                    try {
                        // 通知 t3 进入等待队列

                        "锁1".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t3 end");
                }
            }
        });


        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized ("锁2") {
                    System.out.println("t4 start");
                    try {
                        // 通知 t3 进入等待队列

                        "锁2".notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("t4 end");
                }
            }
        });



        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }


}

结果:第一次:

请添加图片描述

第二次:

请添加图片描述

我们可以由此得知线程的启动并不是根据start的顺序来决定的!

柜台卖票:(Therad实现)

package 任务十__多线程;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-24 16:44
 */
public class Tick {

    public static void main(String[] args) {
        Window w1 = new Window("柜台一");
        Window w2 = new Window("柜台二");
        Window w3 = new Window("柜台三");

        w1.start();
        w2.start();
        w3.start();

    }

}
class Window extends Thread{
    private static int  number = 100;

    public Window(String name){
        super(name);
    }

    @Override
    public void run() {
       while (number>0){
           number--;
           System.out.println(getName()+"兑换成功一张票"+"票号为:"+number);
       }
    }
}


输出结果:

请添加图片描述

这里有一个小问题就是:一开始的99和98票消失了!

解决方法:为每一个票添加标记,可以通过放到一个数组里面然后去验证一下是否在数组里面

柜台买票:( Runnable 接口实现):

首先说一下自己一开始的想法那就是:存放到集合里面去三个线程遍历一个循环

package 任务十__多线程.售票;

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

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-24 20:25
 */
public class Tick1 {

    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 1; i <= 100; i++){
            list.add(i);
        }
        Iterator iterator = list.iterator();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                    while (iterator.hasNext()) {
                        for (int i = 0; i < list.size(); i++) {
                            synchronized ("lock") {
                                String threadname = Thread.currentThread().getName();
                                System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
                                list.remove(i); // 删除已经遍历的元素
                            }
                        }
                    }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                    while (iterator.hasNext()) {
                        for (int i = 0; i < list.size(); i++) {
                            synchronized ("lock") {
                                String threadname = Thread.currentThread().getName();
                                System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
                                list.remove(i);
                            }
                        }
                    }
                }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                    while (iterator.hasNext()) {

                        for (int i = 0; i < list.size(); i++) {
                            synchronized ("lock") {
                                String threadname = Thread.currentThread().getName();
                                System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
                                list.remove(i);
                            }
                        }
                    }
            }
        });

        t1.start();
        t2.start();
        t3.start();


    }
}

上面这段代码由于自己还没有学习,java委托和事件,所以并没有全部完成,以后我会反过来解决这个题,如何三个线程干同一件事情

然后接下来讲解一下怎样使用Runnable 简单实现买票:

package 任务十__多线程.售票;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-24 21:43
 */
public class Ticket implements  Runnable {
    private   int  num1 = 0;    //出票数
    private   int  num2 = 100;   //剩余票数
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (num2 <= 0) {
                    break;
                }
                num1++;
                num2--;
                System.out.println("显示出票信息" + Thread.currentThread().getName() + "抢到第" + num1 + "张票,剩余" + num2 + "张票");
            }
        }
    }
    static class Test {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
            Thread g1 = new Thread(ticket,"柜台一");
            Thread g2 = new Thread(ticket,"柜台二");
            Thread g3 = new Thread(ticket,"柜台三");
            g1.start();
            g2.start();
            g3.start();
        }
    }
}


请添加图片描述

这里遇到了一个小问题:就是总是先启动的哪个线程先抢票而且很有可能他会一直抢下去

请添加图片描述

这里说一个改进方法:

package 任务十__多线程.售票;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-24 23:43
 */
class SaleThread implements Runnable {

    /**
     * 使用静态成员变量作为100张票的保存变量,是一个共享资源。
     */
    private static int tickets = 20;

    @Override
    public void run() {

        // 完成售票过程
        while (true) {
   /*
   字符串可以作为锁对象,因为双引号包含的字符串不管在代码中如何运行,有且只有一个
    */
            synchronized (Thread.currentThread().getName()) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出了" + tickets + "张票");
                    tickets--;
                } else {
                    System.out.println(Thread.currentThread().getName() + "售罄!!!");
                    break;
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new SaleThread(), "售票人员1");
        Thread t2 = new Thread(new SaleThread(), "售票人员2");
        Thread t3 = new Thread(new SaleThread(), "售票人员3");

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

运行结果:

请添加图片描述

说一下改进在了什么地方: synchronized (Thread.currentThread().getName()) {}

我们之前都是用的synchronized{},但是这样写会导致synchronized (this) 会连续执行当前线程,所以会出现大片大片一样的代码

使用Thread实现:

package 任务十__多线程.售票;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-24 23:41
 */
public class Ticket2 extends Thread {
        private  int num1 = 0;
        private  int num2 = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (Thread.currentThread().getName()){ ;
            if (num2 <= 0) {
                break;
            }
            num1++;
            num2--;
            System.out.println("显示出票信息" + Thread.currentThread().getName() + "抢到第" + num1 + "张票,剩余" + num2 + "张票");
        }
        }
    }
}
class  Function{
    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();
        Thread t1 = new Thread(ticket2,"柜台一");
        Thread t2 = new Thread(ticket2,"柜台二");
        Thread t3 = new Thread(ticket2,"柜台三");
        t1.start();
        t2.start();
        t3.start();
    }
}

总是遇到线程安全性的问题:就是同一个票同时被不同线程拿到的假象,这里写一个真实有效的代码:

package 任务十__多线程.售票;

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


public class TestDemo {

    //定义售票线程类(也就是窗口)
    public static class Station extends Thread{
        //构造方法给线程名字赋值
        public Station(String name) {
            super(name);
        }
        //票数要静态定义
        static int tick=50;
        //静态钥匙
        static Object ob ="key"; //值是任意的
        //重写run方法,实现售票操作
        @Override
        public void run() {
            List<Integer>  list = new ArrayList<>();
            while (tick>0) {
                synchronized("key") { //必须使用一个同步锁,进去的人会把钥匙拿在手上,出来后才能交出钥匙
                    if (tick>0) {
                        System.out.printf("%s卖出了第%d张票 \n",getName(),tick);
                        list.add(tick);
                        tick--;
                    }else {
                        System.out.printf("%s:票已售空 \n",getName());

                    }
                }
                try {
                    sleep((int)(Math.random()*3000)+1); //随机休息1-3000ms
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("%s 销售情况: %s \n",getName(),list.toString());
        }
    }

    public static void main(String[] args) {
        //实例化站台对象,并为每一个站台取名字(8个线程窗口一起卖5张票)
        for (int i=1; i<=8; i++) {
            String sName="窗口" + String.valueOf(i);
            Station  Station = new Station(sName);
            Station.start();
        }

    }

}

请添加图片描述

请添加图片描述

可以看出这里就很正常,现在太晚了脑子有点糊,改日回过神来再看!先就到这里了

这里再说一下吧,关于synchronized(this)这个真的令人可苦恼的东西,他并不是说会造成真正的线程安全问题,他仅仅是显示上的问题比如:

package 任务十__多线程.售票;

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


public class TestDemo {

    //定义售票线程类(也就是窗口)
    public static class Station extends Thread{
        //构造方法给线程名字赋值
        public Station(String name) {
            super(name);
        }
        //票数要静态定义
        static int tick=50;
        //静态钥匙
        static Object ob ="key"; //值是任意的
        //重写run方法,实现售票操作
        @Override
        public void run() {
            List<Integer>  list = new ArrayList<>();
            while (tick>0) {
                synchronized(this) { //必须使用一个同步锁,进去的人会把钥匙拿在手上,出来后才能交出钥匙
                    if (tick>0) {
                        System.out.printf("%s卖出了第%d张票 \n",getName(),tick);
                        list.add(tick);
                        tick--;
                    }else {
                        System.out.printf("%s:票已售空 \n",getName());

                    }
                }
                try {
                    sleep((int)(Math.random()*3000)+1); //随机休息1-3000ms
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("%s 销售情况: %s \n",getName(),list.toString());
        }
    }

    public static void main(String[] args) {
        //实例化站台对象,并为每一个站台取名字(8个线程窗口一起卖5张票)
        for (int i=1; i<=8; i++) {
            String sName="窗口" + String.valueOf(i);
            Station  Station = new Station(sName);
            Station.start();
        }

    }

}

运行结果:

请添加图片描述
请添加图片描述

我们可以看出来其实真正的数据并没有错误!!!!!

这里留下悬念,等待日后的复盘!!

分割线···································································································

线程池:使用 newScheduledThreadPool 线程池实现每隔 1 分钟打印一条消息

首先讲解一下使用 newScheduledThreadPool 是怎样使用的以及一些参数说明:

参数说明:

  • command:执行线程
  • initialDelay:初始化延时
  • period:两次开始执行最小间隔时间
  • unit:计时单位

1:首先说一下它的特点:延时启动 、定时启动 、可以自定义最大线程池数量

2:创建实例:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

这里如果没有特殊需求要指定最大线程池数量的话,建议最大线程池数量=运行程序机器的cpu核心数,即

int cpuNubmer = Runtime.getRuntime().availableProcessors();
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(cpuNubmer);

3:延时运行举例:这里需要用匿名内部类的方式,实现Runnable接口,重写Runnable的run方法,将Runnable类型的参数传入schedule方法中。


final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
System.out.println("提交时间: " + sdf.format(new Date()));
scheduledThreadPool.schedule(new Runnable() {
    @Override
    public void run() {
        System.out.println("运行时间: " + sdf.format(new Date()));
    }
}, 3, TimeUnit.SECONDS);//延迟3秒执行
scheduledThreadPool.shutdown();

4:用lambda表达式的写法:函数式编程:

final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
System.out.println("提交时间: " + sdf.format(new Date()));
scheduledThreadPool.schedule(() -> System.out.println("运行时间: " + sdf.format(new Date())), 3, TimeUnit.SECONDS);
scheduledThreadPool.shutdown();

每分钟输出与每秒钟输出:

package 任务十__多线程.周期线程池;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-25 0:49
 */
public class Test {

    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        pool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("一分钟过去了");
            }
        }, 3, 1, TimeUnit.MINUTES);
    }

}






改成秒:
package 任务十__多线程.周期线程池;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author ${范涛之}
 * @Description
 * @create 2021-11-25 0:49
 */
public class Test {

    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        pool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("一分钟过去了");
            }
        }, 3, 1, TimeUnit.SECONDS);
    }

}


其实就是最后的TimeUnit修改一下就好,根据我上面说的参数自己定义即可!

多线程正式完结!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/81045.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!