2022-08-02 顾宇佳 学习笔记 多线程

创建线程(3种)

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

Thread类中的run方法不是抽象方法,Thread类也不是抽象类
java是单继承,所以只能继承Thread一个类,所以Thread类一般用来启动线程的
class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(2);
    }
}

public class Ch01 {

    public static void main(String[] args) {
        System.out.println(1);
        //
        MyThread myThread = new MyThread();
        // 当调用start方法启动一个线程时,会执行重写的run方法的代码
        // 调用的是start,执行的是run,为什么不直接调run
        myThread.start();
        // 普通的对象调方法
//        myThread.run();

        // 线程的优先级,概率问题!做不到百分百
        // 90会先跑主方法 10先跑mythread

        System.out.println(3);
        System.out.println(4);
    }
}class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(2);
    }
}

public class Ch01 {

    public static void main(String[] args) {
        System.out.println(1);
        //
        MyThread myThread = new MyThread();
        // 当调用start方法启动一个线程时,会执行重写的run方法的代码
        // 调用的是start,执行的是run,为什么不直接调run
        myThread.start();
        // 普通的对象调方法
//        myThread.run();

        // 线程的优先级,概率问题!做不到百分百
        // 90会先跑主方法 10先跑mythread

        System.out.println(3);
        System.out.println(4);
    }
}

2.实现Runnable接口,重写run()方法,并用Thread类来包装

接口是多实现,没有单继承的限制

实现Runnable接口是把run()方法写到接口中然后再用Thread类中的start()方法来启动线程

箭头函数(lambda表达式)
public class Ch03 {
    public static void main(String[] args) {
        /*箭头函数接口:抽象类重写方法*/
      new Thread(()-> System.out.println(2)).start();

      try {
          Thread.sleep(1000);
      }catch (InterruptedException e){
          e.printStackTrace();
      }
        System.out.println(3);
        System.out.println(4);
    }
}

3.实现Callable,重写call()方法,包装成FutureTask,然后再包装成Thread

有返回值,借助FutureTask类来判断线程是否执行完毕或取消

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

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

RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

FutureTask

public class FutureTask<V> implements RunnableFuture<V> {
	// 构造函数
	public FutureTask(Callable<V> callable);
	
	// 取消线程
	public boolean cancel(boolean mayInterruptIfRunning);
	// 判断线程
	public boolean isDone();
	// 获取线程执行结果
	public V get() throws InterruptedException, ExecutionException;
}

Callable 也是一种函数式接口

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

三种线程的比较 

Thread: 继承方式为单继承, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
Thread类是
实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现


 

守护线程
 java中提供两种线程
 1.用户线程
 2.守护程序线程
 守护线程为用户线程提供服务,仅在用户线程运行时才需要
 守护线程自己启动不了

 守护线程对于后台支持任务非常有用
 垃圾回收 大多数jvm线程都是守护线程

 创建守护线程

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

线程的生命周期

NEW:这个状态主要是线程未被start()调用执行
RUNNABLE;线程正在jvm中被执行,等待来自操作系统的调度
BLOCKED:阻塞,因为某些原因不能立即执行需要挂起等待
WAITING:无限期等待 object类 如果没有唤醒,就一直等、
TIME_WAITING:有限期等待,线程等待一个指定时间
TERMINATED:终止线程的状态,线程已经执行完毕
等待和阻塞概念相像
阻塞因为外部原因需要等待
等待是主动等待发起主动的等待 等待还可以传入参数确定等待
等待一般是主动调用方法

 

CPU多核缓存结构
 物理内存:硬盘内存。(固态硬盘,尽量不要选择混合硬盘)

 CPU缓存为了提高程序运行的性能,现在CPU在很多方面对程序进行优化。
 CPU处理速度最快,内存次之,硬盘速度最低。
在CPU处理内存数据时,如果内存运行速度太慢,就会拖累CPU的速度
 为了解决这样的问题,CPU设计了多级缓存策略。

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

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

 从CPU到内存,60-80纳秒
 从CPU到L3,15纳秒
 从CPU到L1,1纳秒
 寄存器,0.3纳秒

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

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

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

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

指令重排练习
public class Ch02 {

    private static int x = 0,y = 0;
    private static int a = 0,b = 0;
    private static int count = 0;

    private volatile static int NUM = 1;

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        for (;;) {
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            });
            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("一共执行了:" + count++ + "次");
            if(x == 0 && y ==0){
                long end = System.currentTimeMillis();
                System.out.println("耗时:" +(end - start) + "毫秒,(" + x + "," + y + ")");
                break;
            }
            a = 0;b = 0;x = 0;y = 0;
        }
    }
    /*
        我们发现测试结果中大部分感觉是正确的,(0,1)或(1,0),一个是线程1先执行,一个是线程2先执行。
        按道理来说,绝对不会出现(0,0),如果出现(0,0)代表存在指令重排,乱序执行。
        使用volatile关键字来保证一个变量在一次读写操作时,避免指令重排。
        我们在读写操作之前加入一条指令,当CPU碰到这条指令后必须等到前面的执行执行完成才能继续执行下一条指令。
        【内存屏障】。
     */
}

可见性

hread线程一直在高速读取缓存中的isOver,不能感知主线程已经把isOVer改成了true
 这就是线程的可见性的问题。
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 (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("二号窗口"));

        one.start();
        two.start();
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值