day01(内部类、匿名对象、多线程)

静态代码块

static {},可初始化操作

匿名对象

只能被调用一次,可作为参数传递,较灵活

内部类

成员内部类 -- 写在成员位置

成员内部类访问外部类成员的特点:直接访问 包括私有的。

格式:外部类.内部类 变量名 = new 外部类().new 内部类();

外部类访问内部类的特点: 需要先创建内部类对象,再进行访问,可以直接访问,包括私有的

局部内部类 -- 写在局部位置

访问特点 :直接访问 包括私有的

public class Outer {
    public static void main(String[] args) {
        //局部内部类 不需要加修饰符
        String name = "张三";
        class Inner{
            private void function(){
                System.out.println(name);
            }
        }
        new Inner().function();
    }
}
 

public class Outer {
    public Outer() {
    }

    public static void main(String[] args) {
        final String name = "张三";
        class Inner {
            Inner() {
            }

            public void function() {
                System.out.println(name);
            }
        }

        (new Inner()).function();
    }
}

问题:

final String name = "张三"中为什么要加这一个final?

注意:在JDK1.8之前 需要手动添加final 1.8之后 自动添加

1、name为局部变量,存在栈中,方法运行完变量消失。inner为对象引用存在堆中,当不经常使用的时候会被GC回收。

2、当  inner对象调用方法正在使用name局部变量的时候就消失,不合常理。

3、将定义一个被final修饰的常量存在常量池中(堆中),生命周期比较长久,在jvm退出的时候才消失。

匿名内部类

格式:new 类(包含抽象类)/接口(){重写方法;};

创建的是当前new的这个类或者接口的子类对象,只不过这个子类没有名字


public interface Animal {

    void eat();
    //1.8新特性 定义默认方法
    default void show(){
        System.out.println("你好....");
    }
}

public class Student implements Animal {
    @Override
    public void eat() {
        System.out.println("chi ---");
    }
}

public class Outer {
    public static void main(String[] args) {
        Animal animal = new Student();
        animal.eat();
        System.out.println("-----------------");
        //匿名内部类
        //现在创建的对象是谁?名字我们不知道,但是我们知道 创建的是 Animal的实现类的对象
        //多态
        Animal a = new Animal(){

            @Override
            public void eat() {
                System.out.println("chibuchi---");
            }
        };
        a.eat();
        System.out.println("---------------------------");
        //创建Student子类对象
        Student stu = new Student(){

        };
        stu.eat();
        System.out.println("-------------------------");
        //将这个整体看做成 匿名对象
        //调用方法 只能调用一次
        //做为参数传递 传递前不能做其他的操作
        new Animal(){

            @Override
            public void eat() {
                System.out.println("哈哈");
            }
        }.eat();
        System.out.println("-----------------------");
        //Student student = new Student();
        show(new Animal(){

            @Override
            public void eat() {
                System.out.println("我跟student是兄弟....");
            }
        });
    }

    public static void show(Animal a){
        a.eat();
    }
}

多线程

进程 -- 在内存中正在运行的软件

线程 -- 进程中最小的执行单元

没有线程哪来的进程 -- 存在进程 最少得有一个线程 no code

多线程 -- 多个线程并发的执行

并发:在同一个小的时间段内 线程可以同时执行

线程创建方式一

创建一个类继承Thread

  1. 重写Thread的run方法

  2. 创建子类对象

  3. 启动线程

public class PrimeThread extends Thread{
    //2.重写run方法
    @Override
    public void run() {
        //线程中需要执行的 功能(代码)
        //fori itar iter
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

public class ThreadDemo {
    //main方法本身也是一个线程  由JVM开启的线程
    public static void main(String[] args) {
        //3.创建thread的子类对象
        PrimeThread t1 = new PrimeThread();
        //4.启动线程
        //t1.run(); 调用方法
        t1.start();//启动线程

        for (int i = 0; i < 10; i++) {
            System.out.println("main:" + i);
        }
    }
}

线程创建方式二

  1. 创建一个类实现Runnable接口

  2. 实现run方法

  3. 创建Runnable实现类的对象

  4. 将 Runnable实现类的对象作为参数 创建Thread对象

  5. 启动线程 start方法

public class PrimeRunnable implements Runnable {
    //2. 实现run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

public class PrimeDemo {
    public static void main(String[] args) {
        //3. 创建Runnable实现类的对象
        PrimeRunnable primeRunnable = new PrimeRunnable();
        //4. 将 Runnable实现类的对象作为参数 创建Thread对象
        Thread t1 = new Thread(primeRunnable);
        Thread t2 = new Thread(primeRunnable);
        //5. 启动线程 start方法
        t1.start();
        t2.start();
    }
}

第一种继承方式 继承 只支持单继承 耦合度比较高

第二种方式 实现 多实现 耦合性比较低

采用第二种较好。

线程的安全性问题

当多个线程 共享同一个资源的时候 就有可能会发生线程安全性的问题!!

1、重复票

2、0张票

3、负数票

使用锁解决线程安全性问题

加锁

同步代码块

​ synchronized( 需要一个任意的对象(锁))

 {

​ 代码块中放操作共享数据的代码。

​ }

public class TicketsThread extends Thread {
    //100 张票 三个窗口售票
    //每一个窗口都有100张票 还是  三个窗口共同出售 100张票
    //被static修饰的属性的特点 只有一份  被所有的对象共享
    private static int tickets = 100;

    //锁对象的条件  被所有的线程共享  是一个惟一的
    private static Student stu = new Student();

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

        synchronized (stu) {

            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + tickets + "张票");
                tickets--;
            } else {
                break;
            }
        }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        TicketsThread t1 = new TicketsThread();
        t1.setName("小明");
        TicketsThread t2 = new TicketsThread();
        t2.setName("小红");
        TicketsThread t3 = new TicketsThread();
        t3.setName("小兰");
        t1.start();
        t2.start();
        t3.start();
    }
}

线程安全的 效率低

之前 学习 集合 StringBuilder 线程不安全的 -- 效率高

Thread.currentThread().getName();获取当前线程对象的名称。

为什么要复写run方法? 

因为我们希望程序中的某段代码可以同时运行,提高程序的运行效率。就是start启动线程之后,JVM会自动的调用run方法。

为什么要调用start而不是run?

start方法 :调用系统资源,启动线程 ,开启一个独立的新的空间。并在新的空间中自动去运行run方法。

run方法:普通的方法 ,不能调用系统资源。

start方法和run方法的区别?

run:只是封装线程任务。

​start:先调用系统资源,在内存中开辟一个新的空间启动线程,再执行run方法。

线程的生命周期

新建状态:当我们使用new去创建线程对象的时候。

就绪状态:当我们调用start方法后,线程就有了获得CUP执行权的资格

运行状态:当线程获得CPU的使用权后就进入了运行状态。

注意:就绪状态与运行状态是可以相互转换的,当线程获得CPU使用权就进入运行状态

失去CPU使用权后重新回到就绪状态,等待CPU切到当前线程。

阻塞状态:在运行时期的线程调用了sleep方法或者在等待同步锁的时候就进入了阻塞状态。

当获取到同步锁,或者sleep时间到了的时候则又进入了就绪状态。

死亡状态:当run方法执行完毕或者发生了异常后,线程进入死亡状态。

线程 -- 并发执行

​ 线程不安全 -- 执行效率会高!!

​ 线程是不是越多越好? 线程越多 被CPU切到几率越低

产生一些安全性问题 -- 多个线程 争抢同一个资源

解决方案 -- 加锁 -- 效率会变

发布了22 篇原创文章 · 获赞 18 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39115649/article/details/99213409