多线程

创建线程的三种方式:

1. 继承Thread类,并且重写run方法

Thread类中的run方法不是抽象方法,Thread类也不抽象类

示例如下:

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(1);
    }
}
public class Ch01 {
    public static void main(String[] args) {
        System.out.println(4);
        MyThred myThred = new MyThred();
        myThred.start();

        //普通的对象调用方法
//        myThred.run();

        System.out.println(2);
        System.out.println(3);
        //输出 4 2 3 1
    }
}

当MyThread继承了Thread类之后,它就是一个独立的线程,要让线程启动,调用线程的start方法

2. 实现Runnable接口

示例如下:

class MyThread2 implements Runnable{
    @Override
    public void run() {
        System.out.println(2);
    }
}
public class Ch02 {
    public static void main(String[] args) {
        System.out.println(1);
        MyThread2 myThread2 = new MyThread2();

        Thread t = new Thread(myThread2);
        t.start();

        System.out.println(3);
        System.out.println(4);
        //输出 1 3 4 2
    }
}

当MyThread2成为了Runnable接口的实现类之后,它就是一个独立的线程,要让线程启动,调用线程的start方法

Runnable接口如何调用start方法?

  • 使用Thread类的带参构造器引入这个Runnable接口的实现类的对象,然后通过实例化对象调用

3. 实现Callable接口

Callable接口支持泛型的返回值
示例如下:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread3 implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(2);
        return "call方法的返回值";
    }
}

public class Ch04 {
    public static void main(String[] args) {
        System.out.println(1);

        FutureTask<String> futureTask = new FutureTask<>(new MyThread3());

        new Thread(futureTask).start();
        System.out.println(3);
        System.out.println(4);
        //输出1 3 4 2
    }
}

当MyThread3成为了Callable接口的实现类之后,它就是一个独立的线程,要让线程启动,调用线程的start方法

Callable接口如何调用start方法?

  • 将Callable接口实现类的对象引入到FutureTask构造器中,创建FutureTask的对象
  • 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。

使用箭头函数实现创建线程的第一种方法

箭头函数又称Lambda表达式

示例如下:

public class Ch03 {
    public static void main(String[] args) {
        System.out.println(1);
        new Thread(() -> System.out.println(2)).start();
        try {
        //暂停当前线程(主方法)的执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(3);
        System.out.println(4);
        //输出1 2 3 4
    }
}

守护线程

****Java提供了两件线程守护程序线程和用户线程

  1. 守护线程,是指在程序运行时 在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。
  2. 守护线程为用户线程提供服务,仅在用户线程运行时才需要
  3. 守护线程对后台支持任务非常有用
    如:垃圾回收器。 大多数JVM线程都是守护线程
  4. 任何线程继承创建它的线程守护进程状态。由于主线程是用户线程,因此在main方法内启动的任何线程默认都是守护线程

****守护线程和用户线程的区别用户线程和守护线程几乎一样,唯一的不同之处在于如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了

线程的生命周期

NEW:已创建,线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:阻塞。因为某些原因不能立即执行需要挂起等待
WAITING:无限期等待。Object类,如果没有唤醒,则一直等待
TIMED_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程的状态,线程已经执行完毕

等待和阻塞的区别

  • 阻塞是因为外部原因,需要等待
  • 等待一般都是主动调用方法,发起主动的等待。等待时还可以传入参数来确定等待时间

join方法

join方法阻塞调用此方法的线程,直到线程t完成,此线程再继续

示例如下:

  1. 不使用join()方法的情况:
public static void main(String[] args){
    System.out.println("MainThread run start.");
  
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();

    System.out.println("MainThread join before");
    System.out.println("MainThread run finished.");
}

运行结果如下:

MainThread run start. 
threadA run start. 
MainThread join before 
MainThread run finished. 
threadA run finished.
  1. 使用了join()方法的情况:
public static void main(String[] args){
    System.out.println("MainThread run start.");

    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();

    System.out.println("MainThread join before");
    try {
        threadA.join();    //调用join()
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("MainThread run finished.");
}

运行结果如下:

MainThread run start. 
threadA run start. 
MainThread join before 
threadA run finished. 
MainThread run finished.

对子线程threadA使用了join()方法之后,我们发现主线程会等待子线程执行完成之后才往后执行

CPU多核缓存结构

  1. 物理内存:硬盘内存(固态硬盘,尽量不要选择混合硬盘)
  2. CPU缓存为了提高程序运行的性能,现在CPU在很多方面对程序进行优化
  3. CPU处理速度最快,内存次之,硬盘速度最低
  4. 在CPU处理内存数据时,如果内存运行速度太慢,就会拖累CPU的速度
  5. 为了解决这样的问题,CPU设计了多级缓存策略
  6. CPU分为三级缓存,每个CPU都有L1,L2缓存,但是L3缓存是多核公用的
  7. CPU查找数据时,CPU->L1->L2->L3->内存->硬盘
  8. 进一步优化,CPU每次读取一个数据,读取的时与它相邻的64个字节的数据【缓存行】

MESI协议

  1. 修改态,此缓存被动过,内容与主内存中不同,为此缓存专有
  2. 专有态,此缓存与主内存一致,但是其他CPU没有
  3. 共享态,此缓存与主内存一致,其他的缓存也有
  4. 无效态,此缓存无效,需要从主内存中重新读取

【指令重排】
四条指令,四个人在四张纸上写下【恭喜发财】各一个字

java内存模型-JMM
尽量做到硬件和操作系统之间到达一致的访问效果

线程的可见性

thread线程一直在高速读取缓存器中的isOver,不能感知主线程已经把isOber,这就是线程的可见性的问题

怎么解决?
volatile能够强制改变变量的读写直接在内存中操作

线程的争抢
解决线程争抢的问题最好的方法就是【加锁】

synchronized同步锁
当一个方法加上了synchronized修饰,这个方法就叫做同步方法

线程安全的实现方法

  1. 数据不可变,一切不可变的对象一定是线程安全的,对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施。比如final关键字修饰的基本数据类型,字符串只要一个不可变的对象被正确的创建出来,那外部的可见转态永远都不会改变
  2. 互斥同步。加锁 【悲观锁】
  3. 非阻塞同步【无锁编程】,自旋。我们会用cas来实现这种非阻塞同步
  4. 无同步方案。多个线程需要共享数据,但是这个鞋数据又可以在单独的线程中计算,而得出结果
    我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步了。把共享的数据拿过来,我用我的,你用你的,从而保证线程安全。ThreadLocal

斗地主发牌案例

public class Ticket implements Runnable {

    private static final Object lock = new Object();
    private static Integer count = 100;
    String name;

    public Ticket(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (Ticket.count > 0) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println(name + "出票一张,还剩:" + Ticket.count-- + "张");
            }
        }
    }

    public static void main(String[] args) {
        Thread one = new Thread(new Ticket("一号窗口"));
        Thread two = new Thread(new Ticket("二号窗口"));
        one.start();
        two.start();
    }
}