【韩老师零基础30天学会Java 15】线程,进程,创建线程两种方法,终止礼让插队守护优先级,线程的生命周期6或7种状态,Synchronizeld,死锁

1. 程序 进程 线程

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

进程
1.进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为
该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

2进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的
产生、存在和消亡的过程

●什么是线程
1.线程由进程创建的,是进程的一个实体2.一个进程可以拥有多个线程,如下图

相关概念

线程相关概念

其他相关概念
1.单线程:同一个时刻,只允许执行一个线程
2多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打
开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

3.并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简
单的说,单核cpu实现的多任务就是并发。

4.并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。

获取CPU数量

        Runtime runtime = Runtime.getRuntime();
        //获取当前电脑的cpu数量/核心数
        int cpuNums = runtime.availableProcessors();
        System.out.println("当前有cpu 个数=" + cpuNums);

创建线程的两种方式

在java中线程来使用有两种方法。

1.继承Thread类,重写run方法

2实现Runnable接口,重写run方法

public
class Thread implements Runnable { 

   @Override
   public void run() {
        if (target != null) {
            target.run();
        }
   }

}

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Terminal窗口输入:JConsole

继承Thread类

start()方法调用start0()方法后,该线程并不一定会立马款行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统—调度。

  • start0() 是 JVM来调用,
    • 根据操作系统 不一样,来不同实现。
      • 系统使用算法,考虑硬件资源:CPU,内存,IO
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {

        //创建Cat对象,可以当做线程使用
        Cat cat = new Cat();

        //老韩读源码
        /*
            (1)
            public synchronized void start() {
                start0();
            }
            (2)
            //start0() 是本地方法,是JVM调用, 底层是c/c++实现
            //真正实现多线程的效果, 是start0(), 而不是 run
            private native void start0();

         */

        cat.start();//启动线程-> 最终会执行cat的run方法

        //cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
        //说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        //这时 主线程和子线程是交替执行..
        System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main
        for(int i = 0; i < 30; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            Thread.sleep(1000);
        }

    }
}

//老韩说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */

class Cat extends Thread {

    int times = 0;
    @Override
    public void run() {//重写run方法,写上自己的业务逻辑

        while (true) {
            //该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”
            System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
            //让该线程休眠1秒 ctrl+alt+t
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 49) {
                break;//当times 到80, 退出while, 这时线程也就退出..
            }
        }
    }
}

重写 Runnable

  • 使用了 代理模式(静态代理)
  1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承
    Thread类方法来创建线程显然不可能了。

  2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创
    建线程

class Dog implements Runnable { //通过实现Runnable接口,开发线程

    int count = 0;

    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }
        }
    }
}
        Dog dog = new Dog();
        //dog.start(); 这里不能调用start
        //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();

静态代理

//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy

    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();
    }
}
        Tiger tiger = new Tiger();//实现了 Runnable
        ThreadProxy t = new ThreadProxy(tiger);
        t.start();

区别

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

  • 就是 T3 t= new T3(“h”) 假如T3实现了 Runnable
  • 可以多个 new Thread(t3).start();

买票超卖

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

        //测试
//        SellTicket01 s1 = new SellTicket01();
//        SellTicket01 s2 = new SellTicket01();
//        SellTicket01 s3 = new SellTicket01();
//
//        //这里我们会出现超卖..
//        s1.start();//启动售票线程
//        s2.start();//启动售票线程
//        s3.start();//启动售票线程


        System.out.println("===使用实现接口方式来售票=====");
        SellTicket02 sellTicket02 = new SellTicket02();

        new Thread(sellTicket02).start();//第1个线程-窗口
        new Thread(sellTicket02).start();//第2个线程-窗口
        new Thread(sellTicket02).start();//第3个线程-窗口


    }
}


//使用Thread方式

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 int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {
			//同上

        }
    }
}

2. 线程生命周期 和 方法

线程终止

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();

        //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
        //让t1 退出run方法,从而终止 t1线程 -> 通知方式

        //让主线程休眠 10 秒,再通知 t1线程退出
        System.out.println("main线程休眠10s...");
        Thread.sleep(10 * 1000);
        t1.setLoop(false);
    }
}

class T extends Thread {
    private int count = 0;
    //设置一个控制变量
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {

            try {
                Thread.sleep(50);// 让当前线程休眠50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中...." + (++count));
        }

    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

中断 睡眠 优先级

第一组方法

线程常用方法
常用方法第一组

  1. setName//设置线程名称,使之与参数name相同

  2. getName//返回该线程的名称

  3. start//使该线程开始执行;Java虚拟机底层调用该线程的start0方法

  4. run /调用线程对象run方法;

  5. setPriority 1/更改线程的优先级

  6. getPriority//获取线程的优先级

  7. sleep//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

  8. interrupt //中断线程

●注意事项和细节

  1. start底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程

  2. 线程优先级的范围

    1. 最大是10。Thread.MAX_PRIORITY
    2. 最小是1
    3. 正常为5
    	public final static int MIN_PRIORITY = 1;
    
        public final static int NORM_PRIORITY = 5;
    
        public final static int MAX_PRIORITY = 10;
    
  3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程`

  4. sleep:线程的静态方法,使当前线程休眠

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        //测试相关的方法
        T t = new T();
        t.setName("老韩");
        t.setPriority(Thread.MIN_PRIORITY);//1
        t.start();//启动子线程


        //主线程打印5 hi ,然后我就中断 子线程的休眠
        for(int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + i);
        }

        System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1

        t.interrupt();//当执行到这里,就会中断 t线程的休眠.
    }
}

class T extends Thread { //自定义的线程类
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                //获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "  吃包子~~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(20000);//20秒
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
                //InterruptedException 是捕获到一个中断异常.
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}

礼让 插队

常用方法第二组

  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
    1. 小弟想让老大吃包子,结果包子特别多,就不礼让了。
  2. join: 线程的插队。插队的线程一旦插队成功,则肯定
    先执行完插入的线程所有的任务
    案例:创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1秒,输出hi,输出20次.要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续,
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {

        T2 t2 = new T2();
        t2.start();

        for(int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程(小弟) 吃了 " + i  + " 包子");
            if(i == 5) {
                System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
                //join, 线程插队
                //t2.join();// 这里相当于让t2 线程先执行完毕。谁要插队,调用谁
                
                Thread.yield();//礼让,不一定成功..
                System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
            }

        }
    }
}

class T2 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);//休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大) 吃了 " + i +  " 包子");
        }
    }
}

用户和守护线程

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

  • 3.常见的守护线程:垃圾回收机制
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread d = new MyDaemonThread();
        //如果我们希望当main线程结束后,子线程自动结束
        //,只需将子线程设为守护线程即可
        d.setDaemon(true);//要先设置,在启动
        d.start();

        for( int i = 1; i <= 10; i++) {//main线程
            System.out.println("宝强在辛苦的工作...");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread {
    public void run() {
        for (; ; ) {//无限循环
            try {
                Thread.sleep(1000);//休眠1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
        }
    }
}

生命周期 七种状态

6 或 7状态
public static enum Thread.stateextends Enum<Thread.state>

线程状态。线程可以处于以下状态之一:

NEW

  • 尚未启动的线程处于此状态。

RUNNABLE

  • 在Java虚拟机中执行的线程处于此状态。

BLOCKED

  • 被阻塞等待监视器锁定的线程处于此状态。

WAITING

  • 正在等待另一个线程执行特定动作的线程处于此状态。

TIMED_WAITING

  • 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

TERMINATED (terminated)

  • 已退出的线程处于此状态。

  • new 进入 NEW状态

  • 调用 start(),进入 **可运行状态 **RUNNABLE。如果看成是一个状态 就是6状态,如果看成2个,就是7状态。

    • 分为就绪 Ready (线程如果被调度选中,进入运行)
    • Running 在运行
  • terminated,终止状态。

  • 等待进入同步代码块的锁,进入 Blocked

  • 线程调用 wait()。t.join()。 LockSupport.park()。进入 Waiting

    • 使用 notify() 或 notifyAll() 或 LockSupport.unpark()
  • Thread.sleep(time)。o.wait(time)。t.join(time)。LockSupport.parkNanos()。LockSupport.parkUntil()

    • 进入 timed_waiting 。超时等待,如10秒后,进入 运行态(就绪 或 运行)
  • Running的时候 调用 Thread.yeild 进入 Ready 状态。线程被挂起(suspend()),也是进入 Ready

    • 礼让不成功,是因为 进入就绪态
代码模拟
Thread-0 状态 NEW
Thread-0 状态 RUNNABLE
hi 0
Thread-0 状态 TIMED_WAITING
Thread-0 状态 RUNNABLE
hi 1
Thread-0 状态 TIMED_WAITING
hi 2
Thread-0 状态 RUNNABLE
Thread-0 状态 TIMED_WAITING
hi 3
Thread-0 状态 RUNNABLE
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 4
Thread-0 状态 TIMED_WAITING
Thread-0 状态 RUNNABLE
Thread-0 状态 TERMINATED
public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + " 状态 " + t.getState());
        t.start();

        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + " 状态 " + t.getState());
            Thread.sleep(500);
        }

        System.out.println(t.getName() + " 状态 " + t.getState());

    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 5; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

Synchronizeld

线程同步机制
1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技
术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不
可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.

1.同步代码块
synchronized(对象){/得到对象的锁,才能操作同步代码
需要被同步代码;
}
2. synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
·
//需要被同步的代码
}

就好像某小伙伴上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可在使用厕所了

●基本介绍
1.Java在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只
能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,
表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低

  • 5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)

  • 6.同步方法(静态的)的锁为当前类本身。

注意事项

注意事项和细节
1.同步方法如果没有使用static修饰:默认锁对象为this

2.如果方法使用static修饰,默认锁对象:当前类.class

    //同步方法(静态的)的锁为当前类本身
    //老韩解读
    //1. 锁是加在 SellTicket03.class (当前类)
    public synchronized static void m1() {
    }

	//2. 如果在静态方法中,实现一个同步代码块.
    public static void m2() {
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
    }

3.实现的落地步骤:

  • 需要先分析上锁的代码
  • 选择同步代码块或同步方法
  • 要求多个线程的锁对象为同一个即可!
//使用Thread方式
// new SellTicket01().start()
// new SellTicket01().start(); 很明显,这样锁不住,不是同一个对象。
class SellTicket01 extends Thread {

//    public void m1() {
//        synchronized (this) {
//            System.out.println("hello");
//        }
//    }
}
使用
public class SellTicket {
    public static void main(String[] args) {

        //测试一把
        SellTicket03 sellTicket03 = new SellTicket03();
        
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
    }
}


//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;//控制run方法变量
    Object object = new Object();

    //老韩说明
    //1. public synchronized void sell() {} 就是一个同步方法。
    //在同一时刻, 只能有一个线程来执行sell方法
    //2. 这时锁在 this对象
    public /*synchronized*/ void sell() {

        //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
        synchronized (/*this*/ object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            //休眠50毫秒, 模拟
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
        }//sync结束
        
    }

    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一共同步方法
        }
    }
}

3. 死锁

  • 互斥
  • 不可剥夺
  • 循环等待
  • 请求与保持条件

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

妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业.

我的死锁

public class MyDeadLock {
    public static void main(String[] args) {
        A a = new A();
        a.start();
        B b = new B();
        b.start();
    }
}

class A extends Thread {
    @Override
    public void run() {
        synchronized (A.class) {
            System.out.println("A类:获取到了A锁");
            try {
                //睡1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B.class) {
                System.out.println("A类:获取到了B锁");
            }
        }
    }
}

class B extends Thread {
    @Override
    public void run() {
        synchronized (B.class) {
            System.out.println("B类:获取到了B锁");
            synchronized (A.class) {
                System.out.println("B类:获取到了A锁");
            }
        }
    }
}
A类:获取到了A锁
B类:获取到了B锁

老师写的

public class MyDeadLock {
    public static void main(String[] args) {
        Dead d = new Dead(true);
        d.start();

        Dead d2 = new Dead(false);
        d2.start();
    }
}

class Dead extends Thread {
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

    Dead(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (o1) {
                System.out.println("进入1");
                synchronized (o2) {
                    System.out.println("进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println("进入3");
                synchronized (o1) {
                    System.out.println("进入4");
                }
            }
        }
    }
}
public class DeadLock_ {
    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 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //3. 如果flag 为 F, 线程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");
                }
            }
        }
    }
}

释放锁

1.当前线程的同步方法、同步代码块执行结束案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return案例:没有正常的完事,经理叫他修改bug
不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释
放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

下面操作不会释放锁
1.线程执行同步代码块或同步方法时,程序调用**Thread.sleep()、Thread.yield()**方
法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会

  • yield 礼让,运行状态,切换成 就绪状态。

2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,
该线程不会释放锁。

  • 线程被挂起,会进入 就绪状态。

提示;应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

suspend
v.
暂停,中止;使暂停使用(或生效);使暂时停职(或停学等);延缓,推迟;悬,挂;悬浮,漂浮;<法律> (法官或法庭作出的)暂缓执行(判决);延留(和弦),延长(和音)

4. 线程控制题

B控制A线程结束

public class Homework01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);//一定要注意.
        a.start();
        b.start();
    }
}

//创建A线程类
class A extends Thread {
    private boolean loop = true;

    @Override
    public void run() {
        //输出1-100数字
        while (loop) {
            //new Random();
            //.nextInt(100)
            System.out.println((int)(Math.random() * 100 + 1));
            //休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("a线程退出...");

    }

    public void setLoop(boolean loop) {//可以修改loop变量
        this.loop = loop;
    }
}

//直到第2个线程从键盘读取了“Q”命令
class B extends Thread {
    private A a;
    private Scanner scanner = new Scanner(System.in);

    public B(A a) {//构造器中,直接传入A类对象
        this.a = a;
    }

    @Override
    public void run() {
        while (true) {
            //接收到用户的输入
            System.out.println("请输入你指令(Q)表示退出:");
            char key = scanner.next().toUpperCase().charAt(0);
            if(key == 'Q') { //char可以用用 ==
                //以通知的方式结束a线程
                a.setLoop(false);
                System.out.println("b线程退出.");
                break;
            }
        }
    }
}

取钱 加synchronized

public class Homework02 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.setName("t1");
        
        Thread thread2 = new Thread(t);
        thread2.setName("t2");
        
        thread1.start();
        thread2.start();
    }
}

//编程取款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2. 每次取出 1000
class T implements  Runnable {
    private int money = 10000;

    @Override
    public void run() {
        while (true) {

            //解读
            //1. 这里使用 synchronized 实现了线程同步
            //2. 当多个线程执行到这里时,就会去争夺 this对象锁
            //3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
            //4. 争夺不到this对象锁,就blocked ,准备继续争夺
            //5. this对象锁是非公平锁.

            synchronized (this) {//
                //判断余额是否够
                if (money < 1000) {
                    System.out.println("余额不足");
                    break;
                }

                money -= 1000;
                System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
            }

            //休眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值