一、内部类及匿名内部类

成员变量可以被匿名内部类访问
final修饰的局部变量可以被匿名内部类访问,其他修饰类型不可以

  1. public interface Test01 {
  2. void eat();
  3. }
public class Test02 implements Test01{

    @Override
    public void eat() {
        System.out.println("重写Test01后的eat方法");
    }
}
public class Test03 {
    public static void main(String[] args) {
        //传统方式
        Test01 t=new Test02();
        t.eat();
        /**
         * 匿名内部类
         * {};实现了Test01接口
         */
        Test01 test01 = new Test01() {
            @Override
            public void eat() {
                System.out.println("重写Test01后的匿名内部类的方法");
            }
        };
        test01.eat();
    }
}

image.png
image.png
注意:
直接new接口时必须重写接口的方法。虽然直观上看是直接new的Person。但是实际是创建了Person的内部类,而不能说接口可以实例化。

二、使用匿名内部类的场景

因为匿名内部类的特性,只能在实例化的地方使用。所以匿名内部类主要应用场景:
1. 只提供了接口或抽象类。必须自己编写子类,且之类就用一次。
2. 事件回调
具体情况使用场景:
1. Java 线程
2. AWT事件
3. Android开发中

三、Swing案例—龟兔赛跑

Swing案例是为了说明匿名内部类的事件回调写法

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

public class GTRun {
    static boolean isWin=false;
    public static void main(String[] args) {
        //创建窗口,指定标题
        JFrame jFrame = new JFrame("龟兔赛跑");
        //设置窗口大小
        jFrame.setSize(500,400);
        //窗口,有默认的布局,居中-----清除默认的布局
        jFrame.setLayout(null);

        /**
         * 创建兔子的图标,并添加到标签中,将图片的标签放到窗口中
         */
        //创建兔子图标
        ImageIcon tzimageIcon = new ImageIcon("D:\\ASXT\\javaSE02\\Thread\\tuzi.jpg");
        //并添加到标签中
        JLabel tzjlabel = new JLabel(tzimageIcon);
        tzjlabel.setBounds(0,100,50,50);
        //将图片的标签放到窗口中
        jFrame.add(tzjlabel);
        /**
         * 创建乌龟的图标,并添加到标签中,将图片标签放到窗口中
         */
        //创建乌龟图标
        ImageIcon wgimageIcon=new ImageIcon("D:\\ASXT\\javaSE02\\Thread\\wugui.jpg");
        //并添加到标签中
        JLabel wgjLabel = new JLabel(wgimageIcon);
        wgjLabel.setBounds(0,150,50,50);
        //将图片的标签放到窗口中
        jFrame.add(wgjLabel);
        /**
         * 龟兔同时开跑
         * 龟:线程
         * 1.乌龟每次移动0-5像素,移动后随机休眠0--500毫秒,到达窗口右侧停止
         * 兔:线程
         * 2.兔子每次移动0-10像素,移动后随机休眠0--1000毫秒,到达窗口右侧停止
         */
        Thread tzThread=new Thread(()->{
            try {
                while (true){
                    //获取兔子当前的位置
                    int x=tzjlabel.getX();
                    int i=new Random().nextInt(11);
                    tzjlabel.setBounds(x+i,100,50,50);
                    //终点
                    if (x>=350){
                        /**
                         * isWin:false
                         * !isWin:true
                         * 如果兔子到达终点时,还没有人到达重点
                         * isWin-true
                         * 如果兔子到达终点时,有人到达终点
                         */
                        if (!isWin){
                            isWin=true;
                            JOptionPane.showMessageDialog(null,"兔子胜利","龟兔赛跑结果",JOptionPane.ERROR_MESSAGE);
                        }
                        break;//跳出当前循环
                    }
                    Thread.sleep(new Random().nextInt(11)*100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread wgThread=new Thread(()->{
            try {
                while (true){
                    //获取乌龟当前的位置
                    int x=wgjLabel.getX();
                    int i=new Random().nextInt(6);
                    wgjLabel.setBounds(x+i,200,50,50);
                    //终点
                    if (x>=350){
                        if (!isWin){
                            isWin=true;
                            JOptionPane.showMessageDialog(null,"乌龟胜利","龟兔赛跑结果",JOptionPane.ERROR_MESSAGE);
                        }
                        break;//跳出当前循环
                    }
                    Thread.sleep(new Random().nextInt(6)*100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        /**
         * 添加按钮
         */
        JButton jButton=new JButton("开始");
        jButton.setBounds(200,0,100,50);
        jFrame.add(jButton);
        /**
         * 事件:
         * 用户和程序之间的交互行为,如:鼠标单机 鼠标双击 按键盘
         * 事件回调:自动去调用某一些方法
         * 事件监听:监听触发了哪些事件
         */
        jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                //点击开始按钮开启兔子和乌龟的赛跑线程
                tzThread.start();
                wgThread.start();
            }
        });
        //窗口关闭,程序也关闭
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //窗口展示
        jFrame.setVisible(true);
    }
}

四、线程介绍

1. 进程

进程:系统中正在运行的一个应用程序。当在系统中启动一个程序后,系统就会为该程序分配一个进程。

2. 线程

线程是操作系统中的概念。
线程是操作系统运行调度的最小单位。一个进程中至少会有一个线程。一个线程就是程序代码的顺序执行的过程,代码逐行执行,执行时下面代码必须等待上面代码执行完成。
在java中主方法对应的就是程序的主线程。当启动应用程序运行主方法就是运行在线程中。一个类中只能存在一个主方法,所以只能有一个主线程。

3. 线程的休眠

在一个线程中的代码都是顺序执行的,上面的代码执行完成才会执行下面的代码。这也叫做线程的阻塞性。
使用输出语句可以感受线程的顺序执行,但是不能感觉到阻塞性。为了能够更清除明白线程的阻塞性,可以通过Thread.sleep();方法感受
Thread中sleep方法中
1. 实现功能:哪个线程调用sleep()方法, 就让哪个线程休眠多少毫秒。给人的感觉是执行 Thread.sleep(n); 时花费了n毫秒, 实际为该线程休眠了n毫秒
(注:1000毫秒=1秒)
2. native关键字表示方法的实现调用了另一 种编程语言的代码。具体说明底层使用C语言实现的。
3. 因为方法是static的,所以可以直接使用类名调用。
4. 参数是休眠多少毫秒。

4. 多线程

操作系统内部支持多进程,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。
多线程:一个进程中包含多个线程
多线程的调度, 执行等都是由硬件CPU负责的
利用多线程,让程序具备了多任务处理能力。
我们可以在主线程中创建多个线程,这些线程称为子线程
子线程不会阻塞主线程的执行,子线程中代码可以和主线程中代码同时执行。

5. 并发与并行

并发:一个处理器同时处理多个任务,同一时间间隔处理(时间片轮转算法)
并行:多个处理器或者多核任务处理器同时处理多个任务,同时处理

6. 应用程序、进程、线程的关系

一个应用程序对应一个进程
一个进程可以包含多个线程
所以:一个程序关闭了,在系统中的进程就没了,程序中的线程也就结束了。

五、线程的创建

image.png

1. Thread类的概念

java.lang.Thread类代表线程,任何线程对象都是Thread类的实例
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

2. 创建方式

在java中创建线程有三种实现方式。
1.继承Thread类,重写run方法:创建线程后,调用start()方法开启线程, 自动的调用 重写之后Thread的run方法
2.实现runnable接口,重写run方法:创建线程后,调用start(),首先调用Thread类中的run方法,然后调用Runnable接口实现类中的run方
3.实现Callable接口,结合Future
使用Futuretask类当作线程对象和Callable实现类的桥梁
Futuretask实现了Runnable接口,实现了run方法,
在run方法实现类中调用了Callable接口实现类中的call()方法,
* Futuretask中还封装了返回值,通过get()获取返回值

3.实现方式

3.1 继承Thread类

public class Thread01Imp extends  Thread{
    @Override
    public void run() {
        System.out.println("子线程"+Thread.currentThread().getName());
    }
}
//继承Thread类,重写run方法
public class Thread01 {
    public static void main(String[] args) {
        //使用Thread无参构造方法创建子线程,开启一个子线程
        Thread thread=new Thread01Imp();
        //线程执行
        thread.start();
        /**
         * 匿名内部类
         *继承Thread,重写run方法
         */
        Thread thread1 = new Thread("自定义") {
            @Override
            public void run() {
                System.out.println("自定义的线程执行了"+Thread.currentThread().getName());
            }
        };
        //线程执行
        thread1.start();
        //获取当前执行的线程
        System.out.println(Thread.currentThread().getName());
        System.out.println("主线程执行了");
    }
}

3.2 实现Runnable接口

public class Thread02Imp implements Runnable{
    @Override
    public void run() {
        System.out.println("子线程"+Thread.currentThread().getName());
    }
}
public class Thread02 {
    public static void main(String[] args) {
        /**
         * 自定义Runable实现类,实现Runable中的run方法
         */
        Thread02Imp thread02Imp=new Thread02Imp();
        Thread thread=new Thread(thread02Imp);
        thread.start();
        /**
         * 使用匿名内部类,创建Runable接口的实现类,实现run方法
         */
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类实现runnable接口" + Thread.currentThread().getName());
            }
        };
        Thread thread1=new Thread(runnable);
        thread1.start();
        //简便写法
        new Thread(runnable).start();

        //最简便的写法--lambda表达式
        new Thread(()->{
            System.out.println("实现runnable接口" + Thread.currentThread().getName());
        }).start();

        System.out.println(Thread.currentThread().getName()+"主线程执行了");

    }
}

3.3 实现Callable接口

public class CallableImpl implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("调用了callable中的call 子线程");
        return 7;
    }
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 实现callable接口,实现call方法
 *  Futuretask类当作线程对象和callable实现类的桥梁
 *   futuretask实现了Runnable接口,实现了Run方法
 *   在Run方法实现类中调用了callable接口实现类中的call()方法
 *   Futuretask中还封装了返回值,通过get()获取返回值
 */
public class Thread03 {
    public static void main(String[] args) {
        try {
            CallableImpl callable=new CallableImpl();
            //创建和Thread交互的桥梁
            //FutureTask是Runable实现类
            FutureTask<Integer> integerfutureTask=new FutureTask<>(callable);
            /**
             * 创建线程()需要传递Runable的实现类
             * Futuretask是Runnable接口的实现类
             */
            Thread thread=new Thread(integerfutureTask);
            thread.start();

            Integer integer = integerfutureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

image.png
image.png
image.png
image.png

六、JMM的三大特征

1. 什么是JMM

内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象描述,不同架构下的物理机拥有不一样的内存模型,Java虚拟机(jvm)是一个实现了跨平台的虚拟系统,因此它也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)
2.png

2. 线程/工作内存/主内存 三者的关系

  1. 所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问
    2. 每个线程都有一个独立的工作内存,用于存储线程私有的数据
    3. 线程对变量的操作(读取赋值等)必须在工作内存中进行。(线程安全问题的根本原因)
    a. 首先要将变量从主内存拷贝到线程的工作内存中, 不允许直接操作主内存中的变量
    b. 每个线程操作自己工作内存中的变量副本,操作完成后再将变量写回主内存
    c. 多个线程对一个共享变量进行修改时,都是对自己工作内存中的副本进行操作,相互不可见, 所以主内存最后得到的结果是不可预知的
    d. 因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成

    3. JMM的三个特征

    3.1 可见性

    在Java中,不同线程拥有各自的私有工作内存,当线程需要读取或修改某个变量时,不能直接去操作主内存中变量,而是需要将这个变量读取到该线程的工作内存中(变量副本),当该线程修改其变量副本后,其它线程并不能立刻读取到新值,需要将修改后的值刷新到主内存中,其它线程才能从主内存读取到修改后的值,
    原因: 工作内存和主内存存在同步延迟
    解决方案:
    1. volatile: 可以保证内存可见性
    ① 规定线程每次修改变量副本后立刻同步到主内存中,用于保证其它线程可以看到自己对变量的修改
    ② 规定线程每次使用变量前,先从主内存中刷新最新的值到工作内存,用于保证能看见其它线程对变量修改的最新值
    2. synchronize: 同步锁
    注意:
    1. volilate可以保证可见性,但是假设多线程同时在做a++,在线程A修改共享变量从0到1的同时,线程B已经正在使用值为0的变量,所以这时候可见性已经无法发挥作用,线程B将其修改为1,所以最后结果是1而不是2

    3.2 有序性

    在单线程程序里代码逐行执行,但是在多线程并发时,程序的执行就有可能出现乱序,在多线程执行程序时, 为了提高性能,编译器和处理器常常会自动对指令做重排序,目的是进行相关的优化, 指令重排序使得代码在多线程执行时可能会出现一些问题
    例如:
    源代码:
    int a = 1;
    int b =2;
    a = 3;
    b = 4;
    优化后:
    int a = 1;
    a = 3;
    int b =2;
    b = 4;
    解决方案:
    1. volatile: 在指令序列中插入内存屏障(一组处理器指令)防止指令重排序,有序性
    2. synchronize: 同步锁

    3.3 原子性

    原子性指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个线程的操作一旦开始,就不会被其他线程干扰, 只能当前线程执行完, 其他线程才可以去执行
    解决方案: 1. synchronize: 同步锁

    3.4 总结

  2. volatile
    变量在每次被线程访问时,都会立即从主内存中读该变量的值,而当该变量发生变化时,又会将最新的值立即刷新到主内存,不同的线程总是能够看到该变量的最新值, 除特殊情况(如: 有线程A和线程B, A和B都从主内存中将变量a=0, 加载到了自己的工作内存中, A将a=0变为a=1, 立即刷新主内存, 此时B正在操作a=0变为a=1, 立即刷新也没有任何的作用, 最后的正确结果为a=2, 但是结果却为a=1),
    可以解决指令重排的问题, 但是不能解决原子性
    2. synchronized同步锁
    可以解决可见性, 有序性, 原子性