• JAVA 进程线程详解


    线程和进程

    一、进程

    1. 进程是指运行中的程序,比如我们使用QQ,就启动该进程分配内存空间.
    2. 进程是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自升的产生,存在和消亡的过程

    二、线程

    1. 线程是由进程创建的,是进程的一个实体
    2. 一个进程可以拥有多个线程
      • 一个想线程还可以创建它的子线程

    三、其他概念

    1. 单线程:同时允许执行一个线程

    2. 多线程:同一个时刻,可以执行多个线程

      • 比如:QQ可以打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
    3. 并发:同一个时刻,多个任务交替执行,造成"貌似同时"的错觉,简单的说,单核CPU实现的多任务就是并发

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

      • 也可能出现:并行和并发,并存在的情况

    四、线程的基本使用

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

    1. 基础Thread类,重写run方法

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

    案例一

    ![image-20220327105831422](!

    )

    package com.hspedu.threaduse;
    
    /**
     * @author DL5O
     * @version 1.0
     * 通过继承Thread 类创建线程
     */
    
    public class Thread01 {
        public static void main(String[] args) throws InterruptedException {
            //创建Cat对象,可以当做线程使用
            Cat cat = new Cat();
            //启动线程,调用start的时候还调用run方法
            cat.start();//最终执行 -> cat的run方法
            //cat.run();
            //如果这样写,run方法就是一个普通的方法,
            // 是由主线程调用了,并没有真正的开线程,
            // 会阻塞在这里,执行完毕后才会继续执行下面的代码
    
    
            //说明:当main线程启动一个子线程thread-0,主线程不会阻塞,
            // 即不会等待cat.start执行完毕后再往下执行
            //主线程中如果后面还有代码的话,还会继续执行
            //这时我们的主线程和主线程 是交替执行的
            System.out.println("继续执行~~,"+Thread.currentThread().getName());
            for (int i = 0; i < 60; i++) {
                System.out.println("主线程 i=" + i);
                //休眠1秒
                Thread. sleep(1000);
            }
        }
    }
    
    //1.当一个类继承了Thread 类,该类就可以当做一个线程使用
    //2.我们会重写run方法,写上自己的业务逻辑
    //3.run方法在Thread也是实现了Runnable 接口的run方法
    class Cat extends Thread {
        int times = 0;
    
        @Override
        public void run() {//重写run方法,写上自己的业务逻辑
            while (true) {
                //每隔一秒钟,输出"喵喵,我是小猫咪"
                System.out.println("喵喵,我是小猫咪" + (++times)
                        + ",线程名称=" + Thread.currentThread().getName());
                //让线程休眠1秒钟
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if (times == 80) {
                    break;//当times 等于80就退出这个while循环,这时线程就退出了
                }
            }
        }
    }
    
    1. 当运行这个程序代码的时候,会创建一个进程并给他分配空间
    2. 会先执行main方法,开启一个main线程
    3. 当执行到car.start() 方法时会开启一个新的线程,这个线程是mian线程的子线程
      • 注意:
        • 这个car.start()是非阻塞的,即不会等到这个方法执行完后才继续往下执行
        • car.start()线程开启后,会去调用run方法!!
          • 但是如果直接写car.run(),这个时候则是用的主线程main线程去调用的,并没有开启新线程,执行玩这个方法后才会往下继续执行

    执行过程

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

    image-20220327113953690


    案例二

    实现runnable接口

    说明:

    1. java是单继承的,在某些情况下已经继承了某个父类,这时继承Thread类方法来创建线程,显示是不可能的
    2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnalb接口来创建线程

    package com.hspedu.threaduse;
    
    /**
     * @author DL5O
     * @version 1.0
     * 通过实现接口Runnable 来开发线程
     */
    public class Thread02 {
        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();
            System.out.println("哈哈哈哈");*/
            System.out.println("哈哈哈");
        }
    }
    
    class Animal{}
    
    class Tiger extends Animal implements Runnable{
    
        @Override
        public void run() {
            System.out.println("老虎嗷嗷叫...");
        }
    }
    
    //模拟了最简的Thread类
    class ThreadProxy implements Runnable{//可以当做ThreadProxy ,线程代理
    
        private Runnable target = null;//属性,类型是Runnable
    
        @Override
        public void run() {
            if(target != null){
                target.run();//进行动态绑定 运行类型是tiger,即实现了Runnable的类
            }
        }
    
        public ThreadProxy(Runnable target) {
            this.target = target;
        }
    
        public void start(){
            start0();//这个方法是真正实现多线程的方法
        }
    
        private void start0() {
            run();
        }
    
    }
    
    class Dog implements Runnable{
    
        int count = 0;
        @Override
        public void run() {//普通方法,并没有启动线程
            while(true){
                System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
                //休眠一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if(count == 10){
                    break;
                }
            }
    
        }
    }
    
    • 把实现了runnable接口的对象传入到thread类,进行静态代理,调用start方法时,会去调用start0方法,start0方法还会调用对应该类的run方法,run方法又会去调用传入的对象的run方法,进行动态绑定

    案例三

    package com.hspedu.threaduse;
    
    /**
     * @author DL5O
     * @version 1.0
     * main 线程启动两个子线程
     */
    public class Thread03 {
        public static void main(String[] args) {
            T1 t1 = new T1();
            T2 t2 = new T2();
            Thread thread1 = new Thread(t1);//创建t1线程,用于输出hello,world
            Thread thread2 = new Thread(t2);//创建t2线程,用于输出hi
    
            thread1.start();
            thread2.start();
    
        }
    }
    
    class T1 implements Runnable {
        int count = 0;
    
        @Override
        public void run() {
            while (true) {
                System.out.println("hello,world " + (++count) + " "+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if (count == 10) {
                    break;
                }
            }
        }
    }
    
    class T2 implements Runnable {
        int count = 0;
        @Override
        public void run() {
            while (true) {
                System.out.println("hi " + (++count) + " " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (count == 5) {
                    break;
                }
            }
        }
    }
    

    image-20220327154157276

    • 若还有线程没有执行完毕,那么主进程就不会退出

    五、继承Thread vs 实现Runnable有什么区别

    1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本
      质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
    2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了
      单继承的限制,建议使用Runnable
    package com.hspedu.ticket;
    
    /**
     * @author DL5O
     * @version 1.0
     * 使用多线程,模拟三个窗口同时售票
     * 总票数 100 张
     */
    public class SellTicket {
        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 sellTicket02 = new SellTicket02();
            Thread thread1 = new Thread(sellTicket02);
            Thread thread2 = new Thread(sellTicket02);
            Thread thread3 = new Thread(sellTicket02);
            thread1.start();//第一个线程-窗口
            thread2.start();//第二个线程-窗口
            thread3.start();//第三个线程-窗口
        }
    }
    
    
    //使用第一种继承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;
    
        @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));
            }
        }
    }
    
    • 会出现超卖,和票数对不上的情况,这时候程序就会存在很大的问题=> 引出互斥等概念

    六、线程的退出

    基本说明:

    1. 当线程完成任务后,会自动退出
    2. 还可以通过变量来控制run方法退出的方式停止线程,即通知方式
    package com.hspedu.exit_;
    
    /**
     * @author DL5O
     * @version 1.0
     */
    public class ThreadExitTest_ {
        public static void main(String[] args) throws InterruptedException {
            Test test = new Test();
            test.start();
    
            Thread.sleep(10*1000);
            test.setLoop(false);
        }
    }
    class Test extends Thread{
        private boolean loop = true;
        private int cnt = 0;
        @Override
        public void run() {
            while(loop){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Test 执行中" + (++cnt));
            }
            System.out.println("Test 运行结束");
        }
    
        public boolean isLoop() {
            return loop;
        }
    
        public void setLoop(boolean loop) {
            this.loop = loop;
        }
    
        public int getCnt() {
            return cnt;
        }
    
        public void setCnt(int cnt) {
            this.cnt = cnt;
        }
    }
    

    七、线程常用方法

    常用方法第一组

    1. setName:给线程设置名称
    2. getName:返回线程的名称
    3. start:使线程开始执行
    4. run:线程调用的run方法
    5. setPriority:设置线程的优先级
    6. getPriority:获取线程的优先级
    7. sleep:让正在执行的线程休眠几秒
    8. interrupt:中断线程

    1. start底层会创建新的线程,会调用run,run本身不会启动新线程,不会启动新线程
    2. 线程优先级的范围
    3. interrupt,中断线程,但并没有真正结束线程。所以一般用于中断正在休眠线程
    4. sleep:线程的静态方法,使当前线程休眠

    案例:

    ThreadMethod01

    package com.hspedu.method;
    
    /**
     * @author DL5O
     * @version 1.0
     */
    public class ThreadMethod01 {
        public static void main(String[] args) throws InterruptedException {
            T t = new T();
            t.setName("大龙");
            t.setPriority(Thread.MIN_PRIORITY);//设置最小的优先级
            t.start();//启动子线程
    
            //主线程打印5 个hi, 然后我就中断 子线程的休眠
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("hi " + (i+1));
            }
    
            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 + 1));
    
                }
                try {
                    System.out.println("大龙休息中~~");
                    Thread.sleep(20 * 1000);
                } catch (InterruptedException e) {
                    //InterruptedException 是捕获到一个终端异常
                    System.out.println(Thread.currentThread().getName() + "被 interrupt了");
                }
            }
        }
    }
    

    常用方法第二组

    注意:

    • 在某一个线程后使用了类名.join方法,那么会cpu会全部去执行 这个插队的线程,并且当这个线程全部执行完后,才会继续执行原来的线程
    package com.hspedu.method;
    
    /**
     * @author DL5O
     * @version 1.0
     */
    public class ThreadMethod02 {
        public static void main(String[] args) throws InterruptedException {
            int count = 0;
            Test test = new Test();
            test.start();
            while (true){
                System.out.println("hello " + (++count));
                Thread.sleep(1000);
                if(count == 5){
                    System.out.println("让test 线程先执行");
    //                test.join();//线程插队一定会成功
                    Thread.yield();//线程礼让
    
                    //让test 线程先执行,执行后可以看到,当执行join后,主线程就不再执行,
                    // 当这个test线程执行完毕后,主线程才继续执行
                }
                if(count == 20){
                    break;
                }
            }
    
    
        }
    }
    
    class Test extends Thread{
        private int count = 0;
        @Override
        public void run() {
            while(true){
                System.out.println("hi " + (++count));
                try {
                    Thread.sleep( 1000);//休眠1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (count == 20){
                    break;
                }
            }
    
        }
    }
    

    课堂练习

    package com.hspedu.method;
    
    /**
     * @author DL5O
     * @version 1.0
     */
    public class ThreadMethodExercise {
        public static void main(String[] args) throws InterruptedException {
            T01 t01 = new T01();
            for (int i = 1; i <= 10; i++) {
                System.out.println("hi " + i);
                if (i == 5) {
                    Thread thread = new Thread(t01);
                    thread.start();
                    thread.join();
                    System.out.println("子线程结束...");
                }
                Thread.sleep(1000);
            }
            System.out.println("主线程结束...");
        }
    }
    
    class T01 implements Runnable{
    
        @Override
        public void run() {
    
            for (int i = 1; i <= 10 ; i++) {
                System.out.println("hello " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    八、用户线程和守护线程

    1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
    2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
    3. 常见的守护线程:垃圾回收机制
    package com.hspedu.method;
    
    /**
     * @author DL5O
     * @version 1.0
     * 守护线程的设置
     */
    public class ThreadMethod03 {
        public static void main(String[] args) throws InterruptedException {
            MyDaemonThread myDaemonThread = new MyDaemonThread();
            Test01 test01 = new Test01();
            myDaemonThread.setDaemon(true);
            //如果我们希望当main线程结束后,子线程可以自动退出
            //只需将子线程设为守护线程即可
            myDaemonThread.start();
            test01.start();
    
            for (int i = 1; i <= 10; i++) {
                System.out.println("我是主线程 " + i);
                Thread.sleep(1000);
            }
    
        }
    }
    
    class MyDaemonThread extends Thread {
        @Override
        public void run() {
            for (int i = 1; ; i++ ) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是MyDaemonThread 守护线程 " + i);
            }
        }
    }
    
    class Test01 extends Thread {
        @Override
        public void run() {
            for (int i = 1; i <= 15 ; i++ ) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是Test01 用户线程 " + i);
            }
        }
    }
    

    总结:

    • 当所有的用户线程退出后,守护线程就会自动结束
    • 设置守护线程的方式:对象名.setDaemon(true)
    • 如果不进行设置,线程默认为用户线程

    九、 线程的生命周期

    线程状态转换图:


    九、线程同步机制

    关键字:Synchronized

    1. 在多线程编程,一些敏感数据不允许被多少个线程同时访问,此时就使用同步访问技术保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
    2. 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

    同步具体方法-Synchronized

    1. 同步代码块

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

      public synchronized void m(String name){
          //需要被同步的代码
      }
      
    3. 如何理解:

      • 就好像某个人要上厕所,上厕所前需要把门关上(上锁),完事之后再出来(解锁),那么其他小伙伴就可以在使用厕所了
    4. 使用synchronized解决售票

    package com.hspedu.syn;
    
    /**
     * @author DL5O
     * @version 1.0
     * 使用多线程,模拟三个窗口同时售票
     * 总票数 100 张
     */
    public class SellTicket {
        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 sellTicket02 = new SellTicket02();
            Thread thread1 = new Thread(sellTicket02);
            Thread thread2 = new Thread(sellTicket02);
            Thread thread3 = new Thread(sellTicket02);
            thread1.start();//第一个线程-窗口
            thread2.start();//第二个线程-窗口
            thread3.start();//第三个线程-窗口*/
    
            //测试
            SellTicket03 sellTicket03 = new SellTicket03();
            Thread thread1 = new Thread(sellTicket03);
            Thread thread2 = new Thread(sellTicket03);
            Thread thread3 = new Thread(sellTicket03);
            thread1.start();//第一个线程-窗口
            thread2.start();//第二个线程-窗口
            thread3.start();//第三个线程-窗口
        }
    }
    
    
    //实现接口的方式,使用synchronized 实现线程同步
    class SellTicket03 implements Runnable {
        private int ticketNum = 100;
        private boolean loop = true;
    
        public synchronized void sell() {
            //同步方法,在同一个时刻只能有一个线程来执行我们的run方法
            if (ticketNum <= 0) {
                loop = false;
                System.out.println("票已售空,售票结束..");
                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();
            }
        }
    }
    
    
    //使用第一种继承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;
    
        @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));
            }
        }
    }
    

    十、互斥锁

    基本介绍:

    1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

    2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

    3. 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问

    4. 同步的局限性:导致程序的执行效率要降低

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

      • 非静态的这个锁是加在当前对象的
    6. 同步方法(静态的)的锁为当前类本身。

      • 静态方法的是加载当前类的

    注意事项:

    1. 同步方法如果没有使用static修饰:默认锁对象是:this
    2. 如果方法使用了static修饰:默认是锁对象是:当前类.class
    3. 实现的落地步骤:
      • 需要先分析上锁的代码
      • 选择同步代码块或同步方法
      • 要求多个线程的锁对象为同一个即可!!!!
        • 即共享的资源上
        • 线程同步只会发生在共享同一个资源时
    package com.hspedu.syn;
    
    /**
     * @author DL5O
     * @version 1.0
     * 使用多线程,模拟三个窗口同时售票
     * 总票数 100 张
     */
    public class SellTicket {
        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 sellTicket02 = new SellTicket02();
            Thread thread1 = new Thread(sellTicket02);
            Thread thread2 = new Thread(sellTicket02);
            Thread thread3 = new Thread(sellTicket02);
            thread1.start();//第一个线程-窗口
            thread2.start();//第二个线程-窗口
            thread3.start();//第三个线程-窗口*/
    
            //测试
            SellTicket03 sellTicket03 = new SellTicket03();
            Thread thread1 = new Thread(sellTicket03);
            Thread thread2 = new Thread(sellTicket03);
            Thread thread3 = new Thread(sellTicket03);
            thread1.start();//第一个线程-窗口
            thread2.start();//第二个线程-窗口
            thread3.start();//第三个线程-窗口
        }
    }
    
    
    //实现接口的方式,使用synchronized 实现线程同步
    class SellTicket03 implements Runnable {
        private int ticketNum = 100;
        private boolean loop = true;
        Object obj =new Object();//都是同一个对象
    
        //同步方法(静态的) 的锁为当前类
        //1.他的锁是加载我们这个类上的 SellTicket03.Class 上
        //2.如果要在静态方法中实现一个同步代码块.
        //      synchronized (SellTicket03.class)
        public synchronized static void m1(){
    
        }
        public static void m2(){
            synchronized (SellTicket03.class){
                System.out.println("m2");
            }
        }
    
        //是在方法上加的锁
        //说明
        //1.public synchronized void sell()就是一个同步方法
        //2.这时锁是在this对象
        //3.也可以在代码块上写 synchronized,同步代码块,互斥锁还是在this对象
        public /*synchronized*/ void sell() {//同步方法,在同一个时刻只能有一个线程来执行我们的run方法
            synchronized (/*this*/obj){
                if (ticketNum <= 0) {
                    loop = false;
                    System.out.println("票已售空,售票结束..");
                    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();
            }
        }
    }
    
    
    //使用第一种继承thread的方式
    //new SellTicket01().start()
    //new SellTicket01().start() this只对当前的对象有效
    class SellTicket01 extends Thread {
        private static int ticketNum = 100;//让多个线程共享 ticketNum
    
        //锁一般用于共享的只有
        public void m1(){
            synchronized (this){
                System.out.println("hello");
            }
        }
    
        @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;
    
        @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));
            }
        }
    }
    

    十一、线程的死锁

    基本介绍:

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

    package com.hspedu.syn;
    
    /**
     * @author DL5O
     * @version 1.0
     * 模拟线程死锁
     */
    
    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 Object o2 = new Object();
        boolean flag;
    
        public DeadLockDemo(boolean flag){
            this.flag = flag;
        }
    
        @Override
        public void run() {
            //1.如果flag为T,线程就会先得到/持有 o1的对象锁,然后会尝试去得到o2的对先生
            //  当持有到 o2的对象锁时,才会继续往下执行
            //2.如果线程A 得不到o2的对象锁,就会Blocked
            //3.如果flag 为 false,线程会去得到o2的对象锁,
            //4.如果线程B 拿不到o1的对象锁,就会block
            if(flag){
                synchronized(o1){//对象互斥锁
                    System.out.println(Thread.currentThread().getName()+"进入1");
                    //拿到o1的锁之后会阻塞在这里,直到拿到o2的锁,
                    // 此时第二个线程thread2进入,拿到o2的锁,进入堵塞状态,因为一直拿不到o1的锁,
                    // o1又因为拿不到o2锁,释放不了,故成为了死锁
                    synchronized (o2){
                        System.out.println(Thread.currentThread().getName()+"进行2");
                    }
                }
            }else{
                synchronized (o2){
    
                    //第二个线程 o1的锁已经被拿了,
                    //这时,又想去拿o1的锁,但是o1在第36行阻塞到了,没有拿到o2的锁,没有释放,故这里也会被阻塞
                    //那么这时就会产生一种死锁的情况,要避免
                    System.out.println(Thread.currentThread().getName()+"进入3");
                    synchronized (o1){
                        System.out.println(Thread.currentThread().getName()+"进行4");
                    }
                }
            }
        }
    }
    
    

    十二、释放锁

    下面操作会释放锁

    1. 当线程的同步方法、同步代码块执行结束
    2. 当前线程在同步代码块、同步方法中遇到break、return
    3. 当前线程在同步代码块、同步方法中出现了未处理的errorException,导致异常结束
    4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,也会释放锁

    下面操作不会释放锁

    1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()Thread.yield()暂停当前线程的执行,不会释放锁

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

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


    作业

    package com.hspedu.homework;
    
    import java.util.Random;
    import java.util.Scanner;
    
    /**
     * @author DL5O
     * @version 1.0
     */
    public class Homework01 {
        public static void main(String[] args) {
            A a = new A();
            B b = new B(a);
            a.setName("A");
            b.setName("B");
    //        a.setDaemon(true);
            a.start();
            b.start();
    
        }
    }
    
    class A extends Thread {
        private int num;
        static boolean loop = true;
        private Random random;
    
        public A() {
            random = new Random();
        }
    
        public void showNum() {
            //nextInt(int bound)
            //返回伪随机的,均匀分布 int值介于0(含)和指定值(不包括),从该随机数生成器的序列绘制。
            num = random.nextInt(101);//生成0~100的随机数
            //num = (int)Math.random()*(100-0+1)+0;
            System.out.println("随机数:" + num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    
        @Override
        public void run() {
            while (loop){
                showNum();
            }
        }
    }
    
    //用来控制A线程退出的线程B
    class B extends Thread {
        private String str;
        private A a;
        Scanner scanner;
    
        public B(A a) {
            this.a = a;
            scanner = new Scanner(System.in);
        }
    
        @Override
        public void run() {
            synchronized (B.class) {
                while (true) {
                    str = scanner.next();
                    if (str.equals("Q")) {
                        a.loop = false;
                        break;
                    }
                }
            }
        }
    }
    

    package com.hspedu.homework;
    
    /**
     * @author DL5O
     * @version 1.0
     */
    public class Homework02 {
        public static void main(String[] args) {
            Withdraw withdraw = new Withdraw();
            Thread A = new Thread(withdraw);
            Thread B = new Thread(withdraw);
            A.setName("A");
            B.setName("B");
            A.start();
            B.start();
        }
    }
    
    class Withdraw  implements Runnable {
        private int money = 10000;
        private boolean loop = true;
    
        public void deal() {
    
            //1.这里使用了synchronized 实现了线程同步
            //2.当多个线程执行到这里的时候,就回去争夺 this对象锁
            //3.那个线程执行到 this对象锁,就执行这个代码块,执行玩后,会释放这个锁,准备继续争夺
            //4.争夺不到就阻塞到这里,就blocked
            //5.this 是一个非公平锁
    
            synchronized (/*Withdraw.class*/this){
                if (money <= 0) {
                    loop = false;
                    System.out.println("余额不足");
                    return;
                }
                money -= 1000;
                System.out.println(Thread.currentThread().getName() + "取走了1000,剩余" + money);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            while (loop) {
                deal();
            }
        }
    }
    
  • 相关阅读:
    selenium 常见操作,使用 pywin32库 进行上传操作
    selenium 常见操作,使用 js 操作-日期框及文本框
    selenium 常见操作,js操作-将元素滚动到页面可见区域
    selenium 常见操作,使用 Keys 类来进行键盘的按键操作
    oracle性能诊断sql
    浏览器是如何处理页面元素的Download?
    websphere启动报:Could not resolve placeholder 'hibernate.hbm2ddl.auto' in string value "${hibernate.hbm2ddl.auto}"
    websphere部署不能发布war文件,提示“配置库中已存在应用程序
    websphere gc策略调整
    oracle表结构表数据导入导出
  • 原文地址:https://www.cnblogs.com/DL50/p/16116849.html
Copyright © 2020-2023  润新知