匿名内部类创建线程对象;Thread类中的常用方法;多线程中的线程安全问题及处理方法(Java Day22)
一,使用匿名内部类创建线程对象
- 什么是匿名内部类:没有名字子类对象
-  本质:是一个对象
-  使用前提:
- 必须有继承或实现关系
- 一定有重写方法
-  格式: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());  //花花, 可以重名
}
}
- 结论:
-  线程没有指定名称的时候,默认名称是以thread开头后面跟数字从0 开始依次递增1
-  线程可以指定名称,可以构造指定也可以是set方法指定,可以重名
- 
获取当前线程对象
- 作用:可以在任意位置,获取当前正在执行这段代码的线程对象
- 静态方法:Thread Thread.currentThread();
- 返回当前正在执行这段代码的线程对象的引用
- 哪条线程在执行这段代码,返回的就是哪条线程的对象
代码示例
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线程对象,意味着没有启动新的线程
    }
}
- 
练习
- 获取主方法所在的线程的名称
- 获取垃圾回收线程的线程名称
- 分析:
- 获取线程名称【 getName()被谁线程对象调用】
-  相办法获取当前线程对象【 Thread.currentThread() 】,
-  调用垃圾回收器:System.gc ( )
-  如何确定垃圾回收线程工作?重写 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());
    }
}
- 
线程休眠
- 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
}
}
- 注意事项:
-  sleep 方法存在一个 InterruptedException 异常的。该异常在 main 方法中可以声明处理也可以捕获处理。
-  如果在 run 方法中,InterruptedException 异常只能捕获异常处理。
- 
守护线程
- 概述:给普通线程执行创造执行环境的线程,守护普通线程可以安全的执行。一般守护线程看不到的,默默无闻做奉献的一个线程。比如:垃圾回收线程
- 如果所有的普通线程都消失,守护线程就没有存在的必要。【普通线程消失,守护线程也会及时的关闭】
- 方法:
- setDaemon(boolean flag):给线程设置成为守护线程
- 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类中有三个优先级的常量值:
-  MAX_PRIORITY:线程可以具有的最高优先级。 10
-  MIN_PRIORITY:线程可以具有的最低优先级。 1
-  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();
    }
}
- 
锁对象的说明
- 同步代码块的锁对象是谁?
-  同步代码块的锁对象没有确定前可以是任意引用数据类型对象,一旦确定下来所有的同步代码块的锁对象保证唯一【同一个对象】
-  锁要唯一。不然解决不了安全问题。
- 同步方法的锁对象是谁?
-  普通同步方法:默认的锁对象是this【当前调用对象】使用的时候必须保证所有的同步方法的调用对象是同一个对象
-  静态同步方法:默认的锁对象是 字节码文件对象【类名.class】
- 使用注意事项:【保证锁对象唯一】
-  如果单一方式使用:
-  单一同步代码块:需要同步代码块的所有的锁对象上下一致,保证唯一
-  单一使用同步方法:需要保证所有的同步方法的调用对象始终是同一个对象
-  混合使用:
- 普通同步方法和静态同步方法不能够混用。【锁对象不唯一】
- 同步代码块和同步方法混用:保证同步代码块的锁对象和同步方法的锁对象保持一致
- 总结一句话:锁对象的使用【唯一性】
- 
死锁
- A线程需要甲资源,同时拥有乙资源才能继续执行【甲乙资源合起来是锁资源】;B线程需要乙资源,同时拥有甲资源才能继续,两条线程都不肯释放自己拥有的资源,同时也需要对方的其他的资源时,就都无法进行运行。形成“死锁”现象。
- 代码表现:有了同步代码块的嵌套,就可能发生死锁。某条线程获取了外层的锁对象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方法指定任务。选择接口实现。任务是干卖票。
-  卖票过程:
- 有票【固定数 变量来模拟票】
- 出票 【一次只能出一张 ,出一张票的总数少一张】
- 票数为0不卖了
- 步骤:
- 定义一个变量 充当票以及票数
- 写循环循环里面开始卖票 票数减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();
        }
    }
}
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号