• 匿名内部类创建线程对象;Thread类中的常用方法;多线程中的线程安全问题及处理方法(Java Day22)


    一,使用匿名内部类创建线程对象

    • 什么是匿名内部类:没有名字子类对象
    • ​ 本质:是一个对象
    • ​ 使用前提:
    1. 必须有继承或实现关系
    2. 一定有重写方法
    • ​ 格式new 父类或接口名 (){ 重写的方法  };
    •  多线程的两种实现方式正好满足匿名内部类的使用前提。意味着可以使用匿名内部类实现多线程

    代码示例

    public class Demo01 {
        public static void main(String[] args) {
            // 创建 一条新线程出来 创建 Thread类的对象
           // 继承 [匿名内部类],匿名内部类不用写子类直接继承 Thread() 父类
        // 线程的子类对象
    //线程一 new
    Thread() {
    //重写方法 @Override
    public void run() { // run 方法里面写线程的任务 for (int i = 0; i < 6; i++) { System.out.println("java 就是好"); } } }.start(); // 开启线程
            //线程二
            new Thread() {
    //重写方法 @Override
    public void run() { // run 方法里面写线程的任务 for (int i = 0; i < 6; i++) { System.out.println("越学越简单"); } } }.start(); // 开启线程 //实现接口 //这是一个线程任务对象 Runnable target = new Runnable() {
    //重写方法 @Override
    public void run() { System.out.println("风光无限好"); } }.start; //线程任务对象 Runnable target1 = new Runnable() {
    //重写方法 @Override
    public void run() { System.out.println("只是近黄昏"); } };
    //创建 Thread 的对象并绑定线程任务 Thread t1 = new Thread(target1); t1.start();
    //创建线程对象 Thread t2 = new Thread(new Runnable() { //重写方法 @Override public void run() { System.out.println("花花世界无限好"); } }); t2.start(); } }

    二,Thread类中的常用方法

    • 获取线程名称

    • getName()获取线程名称 【普通方法,被线程对象调用】线程是可以人为命名的
    • 设置线程名称

    • setName(String name) 给线程改名

    代码示例

    public class Demo02 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println(123);
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                System.out.println(123);
            }
        };
        Thread t3 = new Thread("夏天") {
            @Override
            public void run() {
                System.out.println(123);
            }
        };
        //获取线程的名称 [默认的名字]
        System.out.println(t1.getName());  //Thread-0
        System.out.println(t2.getName());  //Thread-1
        System.out.println(t3.getName());  //夏天
        
        //设置线程的名字 [修改线程的名字]
        t1.setName("花花");
        t2.setName("花花");
        System.out.println(t1.getName());  //花花
        System.out.println(t2.getName());  //花花, 可以重名
    }
    }
    • 结论:
    1. ​ 线程没有指定名称的时候,默认名称是以thread开头后面跟数字从0 开始依次递增1
    2. ​ 线程可以指定名称,可以构造指定也可以是set方法指定,可以重名
    • 获取当前线程对象

    1. 作用:可以在任意位置,获取当前正在执行这段代码的线程对象
    2. 静态方法:Thread Thread.currentThread();
    3. 返回当前正在执行这段代码的线程对象的引用
    4. 哪条线程在执行这段代码,返回的就是哪条线程的对象

    代码示例

    public class Demo03 {
        public static void main(String[] args) {
            // 获取 main 的线程对象
            Thread tname = Thread.currentThread();
            System.out.println(tname);// Thread[main,5,main]----main方法,线程优先级5,线程的名字是main
    
            Thread thread =new Thread() {
                public void run() {
                    Thread tname2 = Thread.currentThread();
                    System.out.println(tname2); //Thread[Thread-0,5,main]----线程的名称,优先级5,main方法
                    System.out.println(tname2.getName()); //Thread-0, 获取一条正在执行代码的线程的名称
                }
            }/*.start()*/;// 启动线程,如果不启动的化,run方法执行不了
            //调用 start() 得到的线程对象 说明启动了新的线程
            thread.run();  //调用run方法得到的main线程对象,意味着没有启动新的线程
        }
    }
    • 练习

    • 获取主方法所在的线程的名称
    • 获取垃圾回收线程的线程名称
    • 分析:
    1. 获取线程名称【 getName()被谁线程对象调用】
    2. ​ 相办法获取当前线程对象【 Thread.currentThread() 】,
    3. ​ 调用垃圾回收器:System.gc ( )
    4. ​ 如何确定垃圾回收线程工作?重写 Object 中的 finalize()

    代码示例

    public class Test01 {
        public static void main(String[] args) throws Throwable {
            // 获取主线程的线程对象
            Thread tname = Thread.currentThread();
            // 获取线程名称
            System.out.println(tname.getName());
            // 获取垃圾回收线程
            // 创建一个匿名对象
            // Test01 test01 = new Test01();// 把对象赋值给变量 gc方法调用的是回收不到对象gc线程就没有执行
             // 匿名对象  只能使用一次
             new Test01();// 匿名对象没名字,gc方法回收这个对象, finalize()就会执行,意味着gc的线程就开启执行了
             System.gc(); // 调用finalize()是垃圾线程的
             test01.finalize(); // 是main方法行为不是垃圾线程的行为
        }
      
        @Override
        public  void finalize() throws Throwable {
            // 监听垃圾回收线程的对象和名称
            Thread thread = Thread.currentThread();
            System.out.println(thread);
            System.out.println(thread.getName());
        }
    }
    • 线程休眠

    1. Thread.sleep(long time):使开启的线程休眠time常时间。以毫秒为单位,他是一个静态方法。

    代码示例

    public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        //  要主线程休息1 s,过了一秒之后才执行
        //Thread.sleep(1000);
        //System.out.println(123);
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);  //sleep方法的异常在 run方法中只能捕获处理
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("春夏秋冬");  //春夏秋冬
            }
        }.start();
    
        Thread.sleep(1000);
        System.out.println(123);//123
        //先执行春夏秋冬一秒后执行123
    }
    }
    • 注意事项:
    1. sleep 方法存在一个 InterruptedException 异常的。该异常在 main 方法中可以声明处理也可以捕获处理。
    2. 如果在 run 方法中,InterruptedException 异常只能捕获异常处理。
    • 守护线程

    • 概述:给普通线程执行创造执行环境的线程,守护普通线程可以安全的执行。一般守护线程看不到的,默默无闻做奉献的一个线程。比如:垃圾回收线程
    • 如果所有的普通线程都消失,守护线程就没有存在的必要。【普通线程消失,守护线程也会及时的关闭】
    • 方法:
    1. setDaemon(boolean flag):给线程设置成为守护线程
    2. isDaemon():判断该线程是否为守护线程

    代码示例

    public class Demo05 {
    public static void main(String[] args) {
        //验证垃圾回收线程是否为守护线程, 结果为true, 所以这个线程是守护线程
        new Demo05();
        System.gc();
        //创建一条新的线程
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println(123);
        }
        };
        System.out.println(t1.isDaemon());  //false,  t1不是守护线程
        
        //将t1转换为守护线程
        t1.setDaemon(true);
        System.out.println(t1.isDaemon()); //true, t1是守护线程
    }
    @Override
        protected void finalize() throws Throwable {
            boolean b = Thread.currentThread().isDaemon();
            System.out.println(b); //true
        }
    }
    • 线程优先级

    • jvm 对线程的执行采用的是抢占式调度,线程的执行顺序没法控制的,但是有的时候对于一些特殊的线程要求提前执行情况,想办法人为的控制一下执行顺序,给线程增加了优先级的概念。优先级数值越大优先级越靠前。但是优先级有范围的:1到10
    • setPriority(int newPriority) :更改线程的优先级。线程的默认优先级是5
    • thread类中有三个优先级的常量值:
    1. MAX_PRIORITY :线程可以具有的最高优先级。 10
    2. MIN_PRIORITY :线程可以具有的最低优先级。 1
    3. NORM_PRIORITY :分配给线程的默认优先级。 5

    代码示例

    public class Demo06 {
        public static void main(String[] args) {
            Thread t1 = Thread.currentThread();
            System.out.println(t1);// 显示优先级,Thread[main,5,main]默认是5
            t1.setPriority(10);
            System.out.println(t1);// Thread[main,10,main] 优先级由5变为了10
    
            for (int i = 0; i < 5; i++) {
                System.out.println("春暖花开");
            }
    
            // 创建一个新的线程
            new Thread() {
                @Override
                public void run() {
                    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);  //优先级为1
                    // 没有指定优先级,默认的是5
                    for (int i = 0; i < 50; i++) {
                        System.out.println("鸟语花香");
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);  //优先级为10
                    for (int i = 0; i < 200; i++) {
                        System.out.println("花花世界");
                    }
                }
            }.start();
        }
    }

    三,多线程中的线程安全问题

    • 问题描述

    • 两个线程操作同一个对象中的资源,执行的时候导致,showA方法没有执行完毕,就去执行showB方法,导致两个方法的结果交叉感染了。出现问题了。
    • 原因:同一个对象的资源被两个不同的线程执行使用,线程执行机制是抢占式调度,执行时jvm两个线程之间来回的切换,出现一个线程方法还没有执行完毕,就去执行另一个线程,导致两个线程的一部分内容出现交叉问题。
    • 多线程问题的产生就是多个线程使用同一个资源 产生的

    代码示例

    //定义一个测试类
    public class Person_Test {
        public static void main(String[] args) {
            // 创建了一个person对象
            Person person = new Person();
            // 创建线程对象
            new Thread() {
                public void run() {
                    person.showA();// 效果输出  我爱中国
                }
            }.start();
    new Thread() {
                public void run() {
                    person.showB();//  好大的家
                }
            }.start();
        }
    }

    //定义一个Person类
    public class Person {
        public void showA() {
            while(true) {
                System.out.print("我");
                System.out.print("爱");
                System.out.print("中");
                System.out.print("国");
                System.out.println();
            }
        }
    public void showB() {
            while(true) {
                System.out.print("好");
                System.out.print("大");
                System.out.print("的");
                System.out.print("家");
                System.out.println();
            }
        }
    } 
    • 解决方案:
    • ​ 分析:问题产生的直接原因,线程没有执行完jvm就跑路
    • ​ 思路:想办法要jvm把正在做的事做完再跑路 【排个队,把线程事干完再去干别的线程不然不让jvm走】
    • ​ 方案:给jvm干的事加约束【锁】锁机制解决安全问题
    • ​ 线程要干的事使用锁锁起来把jvm执行权留在自己线程内部【jvm要执行线程的时候得到这个锁】,执行完毕之后把锁还给jvm,jvm拿着锁去执行所有的线程【的锁要求是同一个锁】
    • 加锁的方式:有同步代码块、同步方法
    • 同步代码块

    • 作用:就是解决多线程安全问题的。
    • 使用格式:
      synchronized (锁对象) {会发生线程安全的代码}
    • 使用同步代码块之后的效果:

    代码示例//定义Person类

    public class Person {
        public void showA() {
            while(true) {
                // 同步代码块
                synchronized(this) {
                    System.out.print("我");
                    System.out.print("爱");
                    System.out.print("中");
                    System.out.print("国");
                    System.out.println();
                }
            }
        }
        public void showB() {
            while(true) {
                synchronized(this) {
                    System.out.print("好");
                    System.out.print("大");
                    System.out.print("的");
                    System.out.print("家");
                    System.out.println();
                }
            }
        }
    }

    //定义测试类
    public class Person_Test { public static void main(String[] args) { // 创建了一个person对象 Person person = new Person(); //Person person1 = new Person(); // 创建线程对象 new Thread() { public void run() { person.showA();// 效果输出 我爱中国 } }.start(); new Thread() { public void run() { person.showB();// 好大的家 } }.start(); } }
    • 同步代码块上锁:上在资源有可能产生问题的代码上
    • 同步方法

    • 把线程要执行的代码使用方法封装起来,然后我给方法上把锁,将来jvm想要执行这个方法,必须有这个方法对应锁。
    • 同步方法的格式:
        权限修饰符 synchronized 返回值类型 方法名称(参数列表) { 需要同步的方法体【多条线程需要共同执行的代码段}

    代码示例

    public class Person {
        public void showA() {
            while(true) {
                // 使用同步方法解决
                show();
            }
        }
        // 同步方法1
        public synchronized void show() {
            System.out.print("我");
            System.out.print("爱");
            System.out.print("中");
            System.out.print("国");
            System.out.println();
        }
    public void showB() {
            while(true) {
                // 同步方法
                print();
            }
        }
        public synchronized void print() {
            System.out.print("好");
            System.out.print("大");
            System.out.print("的");
            System.out.print("家");
            System.out.println();
        }
    }
    • 锁对象的说明

    • 同步代码块的锁对象是谁?
    1. ​ 同步代码块的锁对象没有确定前可以是任意引用数据类型对象,一旦确定下来所有的同步代码块的锁对象保证唯一【同一个对象】
    2. ​ 锁要唯一。不然解决不了安全问题。
    • 同步方法的锁对象是谁?
    1. ​ 普通同步方法:默认的锁对象是this【当前调用对象】使用的时候必须保证所有的同步方法的调用对象是同一个对象
    2. ​ 静态同步方法:默认的锁对象是 字节码文件对象【类名.class】
    • 使用注意事项:【保证锁对象唯一】
    • ​ 如果单一方式使用:
    1. ​ 单一同步代码块:需要同步代码块的所有的锁对象上下一致,保证唯一
    2. ​ 单一使用同步方法:需要保证所有的同步方法的调用对象始终是同一个对象
    • ​ 混合使用:
    1. 普通同步方法和静态同步方法不能够混用。【锁对象不唯一】
    2. 同步代码块和同步方法混用:保证同步代码块的锁对象和同步方法的锁对象保持一致
    • 总结一句话:锁对象的使用【唯一性】
    • 死锁

    1. A线程需要甲资源,同时拥有乙资源才能继续执行【甲乙资源合起来是锁资源】;B线程需要乙资源,同时拥有甲资源才能继续,两条线程都不肯释放自己拥有的资源,同时也需要对方的其他的资源时,就都无法进行运行。形成“死锁”现象。
    2. 代码表现:有了同步代码块的嵌套,就可能发生死锁。某条线程获取了外层的锁对象A,需要内层的锁对象B,等待;另外一条线程获取了外层的锁对象B,需要内层的锁对象A,等待。两条线程就会形成死锁。

    代码示例

    public class Demo07 {
        public static void main(String[] args) {
            new Thread() {
                @Override
                public void run() {
                    while(true) {
                        synchronized ("a") {
                            System.out.println(Thread.currentThread().getName()+"得到了a等待b");
                            synchronized ("b") {
                                System.out.println(Thread.currentThread().getName()+"得到了b可以继续执行了");
                            }
                        }
                    }
                }
            }.start();
    new Thread() {
                @Override
                public void run() {
                    while(true) {
                        synchronized ("b") {
                            System.out.println(Thread.currentThread().getName()+"得到了b等待a");
                            synchronized ("a") {
                                System.out.println(Thread.currentThread().getName()+"得到了a可以继续执行了");
                            }
                        }
                    }
                }
            }.start();
        }
    }
    • 线程安全火车票案例

    • 分析:多窗口卖票,票唯一的资源。每个窗口的动作相同。一个窗口看成一个线程,线程任务是一样。只需要重写一次run方法指定任务。选择接口实现。任务是干卖票。
    • ​ 卖票过程:
    1. 有票【固定数 变量来模拟票】
    2. 出票 【一次只能出一张 ,出一张票的总数少一张】
    3. 票数为0不卖了
    • 步骤:
    1. 定义一个变量 充当票以及票数
    2. 写循环循环里面开始卖票 票数减1【循环条件,卖票的条件】

    代码示例

    //定义一个类继承接口
    public class SellTicket implements Runnable{ int ticket = 100; int num = 0; @Override public void run() { while(true) { synchronized ("a") { // 票数为0 时不卖了 if (ticket == 0) { System.out.println("票已经卖完了"); break; }else if (ticket >0 && ticket <= 100) { try { // 出票 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"正在卖第"+(++num)+"张票剩余票数"+(--ticket)+""); } } } } }

    //定义测试类
    public class Ticket_Test { public static void main(String[] args) { SellTicket target = new SellTicket(); // 创建窗口 new Thread(target, "窗口一").start(); new Thread(target, "窗口二").start(); new Thread(target, "窗口三").start(); } }
    •  多线程练习
    • 按要求编写多线程应用程序,模拟多个人通过一个山洞:
    • 这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒;随机生成10个人,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。
    • 显示每次通过山洞的人的姓名 [格式:pn  n为人数的个数],和通过顺序
    • 分析:

      1.定义一个隧道类,实现Runnable接口:
      1.1 定义一个变量,用来记录通过隧道的人数;
      1.2 重写Runnable的run方法;
      1.3 定义一个同步方法,模拟每个人通过隧道需要5秒钟:
      1.3.1 子线程睡眠5秒钟,模拟每个人通过隧道需要5秒钟;
      1.3.2 改变通过的人次;
      1.3.3 打印线程名称及其通过隧道的顺序,模拟人通过隧道及其顺序;
      1.4 调用通过隧道的方法;
      2.定义一个测试类:
      2.1 在main方法中创建一个隧道类对象;
      2.2 在main方法中,循环创建10个子线程对象,通过构造方法把隧道对象
      和线程名(作为人的姓名)传递进去,并开启子线程;

    代码示例

    public class Tunnel implements Runnable {
        int count = 0;
        @Override
        public void run() {
            //调用通过隧道的方法
            try {
                crossPerson();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        //定义一个同步方法,模拟每个人通过隧道需要5秒钟
        public synchronized void crossPerson() throws InterruptedException {
            //子线程睡眠5秒钟,模拟每个人通过隧道需要5秒钟
            Thread.sleep(5000);
            //改变通过的人次
            count++;
            //打印线程名称及其通过隧道的顺序,模拟人通过隧道及其顺序
            System.out.println(Thread.currentThread().getName()+"正在通过山洞是第"+count+"个通过的");
        }
    }
    
    //定义测试类
    package zuoye;
    public class Tunnel_Test {
        public static void main(String[] args) {
            Tunnel tul = new Tunnel();
            //利用for 循环完成
            for (int i = 1; i < 10; i++) {
                Thread t = new Thread(tul,"p"+i);
                t.start();
            }
        }
    }
  • 相关阅读:
    JDBC-HikariCP
    11、JDBC-Druid
    JDBC-DBCP
    JDBC-C3P0
    第十七篇-使用RadioGroup实现单项选择
    第十六篇-使用CheckBox实现多项选择
    第一篇-ubuntu18.04访问共享文件夹
    第十五篇-EditText做简单的登录框
    第十四篇-ImageButton控制聚焦,单击,常态三种状态的显示背景
    第十三篇-通过Button设置文本背景颜色
  • 原文地址:https://www.cnblogs.com/nastu/p/12563382.html
Copyright © 2020-2023  润新知