2022-8-2 第八组 曹雨 多线程

创建线程 3种方法:

在Java中,创建线程:

  • 继承Thread类,重写run方法。
  • Thread类,不是抽象类,Thread方法不是抽象方法
  • 当myThread类继承了线程以后,他已经不是一个普通的类了,他是一个独立的线程,要让线程启动。调用线程start方法

1、继承Thread类,重写run方法

  //重写run方法
  class MyThread extends Thread{
      @Override
    public void run() {
        System.out.println("重写的run方法");
      }
  }

  //调用start方法
  //我调用start,执行run,为啥不直接执行run?
  //因为如果直接调run,那就是普通的对象调方法,与继承线程类没有任何关系
   public static void main(String[] args) {
       MyThread myThread = new MyThread();
       myThread.start();
    }
  }

线程的优先级:

  • 主方法的优先级高于线程
  • 线程的优先级是一个概率问题

2、实现RUNNABLE接口:

我们通过看Thread类的源码可知,Thread类实现了Runnable接口。
我们是否可以通过让我们的类直接实现Runnable接口来实现调用start方法,来使得线程启动

    //让类实现Runnable接口
    class  Mythread2 implements Runnable{
     @Override
    public void run() {
    }
 }


public class Ch02 {
    public static void main(String[] args) {
        //start
        Mythread2 mythread2 = new Mythread2();
       //试图调用Thread类的start方法
       //实现Runnable接口时,却找不到start方法。    
         Thread t =new Thread (mythread2);
         t.start();
        }
    }

使用箭头函数:

    public static void main(String[] args) {
        System.out.println(1);
        //箭头函数接口,抽象类,重写方法
        Thread t = new Thread(
                () -> System.out.println(2)
        );
        t.start();
        //线程的休眠
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(3);
        System.out.println(4);
    }

3、Callable接口:

    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());
        new Thread(futureTask).start();
        System.out.println(3);
        System.out.println(4);

    }
}

守护线程:

  • 为用户线程提供服务,仅在用户线程运行时才需要
  • 守护线程对后台支持的应用非常有用
  • 创建守护线程:在主方法里启动是守护线程

线程的生命周期:

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

等待和阻塞两个概念有点像,阻塞因为外部原因,需要等待, 而等待一般是主动调用方法,发起主动的等待。等待还可以传入参数确定等待时间。

CPU的多核缓存结构:

CPU有物理内存,硬盘内存。

CPU的三级缓存:

每个CPU都有L1,L2,L3(多核公用)缓存。
在查找数据时:
CPU->L1->L2->L3->内存->硬盘

缓存行:

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

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

指令重排:

没有依赖关系的指令,被编译器重新调整顺序

线程安全:

定义:当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

解决线程争抢的方法:

1)数据不可变。
 一切不可变的对象一定是线程安全的。
 对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施。
 比如final关键字修饰的基本数据类型,字符串。
 只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变。




(2)互斥同步。加锁。【悲观锁】
    //“悲观”的认为,你一定会出“问题”。

      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);
  }
}


(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();
    }
}

总结

今天开始了多线程的学习,感觉概念确实比较多。听了一天感觉有些乱。晚自习的时候还需要去理解理解思想,并去掌握一下一些关键字的应用。

posted on   figh466  阅读(27)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示