20220802 第五小组 罗大禹 学习笔记

20220802 第五小组 罗大禹 学习笔记

Java 多线程的创建

学习重点

1.创建线程
2.线程安全的实现方法

学习内容

Java 多线程的创建

创建线程

在Java中,创建线程有三种方式。

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
1.继承Thread类

​ 继承Thread类,重写run方法

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

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

举例说明
// 外部类继承Thread类
class MyThread extends Thread {
	//重写run()方法
    @Override
    public void run() {
        System.out.println(2);
    }
}
public class Ch01 {
    // 线程是有优先级的,是概率问题,做不到百分百
    // main是一个线程,优先级更高,myThread也是一个线程,这是两个独立的线程
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println(1);
        // 当调用start方法启动一个线程时,会执行重写的run方法的代码
        // 调用的是start,执行的是run,为什么不直接调run??
        // 因为普通的对象调方法,不是启动线程
        System.out.println(3);
        System.out.println(4);
    }

}
2.实现Runnable接口

​ 实现Runnable接口,通过Thread构造器传入Runnable对象从而调用start()方法

举例说明:
// 外部类实现Runnable接口
class MyThread2 implements Runnable {
	// 重写run()方法
    @Override
    public void run() {
        System.out.println(2);
    }
}

public class Ch02 {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
        // 如果想要线程启动,必须调用Thread类中的start方法
        // 问题:实现了Runnable接口,调用不了Thread中的start方法,怎么办?
        // 通过Thread构造器传入Runnable对象,从而调用start()方法
        Thread t = new Thread(myThread2);
        t.start();
        System.out.println(1);
        System.out.println(3);
        System.out.println(4);
    }
}

使用箭头函数(λ表达式 lambda表达式)
public class Ch03 {

    public static void main(String[] args) {
        System.out.println(1);
        // 箭头函数接口、抽象类,重写方法
         new Thread(() -> System.out.println(2)).start();
        try {
            // 主方法(线程)休眠1000毫秒,有可能抛异常,因此要try...catch解决
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(3);
        System.out.println(4);
    }
}
3.实现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);
        // Callable -> FutureTask -> RunnableFuture -> Runnable -> Thread
        // 传入对象的方式
        FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
        // 箭头函数的方式
        FutureTask<String> futureTask = new FutureTask<>(() ->{
            System.out.println(2);
            return "我是call返回值";
        });
        
        // 调用start()方法
        new Thread(futureTask).start();
        System.out.println(3);
        System.out.println(4);

    }
}

守护线程

Java中提供两种类型的线程

  1. 用户线程
  2. 守护程序线程

守护线程为用户线程提供服务,仅在用户线程运行时才需要。

守护线程对于后台支持的任务非常有用。

垃圾回收,大多数JVM线程其实都是守护线程

创建守护线程

任何线程继承创建它的线程守护进程状态。由于主线程是用户线程,因此在main方法内启动的任何线程默认都是守护线程

举例说明

public class Ch05 extends Thread{
    @Override
    public void run() {
        super.run();
    }

    public static void main(String[] args) {
        Ch05 ch05 = new Ch05();
//        Thread thread = new Thread();

        // 让ch05为守护线程
        ch05.setDaemon(true);
        ch05.start();
    }
}

线程的生命周期

​ 从摇篮到坟墓

​ NEW:这个状态主要是线程未被start()方法调用执行

​ RUNNABLE(可运行 就绪):线程正在JVM中被执行,等待来自操作系统的调度

​ BLOCKED:阻塞,因为某些原因不能立即执行,需要挂起等待

​ WAITING:无限期等待。Object类。如果没有唤醒,则一直等待

​ TIMED_WAITING:有限期等待,线程等待一个指定的时间。

​ TERMINATED:终止线程的状态,线程已经执行完毕

​ 和阻塞两个概念有点像,阻塞因为外部原因,需要等待

​ 而等待一般是主动调用方法来发起主动的等待。等待还可以传入参数来确定等待时间

public class Ch06 {

    public static void sleep(int i) {
        try {
            // 让该线程等待i毫秒
            Thread.sleep(i);
            System.out.println("桀桀桀桀桀");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        sleep(3000);

    }
}

CPU多核缓存结构

物理内存:硬盘内存。(固态硬盘,尽量不要选择混合硬盘)

CPU缓存为了提高程序运行的性能,现在CPU在很多方面对程序进行优化。

CPU处理速度最快,内存次之,硬盘速度最低。

在CPU处理内存数据时,如果内存运行速度太慢,就会拖累CPU的速度

为了解决这样的问题,CPU设计了多级缓存策略。

CPU分为三级缓存:每个CPU都有L1,L2缓存,但是L3缓存是多核公用的。

CPU查找数据时,CPU->l1->l2->l3->内存->硬盘

从CPU到内存,60-80纳秒

从CPU到L3,15纳秒

从CPU到L2,3纳秒

从CPU到L1,1纳秒

寄存器,0.3纳秒

进一步优化,CPU每次读取一个数据,读取的时与它相邻的64个字节的数据。

【缓存行】。

英特尔提出了一个协议MESI协议

1、修改态,此缓存被动过,内容与主内存中不同,为此缓存专有

2、专有态,此缓存与主内存一致,但是其他CPU中没有

3、共享态,此缓存与主内存一致,其他的缓存也有

4、无效态,此缓存无效,需要从主内存中重新读取

指令重排

​ 在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型:

​ 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

​ 指令级并行的重排序。现代处理器采用了指令级并行技术,来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

​ 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

volatile关键字用来降低指令重排的概率

可见性

thread线程一直在高速读取缓存中的isOver,不能感知主线程已经把isOVer改成了true

这就是线程的可见性的问题。

怎么解决?

volatile能够强制改变变量的读写直接在内存中操作。
public class Ch03 {

    private volatile static boolean isOver = false;

    private static int number = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!isOver){

                }
                System.out.println(number);
            }
        });
        thread.start();
        Thread.sleep(1000);
        number = 50;
        // 已经改了,能退出循环了
        isOver = true;
    }

}

线程争抢

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

synchronized同步锁,线程同步

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

public class Ch04 {

    private volatile static int count = 0;

    public synchronized static void add() {
        count ++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("最后的结果是:" + count);
    }
}

线程安全的实现方法

(1)数据不可变。

​ 一切不可变的对象一定是线程安全的。

​ 对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施。

​ 比如final关键字修饰的基本数据类型,字符串。

​ 只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变。

(2)互斥同步。加锁。【悲观锁】

(3)非阻塞同步。【无锁编程】,自旋。我们会用cas来实现这种非阻塞同步。

(4)无同步方案。多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,

得出结果
我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步。把共享的数

据拿过来,我用我的,你用你的,从而保证线程安全。ThreadLocal

public class Ch05 {

    private final static ThreadLocal<Integer> number = new ThreadLocal<>();

    private static int count = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // t1内部自己用一个count
                number.set(count);
                for (int i = 0; i < 10; i++) {
                    number.set(++count);
                    System.out.println("t1-----" + number.get());
                }
            }
        });
        Thread t2= new Thread(new Runnable() {
            @Override
            public void run() {
                // t2内部自己用一个count
                number.set(count);
                for (int i = 0; i < 10; i++) {
                    number.set( ++count);
                    System.out.println("t2-----" + number.get());
                }
            }
        });
        t1.start();
        t2.start();
    }
}
买票例子
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 (Ticket.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("二号窗口"));
        Thread three = new Thread(new Ticket("三号窗口"));

        one.start();
        two.start();
        three.start();
    }
}
posted @   怂还嘴硬  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示