随笔 - 8  文章 - 0  评论 - 0  阅读 - 1252

Java多线程基础

多线程基础相关笔记

1.线程相关知识

1.1、程序

是为完成特定任务、用某种语言编写的一组指令的集合。
简单的说:就是我们写的代码

1.2、进程

1.进程是指运行中的程序,比如我们使用浏览器,就启动了一个进程,操作系统就会为
该进程分配内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的
产生、存在和消亡的过程.

1.2、线程

1.线程由进程创建的,是进程的一个实体。
2.一个进程可以拥有多个线程。

1.3、并发与并行

并发是指在一个时间段内有多个进程在执行。

并行指的是在同一时刻有多个进程在同时执行。

​ 如果是在只有一个CPU的情况下,是无法实现并行的,因为同一时刻只能有一个进程被调度执行,如果此时同时要执行其他进程则必须上下文切换,这种只能称之为并发,而如果是多个CPU的情况下,就可以同时调度多个进程,这种就可以称之为并行。(一般情况下为多核)

举个例子,一个人坐在车上同时打电话和开车为并发,两个人坐在车上一个人打电话一个人开车分工进行为并行。

2.线程的基本使用

2.1、案例1:继承Thread类,重写run方法

业务逻辑:每隔一秒输出“喵喵,我是小猫咪“,输出多少次。

public class MyThread {
    public static void main(String[] args) {
        //创建一个cat对象可以当做线程使用
        Cat cat=new Cat();
        cat.start();//启动线程==>最终会执行 cat 的 run 方法
        /**
         * cat.run();run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
         * 调用 cat.run()打印的线程名称是main(可以观看打印结果)
         * 说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
         * 这时 主线程和子线程是交替执行..
         */
         for(int i = 0; i < 80; i++){
             System.out.println("主线程 i=" + i+"主线程继续执行" + Thread.currentThread().getName()); //让主线程main休眠
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
    }
}
/**
 *  1:当一个类继承了Thread类,该类就可以当做线程使用
 *  2:我们会重写run方法, 写上自己的业务代码
 *  3:run Thread 类实现了RunnabLe 接口的run方法
*/
 class Cat extends Thread{
      //业务逻辑:每隔一秒输出“喵喵,我是小猫咪“
    int num=0;
    @Override
     public void run(){//重写run方法,写上自己的业务逻辑
         while(true){
             System.out.println("喵喵,我是小猫咪:"+(++num)+""+":cat线程名称" + Thread.currentThread().getName());
             //快捷键Ctrl+Alt+t
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             if (num==80){
                 break;
             }
         }
     }
 }

2.2、案例2:通过实现接口Runnable来开发线程

业务逻辑:请编写程序该程序可以每隔1秒。在控制台输出"小狗汪汪叫" ,当输出10次后,自动退出。
这里底层使用了设计模式[代理模式] =>代码模拟实现Runnable接口开发线程的机制。下面代码会模拟代理(可以打断点观察运行过程)。

public class MyThread02 {
    public static void main(String[] args) {
        Dog dog=new Dog();
        //dog.start();这里不能调用start();
        // 创建Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
        Thread thread=new Thread(dog);
        thread.start();

         Tiger tiger = new Tiger();//实现了 Runnable
         ThreadProxy threadProxy = new ThreadProxy(tiger);
         threadProxy.start();
    }
}

class Dog implements Runnable{//通过实现接口Runnable来开发线程
    int num=0;
    @Override
    public void run() {
        while (true){
            System.out.println("小狗汪汪叫:"+(++num)+""+"。线程名称:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (num==10){
                break;
            }
        }
    }
}
class Animal { }
class Tiger extends Animal implements Runnable {
    @Override
    public void run(){
        System.out.println("老虎嗷嗷叫....");
    }
}

//线程代理类 , 模拟了一个极简的 Thread 类
class ThreadProxy implements Runnable {//线程代理
     private Runnable target = null;//属性,类型是 Runnable
     @Override
     public void run() {
         if (target != null) {
             target.run();//动态绑定(运行类型 Tiger) } }
         }
     }
    public ThreadProxy(Runnable target) {
         this.target = target;
    }
    public void start() {
         start0();//这个方法时真正实现多线程方法
    }
    public void start0() {
         run();
 }
}

2.3、案例3:多线程执行

public class MyThread03 {
    public static void main(String[] args) {
        T1 t1=new T1();
        T2 t2=new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
    }
}
class T1 implements Runnable{
    int num=0;
    @Override
    public void run() {
        while (true){
            System.out.println("小狗汪汪汪汪~:"+(++num)+""+"。线程名称:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (num==60){
                break;
            }
        }
    }
}
class T2 implements Runnable{
    int num=0;
    @Override
    public void run() {
        while (true){
            System.out.println("小鸟渣渣渣~:"+(++num)+""+"。线程名称:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (num==60){
                break;
            }
        }
    }
}

2.4、继承 Thread vs 实现 Runnable 的区别

从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本
质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现Runnable接口。
实现Runnable接口方式更加适合多个线程共享一个资源的情况, 并且避免了单继承的限制,建议使用Runnable。

编写购票系统留下问题:在下面的购票系统中(两种创建线程的方法都会出现问题)会有一张票被售出多次和超卖的现象。(区别在后面的第6.2章节体现的特别明显,仔细研究6.2章节代码与下面代码可以看出区别)

public class MyThread04 {
    public static void main(String[] args) {
        //测试
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();
        //这里我们会出现超卖..
        sellTicket01.start();//启动售票线程
        sellTicket02.start();//启动售票线程
        sellTicket03.start();//启动售票线程

//        System.out.println("===使用实现接口方式来售票=====");
//        SellTicket02 sellTicket04= new SellTicket02();
//        new Thread(sellTicket04).start();//第 1 个线程-窗口
//        new Thread(sellTicket04).start();//第 2 个线程-窗口
//        new Thread(sellTicket04).start();//第 3 个线程-窗口
    }
}
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享 ticketNum
     @Override
     public void run(){
         while (true) {
             if (ticketNum <= 0) {
                 System.out.println("售票结束...");
                 break;
             }//休眠 50 毫秒, 模拟
             try {
                 Thread.sleep(50);
             } catch (InterruptedException e) {
                   e.printStackTrace();
             }
             System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
         }
     }
}
//实现接口方式
class SellTicket02 implements Runnable{
    private static int ticketNum = 100;//让多个线程共享 ticketNum
    @Override
    public void run(){
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束了...");
                break;
            }//休眠 50 毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
        }
    }
}

3.线程的退出和终止

1.当线程完成任务后,会自动退出。
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。

通知方式演示:

package com.wushang.threadStudy;

public class MyThread05 {
    public static void main(String[] args) {
       ThreadTest  threadTest=new ThreadTest();
       Thread thread=new Thread(threadTest);
       thread.start();
       //改变Lifestate的值使其结束(通知)
        try {
            //为了效果明显让thread这个线程张扬一会儿(main线程休眠)时间自定义
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //主线程停止休眠之后更改Lifestate的值通知它寿命到了。
        threadTest.exLifestatus(false);
    }
}
class ThreadTest implements Runnable{
    private Boolean Lifestate=true;
    private int num=0;
    @Override
    public void run() {
      while (Lifestate){
          System.out.println("你被狗咬了"+(++num)+"口,请赶紧去打狂犬疫苗");
          try {
              Thread.sleep(50);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
    }
    public void exLifestatus(Boolean status){
         this.Lifestate=status;
        //用于接收阎王(main线程)的通知。
    }

}

4.线程的常用方法

4.1、第一组:

1. setName //设置线程名称,使之与参数name相同
2. getName //返回该线程的名称
3. start //使该线程开始执行; Java 虚拟机底层调用该线程的startO方法
4. run //调用线程对象run方法:
5. setPriority //更改线程的优先级
6. getPriority //获取线程的优先级
7. sleep
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8. interrupt //中断线程
/**
1. start 底层会创建新的线程,调用run, run就是一个简单的方法调用,不会启动新
线程
2.线程优先级的范围
3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
4. sleep:线程的静态方法,使当前线程休眠
*/

应用举例,(张三搬砖(老板和张三属于一个进程))

package com.wushang.threadStudy;

public class MyThread06 {
    public static void main(String[] args) {
            T t=new T();
            Thread thread=new Thread(t);
            thread.setName("张三");
            thread.setPriority(1);
            thread.start();
            for (int i=5;i>0;i--){
                try {
                    Thread.sleep(1000);
                    System.out.println("老板来了在喊倒计时"+i+","+thread.getName()+"该起来工作了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           System.out.println("倒计时结束执行thread.interrupt()");
           thread.interrupt();//中断张三的休眠状态
        //中断执行之后main线程执行结束(老板走了,张三搬10块砖之后又开始偷懒)
    }
}
class T implements Runnable{
    @Override
    public void run() {
        while (true){
//            System.out.println();
        for (int i=1;i<=10;i++){
            System.out.println(Thread.currentThread().getName()+"为了挣钱养家搬了"+i+"块砖");
        }
        try {
            System.out.println(Thread.currentThread().getName()+"快要累死了休息会儿,然后准备继续搬砖");
            Thread.sleep(20000);
        } catch (InterruptedException e) {
           //当休眠被终端的时候会抛出一个异常
            System.out.println(Thread.currentThread().getName()+"被老板中断休眠(interrupt),赶紧起来搬砖");
        }
        }
    }
}

4.3、第二组:

1. yield//线程的礼让。让出cpu, 让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
2. join//线程的插队。插队的线程- 旦插队成功,则肯定先执行完插入的线程所有的任务
//案例:(体育老师和校长亲信赛跑)
public class MyThread07 {
    public static void main(String[] args) {
        System.out.println("体育老师举办了运动会,和校长亲信比赛跑步");
         Th th=new Th();
         Thread t=new Thread(th);
         t.start();
        for (int i=100;i>=0;i--){
            try {
                Thread.sleep(500);
                System.out.println("体育老师(main)还差"+i+"m到达终点");
                if(i==10){
                    System.out.println("体育老师面对人情世故,在距离终点线10米的地方崴脚(join())了,让校长亲信跑到终点时站起来继续跑");
                    t.join();
             //System.out.println("体育老师礼让一下校长亲信");
                   // Thread.yield();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Th implements Runnable{

    @Override
    public void run() {
            for (int i=100;i>=0;i--){
                try {
                    Thread.sleep(500);
                    System.out.println("校长亲信还差"+i+"m到达终点");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
}

4.3用户线程和守护线程

1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程:垃圾回收机制

案例:小强和小王监控老板位置然后打游戏。

package com.wushang.threadStudy;

public class MyThread08 {
    public static void main(String[] args) {
         Thr thr=new Thr();
         Thread t=new Thread(thr);
        //如果我们希望当main线程结束后,子线程自动结束
        //简单理解老板没有在办公室工作的时候小强和小王停止打游戏
        //只需将子线程设为守护线程即可(Start之前)
        t.setDaemon(true);
        t.start();
        for (int i=0;i<10;i++) {
            try {
                Thread.sleep(500);
                System.out.println("老板在办公室工作,");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Thr implements Runnable{
    @Override
    public void run() {
        while (true){//无线循环
            try {
                Thread.sleep(500);
                System.out.println("小强和小王上班时候打游戏");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.线程的生命周期

public static enum Thread. State
extends Enum<ThreadState>
/*
线程状态。线程可以处于以下状态之一 :
●NEW
尚未启动的线程处于此状态。
●RUNNABIE(可运行状态)
在Java虛拟机中执行的线程处于此状态。
//@TODO RUNNABIE分为两个状态,running(运行态)和Ready(就绪状态)状态
●BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
●WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
●TIMED WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
●TERMINATED
己退出的线程处于此状态。
*/

/**
对流程图的简单解释(关于其中的区别看百度)
   yield()==>礼让
   new==>创建
   start()==>执行
   join()==>插队
   sleep()==>休眠
  /*
   wait()==>等待
   notify()==>当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够wait状态中恢复,然后继续运行wait()后面的语句;只会唤醒等待该锁的其中一个线程。
   notifyAll()==>当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行;唤醒等待该锁的所有线程。
   */
   /* 
        其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。             LockSupport.park(thread)==>中断
        LockSupport.unpark(thread)==>唤醒 
        */
*/

6、线程的同步机制

6.1、理论基础和注意事项

/*
1.在多线程编程,-些敏感数据不允许被多个线程同时访问,此时就使用同步访问技
术,保证数据在任何同时刻, 最多有一个线程访问,以保证数据的完整性。
2. 也可以这里理解: 线程同步,即当有一个线程在对内存进行操作时, 其他线程都不
可以对这个内存地址进行操作,直到该线程完成操作,其他线程才 能对该内存地
址进行操作
*/
/*1. Java语言中, 引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应一个可称为“互斥锁"的标记,这个标记用来保证在任一时刻,只
能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,
表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个锁对象)
6.同步方法(静态的)的锁为当前类本身。
*/

6.2、案例介绍(本章精华)

在前面2.4章节的购票系统中埋下了雷,现在予以解决。此时可以很明显的看出来继承和接口的区别。

package com.wushang.threadStudy;

public class MyThread04 {
    public static void main(String[] args) {
//        System.out.println("===使用实现继承方式来售票(同步方法+继承)=====");
//        SellTicket01 s10 = new SellTicket01();
//        SellTicket01 s11 = new SellTicket01();
//        SellTicket01 s12 = new SellTicket01();
//        //这里我们会出现超卖..
//        s10.start();//启动售票线程
//        s11.start();//启动售票线程
//        s12.start();//启动售票线程
//
//        System.out.println("===使用实现接口方式来售票(接口+同步方法)=====");
//        SellTicket02 s2= new SellTicket02();
//        new Thread(s2).start();//第 1 个线程-窗口
//        new Thread(s2).start();//第 2 个线程-窗口
//        new Thread(s2).start();//第 3 个线程-窗口
//
//        System.out.println("===使用实现接口方式来售票(接口+同步代码块测试)=====");
//        SellTicket03 s3= new SellTicket03();
//        new Thread(s3).start();//第 1 个线程-窗口
//        new Thread(s3).start();//第 2 个线程-窗口
//        new Thread(s3).start();//第 3 个线程-窗口

        System.out.println("===使用实现继承方式来售票(同步代码快+继承)=====");
        SellTicket04 s40 = new SellTicket04();
        SellTicket04 s41 = new SellTicket04();
        SellTicket04 s42 = new SellTicket04();
        //这里我们会出现超卖..
        s40.start();//启动售票线程
        s41.start();//启动售票线程
        s42.start();//启动售票线程
    }
}
//extends Thread直接用同步方法会超卖,是因为每个线程都是执行他们各自对象的方法
//假如使用继承Thread类的话,会创建多个对象,所以继承thread 类多线程得加类锁 即 static synchronized,在后面代码块时不能直接用this也是这个道理,锁对象应该是同一个
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享 ticketNum
    private static Boolean loop=true;
    public static synchronized void sell(){//同步方法+继承
        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            loop=false;
            return;
        }//休眠 50 毫秒, 模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
    }
     @Override
     public void run(){
         while (loop) {
             sell();
         }
     }
}
//实现接口方式
class SellTicket02 implements Runnable{
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private Boolean loop=true;
    public synchronized void sell(){
        if (ticketNum <= 0) {
            System.out.println("售票结束了...");
            loop=false;
            return;
        }//休眠 50 毫秒, 模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
    }
    @Override
    public void run(){
        while (loop) {
           sell();
        }
    }
}
class SellTicket03 implements Runnable{
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private Boolean loop=true;
    public /**synchronized*/ void sell(){
      synchronized (this) {
          if (ticketNum <= 0) {
              System.out.println("售票结束了...");
              loop = false;
              return;
          }
          try {
              //休眠 50 毫秒, 模拟
              Thread.sleep(50);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
      }
    }
    @Override
    public void run(){
        while (loop) {
            sell();
        }
    }
}
class SellTicket04 extends Thread {
    private static  int ticketNum = 100;//让多个线程共享 ticketNum
    private static Boolean loop=true;
    private static  Object object=new Object();
    //这里使用的是继承,在main中new了3个对象,所以要用一个锁对象来表示。
    public static /**synchronized*/ void sell() {
        synchronized (object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }//休眠 50 毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
        }
    }
    @Override
    public void run(){
        while (loop) {
            sell();
        }
    }
}

7、线程死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。

生活举例:甲和乙打架时揪住了对方的头发,甲说:“你放手,然后我再放手”。乙说:“你先放手,然后我再放手”,一直循环(不要模仿他们打架,危险动作)

public class MyThread09 {
    public static void main(String[] args) {
        //模拟死锁现象
         DeadLockDemo A = new DeadLockDemo(true);
         A.setName("A 线程");
         DeadLockDemo B = new DeadLockDemo(false);
         B.setName("B 线程");
         A.start();
         B.start();
    }
}
class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
    static Object o2 = new Object();
    boolean flag;
    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }
    @Override
    public void run() {
        //下面业务逻辑的分析
        // 1. 如果 flag 为 True, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        // 2. 如果线程 A 得不到 o2 对象锁,就会 Blocked(阻塞)
        // 3. 如果 flag 为 False, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
        // 4. 如果线程 B 得不到 o1 对象锁,就会 Blocked(阻塞)
        if (flag) {
                synchronized (o1) {//对象互斥锁, 下面就是同步代码
                    System.out.println(Thread.currentThread().getName() + " 进入 1");
                synchronized (o2) { // 这里获得 li 对象的监视权
                         System.out.println(Thread.currentThread().getName() + " 进入 2");
                    }
                }
        } else {
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + " 进入 3");
                synchronized (o1) { // 这里获得 li 对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入 4");
                    }
                }
            }
    }
}


8.释放锁

释放锁的操作:
1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return.
案例:没有正常的完事,经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait(方法,当前线程暂停,井释
放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
不会释放锁的操作:
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep(). Thread.yield0方
法暂停当前线程的执行,不会释放锁
案例上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的suspend0方法将该线程挂起,
该线程不会释放锁。
提示:应尽量避免使用suspend0和resume()来控制线程,方法不再推荐使用
(sleep原地休息,不会将资源交出去,wait将资源交出去然后等待)

9.应用举例

9.1、例1

//(1)在main方法中启动两个线程(线程协调问题)
//(2)第1个线程循环随机打印100以内的整数
//(3)直到第2个线程从键盘读取了"Q"命令。
思考:
        我看到网上有些人说使用守护线程,但是俺觉得要考虑如果还有其他线程或者main里面还有任务呢是否第二个线程还可以控制第一个线程呢?
//(1)在main方法中启动两个线程
//(2)第1个线程循环随机打印100以内的整数
//(3)直到第2个线程从键盘读取了"Q"命令。
import java.util.Scanner;

public class MyThread10 {
    public static void main(String[] args) {
          Th1 th1=new Th1();
          Thread thread=new Thread(th1);
         // thread.setDaemon(true);
         // 考虑一个问题如果main线程还有任务那么会有什么效果
          thread.start();
          Th2 th2=new Th2(th1);
          Thread thread1=new Thread(th2);
          thread1.start();
//          Th3 th3=new Th3();
//          Thread thread1=new Thread(th3);
//          thread1.start();
    }
}

class Th1 implements Runnable{
    private Boolean flag=true;
    @Override
    public void run() {
        while (flag){
            try {
                int ranNum=(int) (Math.random()*(100-1))+1;
                System.out.println("100以内的随机数:"+ranNum);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void setFlag(Boolean flag){
       this.flag=flag;
    }

}
class Th2 implements Runnable{
    //方法1:直接改变Th1里面的数据使其停止循环打印
    private Th1 th1;
    private Boolean loop=true;
    private Scanner input = new Scanner(System.in);
    public Th2(Th1 th1) {
        this.th1 = th1;
    }
    @Override
    public void run() {
        while (loop){
            System.out.println("请输入Q,停止线程th1:");
            char key = input.next().toUpperCase().charAt(0);
            if(key=='Q'){
                th1.setFlag(false);
                System.out.println("线程停止。。。。。");
                loop=false;
            }
        }
    }
}
class Th3 implements Runnable{
//通过守护线程然后控制线程th1,守护线程是等所有的结束之后才会停
//(考虑一个问题如果main线程还有任务那么会有什么效果,所以要考虑周全)
//方法1:直接改变Th1里面的数据使其停止循环打印
    private Boolean loop=true;
    private Scanner input = new Scanner(System.in);
    @Override
    public void run() {
        while (loop){
            System.out.println("请输入Q,停止线程th1:");
            char key = input.next().toUpperCase().charAt(0);
            if(key=='Q'){
                System.out.println("线程停止。。。。。");
                loop=false;
            }
        }
    }
}

9.2、例2

/*
(1)有2个用户分别从同一个卡上取钱(总额: 10000)
(2)每次都取1000,当余额不足时,就不能取款了
(3)不能出现超取现象=》线程同步问题
 */
public class MyThread11 {
    public static void main(String[] args) {
          Th01 th01=new Th01();
          Thread thread1=new Thread(th01);
          thread1.setName("甲");
          thread1.start();
          Thread thread2=new Thread(th01);
          thread2.setName("乙");
          thread2.start();
          Thread thread3=new Thread(th01);
          thread3.setName("丙");
          thread3.start();
    }
}
class Th01 implements Runnable{
    private Boolean flag=true;
    private int money=100000;
    public void quqian(){
        synchronized (this){
            try {
                if(money < 1000){
                    System.out.println("余额不足..");
                    flag=false;
                    return;//要结束这个方法
                }
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money-=1000;
            System.out.println(Thread.currentThread().getName()+"取1000元钱,银行还剩"+money+"元");
        }
    }
    @Override
    public void run() {
        while (flag){
            quqian();
        }
    }
}

看韩顺平老师在哔哩哔哩线程专题教学视频写的笔记,

posted on   嘉&澍  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示