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

x33g5p2x  于2021-11-25 转载在 Java  
字(11.6k)|赞(0)|评价(0)|浏览(826)

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

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

  1. package 任务十__多线程;
  2. /** * @author ${范涛之} * @Description * @create 2021-11-22 21:20 */
  3. public class Test1 {
  4. public static void main(String[] args) {
  5. Thread t1 = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. synchronized ("锁1") {
  9. System.out.println("t1 start");
  10. try {
  11. // t1 释放锁
  12. "锁1".wait();
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println("t1 end");
  17. }
  18. }
  19. });
  20. Thread t2 = new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. synchronized ("锁2") {
  24. System.out.println("t2 start");
  25. try {
  26. // 通知 t1 进入等待队列
  27. "锁2".wait();
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. System.out.println("t2 end");
  32. }
  33. }
  34. });
  35. Thread t3 = new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. synchronized ("锁3") {
  39. System.out.println("t3 start");
  40. try {
  41. // 通知 t3 进入等待队列
  42. "锁3".notify();
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. System.out.println("t3 end");
  47. }
  48. }
  49. });
  50. Thread t4 = new Thread(new Runnable() {
  51. @Override
  52. public void run() {
  53. synchronized ("锁4") {
  54. System.out.println("t4 start");
  55. try {
  56. // 通知 t3 进入等待队列
  57. "锁4".notify();
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. }
  61. System.out.println("t4 end");
  62. }
  63. }
  64. });
  65. t1.start();
  66. t2.start();
  67. t3.start();
  68. t4.start();
  69. }
  70. }

输出结果是这样:

我们可以看到为什么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锁都有自己的钥匙)

  1. package 任务十__多线程;
  2. /** * @author ${范涛之} * @Description * @create 2021-11-22 21:20 */
  3. public class Test1 {
  4. public static void main(String[] args) {
  5. Thread t1 = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. synchronized ("锁1") {
  9. System.out.println("t1 start");
  10. try {
  11. // t1 释放锁
  12. "锁1".wait();
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println("t1 end");
  17. }
  18. }
  19. });
  20. Thread t2 = new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. synchronized ("锁2") {
  24. System.out.println("t2 start");
  25. try {
  26. // 通知 t1 进入等待队列
  27. "锁2".wait();
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. System.out.println("t2 end");
  32. }
  33. }
  34. });
  35. Thread t3 = new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. synchronized ("锁1") {
  39. System.out.println("t3 start");
  40. try {
  41. // 通知 t3 进入等待队列
  42. "锁1".notify();
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. System.out.println("t3 end");
  47. }
  48. }
  49. });
  50. Thread t4 = new Thread(new Runnable() {
  51. @Override
  52. public void run() {
  53. synchronized ("锁2") {
  54. System.out.println("t4 start");
  55. try {
  56. // 通知 t3 进入等待队列
  57. "锁2".notify();
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. }
  61. System.out.println("t4 end");
  62. }
  63. }
  64. });
  65. t1.start();
  66. t2.start();
  67. t3.start();
  68. t4.start();
  69. }
  70. }

结果:第一次:

第二次:

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

柜台卖票:(Therad实现)

  1. package 任务十__多线程;
  2. /** * @author ${范涛之} * @Description * @create 2021-11-24 16:44 */
  3. public class Tick {
  4. public static void main(String[] args) {
  5. Window w1 = new Window("柜台一");
  6. Window w2 = new Window("柜台二");
  7. Window w3 = new Window("柜台三");
  8. w1.start();
  9. w2.start();
  10. w3.start();
  11. }
  12. }
  13. class Window extends Thread{
  14. private static int number = 100;
  15. public Window(String name){
  16. super(name);
  17. }
  18. @Override
  19. public void run() {
  20. while (number>0){
  21. number--;
  22. System.out.println(getName()+"兑换成功一张票"+"票号为:"+number);
  23. }
  24. }
  25. }

输出结果:

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

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

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

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

  1. package 任务十__多线程.售票;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. import java.util.List;
  5. /** * @author ${范涛之} * @Description * @create 2021-11-24 20:25 */
  6. public class Tick1 {
  7. public static void main(String[] args) {
  8. List list = new ArrayList();
  9. for (int i = 1; i <= 100; i++){
  10. list.add(i);
  11. }
  12. Iterator iterator = list.iterator();
  13. Thread t1 = new Thread(new Runnable() {
  14. @Override
  15. public void run() {
  16. while (iterator.hasNext()) {
  17. for (int i = 0; i < list.size(); i++) {
  18. synchronized ("lock") {
  19. String threadname = Thread.currentThread().getName();
  20. System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
  21. list.remove(i); // 删除已经遍历的元素
  22. }
  23. }
  24. }
  25. }
  26. });
  27. Thread t2 = new Thread(new Runnable() {
  28. @Override
  29. public void run() {
  30. while (iterator.hasNext()) {
  31. for (int i = 0; i < list.size(); i++) {
  32. synchronized ("lock") {
  33. String threadname = Thread.currentThread().getName();
  34. System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
  35. list.remove(i);
  36. }
  37. }
  38. }
  39. }
  40. });
  41. Thread t3 = new Thread(new Runnable() {
  42. @Override
  43. public void run() {
  44. while (iterator.hasNext()) {
  45. for (int i = 0; i < list.size(); i++) {
  46. synchronized ("lock") {
  47. String threadname = Thread.currentThread().getName();
  48. System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
  49. list.remove(i);
  50. }
  51. }
  52. }
  53. }
  54. });
  55. t1.start();
  56. t2.start();
  57. t3.start();
  58. }
  59. }

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

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

  1. package 任务十__多线程.售票;
  2. /** * @author ${范涛之} * @Description * @create 2021-11-24 21:43 */
  3. public class Ticket implements Runnable {
  4. private int num1 = 0; //出票数
  5. private int num2 = 100; //剩余票数
  6. @Override
  7. public void run() {
  8. while (true) {
  9. synchronized (this) {
  10. if (num2 <= 0) {
  11. break;
  12. }
  13. num1++;
  14. num2--;
  15. System.out.println("显示出票信息" + Thread.currentThread().getName() + "抢到第" + num1 + "张票,剩余" + num2 + "张票");
  16. }
  17. }
  18. }
  19. static class Test {
  20. public static void main(String[] args) {
  21. Ticket ticket = new Ticket();
  22. Thread g1 = new Thread(ticket,"柜台一");
  23. Thread g2 = new Thread(ticket,"柜台二");
  24. Thread g3 = new Thread(ticket,"柜台三");
  25. g1.start();
  26. g2.start();
  27. g3.start();
  28. }
  29. }
  30. }

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

这里说一个改进方法:

  1. package 任务十__多线程.售票;
  2. /** * @author ${范涛之} * @Description * @create 2021-11-24 23:43 */
  3. class SaleThread implements Runnable {
  4. /** * 使用静态成员变量作为100张票的保存变量,是一个共享资源。 */
  5. private static int tickets = 20;
  6. @Override
  7. public void run() {
  8. // 完成售票过程
  9. while (true) {
  10. /* 字符串可以作为锁对象,因为双引号包含的字符串不管在代码中如何运行,有且只有一个 */
  11. synchronized (Thread.currentThread().getName()) {
  12. try {
  13. Thread.sleep(100);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. if (tickets > 0) {
  18. System.out.println(Thread.currentThread().getName() + "售出了" + tickets + "张票");
  19. tickets--;
  20. } else {
  21. System.out.println(Thread.currentThread().getName() + "售罄!!!");
  22. break;
  23. }
  24. }
  25. }
  26. }
  27. }
  28. public class Demo {
  29. public static void main(String[] args) {
  30. Thread t1 = new Thread(new SaleThread(), "售票人员1");
  31. Thread t2 = new Thread(new SaleThread(), "售票人员2");
  32. Thread t3 = new Thread(new SaleThread(), "售票人员3");
  33. t1.start();
  34. t2.start();
  35. t3.start();
  36. }
  37. }

运行结果:

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

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

使用Thread实现:

  1. package 任务十__多线程.售票;
  2. /** * @author ${范涛之} * @Description * @create 2021-11-24 23:41 */
  3. public class Ticket2 extends Thread {
  4. private int num1 = 0;
  5. private int num2 = 100;
  6. @Override
  7. public void run() {
  8. while (true) {
  9. synchronized (Thread.currentThread().getName()){ ;
  10. if (num2 <= 0) {
  11. break;
  12. }
  13. num1++;
  14. num2--;
  15. System.out.println("显示出票信息" + Thread.currentThread().getName() + "抢到第" + num1 + "张票,剩余" + num2 + "张票");
  16. }
  17. }
  18. }
  19. }
  20. class Function{
  21. public static void main(String[] args) {
  22. Ticket2 ticket2 = new Ticket2();
  23. Thread t1 = new Thread(ticket2,"柜台一");
  24. Thread t2 = new Thread(ticket2,"柜台二");
  25. Thread t3 = new Thread(ticket2,"柜台三");
  26. t1.start();
  27. t2.start();
  28. t3.start();
  29. }
  30. }

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

  1. package 任务十__多线程.售票;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class TestDemo {
  5. //定义售票线程类(也就是窗口)
  6. public static class Station extends Thread{
  7. //构造方法给线程名字赋值
  8. public Station(String name) {
  9. super(name);
  10. }
  11. //票数要静态定义
  12. static int tick=50;
  13. //静态钥匙
  14. static Object ob ="key"; //值是任意的
  15. //重写run方法,实现售票操作
  16. @Override
  17. public void run() {
  18. List<Integer> list = new ArrayList<>();
  19. while (tick>0) {
  20. synchronized("key") { //必须使用一个同步锁,进去的人会把钥匙拿在手上,出来后才能交出钥匙
  21. if (tick>0) {
  22. System.out.printf("%s卖出了第%d张票 \n",getName(),tick);
  23. list.add(tick);
  24. tick--;
  25. }else {
  26. System.out.printf("%s:票已售空 \n",getName());
  27. }
  28. }
  29. try {
  30. sleep((int)(Math.random()*3000)+1); //随机休息1-3000ms
  31. }catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. System.out.printf("%s 销售情况: %s \n",getName(),list.toString());
  36. }
  37. }
  38. public static void main(String[] args) {
  39. //实例化站台对象,并为每一个站台取名字(8个线程窗口一起卖5张票)
  40. for (int i=1; i<=8; i++) {
  41. String sName="窗口" + String.valueOf(i);
  42. Station Station = new Station(sName);
  43. Station.start();
  44. }
  45. }
  46. }

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

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

  1. package 任务十__多线程.售票;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class TestDemo {
  5. //定义售票线程类(也就是窗口)
  6. public static class Station extends Thread{
  7. //构造方法给线程名字赋值
  8. public Station(String name) {
  9. super(name);
  10. }
  11. //票数要静态定义
  12. static int tick=50;
  13. //静态钥匙
  14. static Object ob ="key"; //值是任意的
  15. //重写run方法,实现售票操作
  16. @Override
  17. public void run() {
  18. List<Integer> list = new ArrayList<>();
  19. while (tick>0) {
  20. synchronized(this) { //必须使用一个同步锁,进去的人会把钥匙拿在手上,出来后才能交出钥匙
  21. if (tick>0) {
  22. System.out.printf("%s卖出了第%d张票 \n",getName(),tick);
  23. list.add(tick);
  24. tick--;
  25. }else {
  26. System.out.printf("%s:票已售空 \n",getName());
  27. }
  28. }
  29. try {
  30. sleep((int)(Math.random()*3000)+1); //随机休息1-3000ms
  31. }catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. System.out.printf("%s 销售情况: %s \n",getName(),list.toString());
  36. }
  37. }
  38. public static void main(String[] args) {
  39. //实例化站台对象,并为每一个站台取名字(8个线程窗口一起卖5张票)
  40. for (int i=1; i<=8; i++) {
  41. String sName="窗口" + String.valueOf(i);
  42. Station Station = new Station(sName);
  43. Station.start();
  44. }
  45. }
  46. }

运行结果:

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

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

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

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

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

参数说明:

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

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

2:创建实例:

  1. ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

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

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

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

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

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

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

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

  1. package 任务十__多线程.周期线程池;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.ScheduledExecutorService;
  4. import java.util.concurrent.TimeUnit;
  5. /** * @author ${范涛之} * @Description * @create 2021-11-25 0:49 */
  6. public class Test {
  7. public static void main(String[] args) {
  8. ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
  9. pool.scheduleAtFixedRate(new Runnable() {
  10. @Override
  11. public void run() {
  12. System.out.println("一分钟过去了");
  13. }
  14. }, 3, 1, TimeUnit.MINUTES);
  15. }
  16. }
  17. 改成秒:
  18. package 任务十__多线程.周期线程池;
  19. import java.util.concurrent.Executors;
  20. import java.util.concurrent.ScheduledExecutorService;
  21. import java.util.concurrent.TimeUnit;
  22. /** * @author ${范涛之} * @Description * @create 2021-11-25 0:49 */
  23. public class Test {
  24. public static void main(String[] args) {
  25. ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
  26. pool.scheduleAtFixedRate(new Runnable() {
  27. @Override
  28. public void run() {
  29. System.out.println("一分钟过去了");
  30. }
  31. }, 3, 1, TimeUnit.SECONDS);
  32. }
  33. }

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

多线程正式完结!

相关文章

最新文章

更多

目录