其他分享
首页 > 其他分享> > 2022-08-02 第四组 王佳齐 学习笔记

2022-08-02 第四组 王佳齐 学习笔记

作者:互联网

 

 

线程是由进程创建的,是进程的一个实体,具体干活的人

一个进程可以有多个线程,线程不独立分配内存,而是共享进程的内存资源

线程可以共享cpu资源

进程更强调内存的资源分配

线程更强调计算的资源分配

线程之间相互隔离,更安全,同一进程中的线程不能互相修改数据

进程不能直接创建调度线程,cpu可以

 

1.创建线程

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

a.继承Thread类,并且重写run方法 【Thread类不是抽象类,run不是抽象方法】

package morning;
//Thread不是抽象类
class MyThread extends Thread{
    //重写的run方法
    @Override
    public void run() {
        System.out.println("2");
    }
    //myThread当继承Thread之后,他就是一个独立的线程
    //要让线程启动,调用线程的start方法
}


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


        //线程的优先级,概率问题,做不到100%
        //90%会优先跑主方法,10%会跑myThread
    }

 

 

2.实现Runnable接口1.0版本

package morning;
/*
* 实现Runnable接口
* 实现接口和继承都是is的关系*/
public class Ch02 implements Runnable{


    @Override
    public void run() {
        System.out.println(2);
    }
}
class Hc02{
    public static void main(String[] args) {
        System.out.println(1);
        MyThread myThread2 = new MyThread();
        //想要启动线程一定要调用Thread类的start方法
        //问题:实现了Runnable接口,找不到start方法
        Thread t = new Thread(myThread2);
        t.start();


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

 

3.实现Callable接口1.5版本

package morning;


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


/*
* 实现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);
    }

 

 

守护线程

Java中提供的两种类型的线程

1.用户线程

2.守护程序线程

 

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

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

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

QQ,主程序就是用户线程,聊天框是守护线程。

 

创建守护线程

任何线程继承创建它的线程守护线程状态,由于主线程是用户线程

因此在main方法里启动的任何线程默认是守护线程

 

线程的生命周期

很重要

线程的生命周期*

从出生到消亡

NEW 这个状态主要是线程未被start调用执行

RUNNABLE 线程正在JVM中被执行,等待来自操作系统的调用

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

WAITING 无限期等待。如果没有唤醒则一直等待

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

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

 

1.cpu多核缓存结构

物理内存,硬盘内存

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

cpu处理速度最快,内存次之,硬盘最慢

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

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

cpu分为三级缓存,每个cpu都有L1、L2缓存,但是L3缓存是多级公用的

cpu查找数据时,cpu->L1->L2->L3->内存->硬盘

从cpu到内存,60-80nm

从cpu到L3,15nm

从cpu到L2,3nm

从cpu到L1,1nm

从cpu到寄存器,0.3nm

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

【缓存行】

【指令重排】

四条指令:让四个人在四张纸上写【恭喜发财】

Java内存模型JMM

尽量做到硬件和操作系统之间达到一致的访问效果

 

阻塞因为外部原因,需要等待

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

 

public class Ch01 {

    {
        int a = 1; // 1
        int b = 2; // 2
        int c = a + b; // 3

        /* 指令3不能被排到1和2前面
            但是1和2之间没有依赖关系,编辑器就可以重拍1和2。
            不会对程序的执行顺序产生干扰。
         */
    }

    public static void main(String[] args) {
        int [] nums = new int[]{1,2,3,4,5};
        for (int i = 0; i < nums.length; i++) {
            System.out.println(nums[i]);
        }
    }

}

 

 我们发现测试结果中大部分感觉是正确的,(0,1)或(1,0),一个是线程1先执行,一个是线程2

先执行。

按道理来说,绝对不会出现(0,0),如果出现(0,0)代表存在指令重排,乱序执行。

使用volatile关键字来保证一个变量在一次读写操作时,避免指令重排。

我们在读写操作之前加入一条指令,当CPU碰到这条指令后必须等到前面的执行执行完成才能继

续执行下一条指令。

【内存屏障】。

{

    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碰到这条指令后必须等到前面的执行执行完成才能继续执行下一条指令。
        【内存屏障】。
     */
}

 


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

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

 

package com.jsoft.afternoon;

/**
 * 可见性
 * 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();
    }
}

 

学习心得:今天学习了线程,感觉确实很难理解。

标签:02,王佳齐,Thread,System,线程,println,第四组,public,out
来源: https://www.cnblogs.com/yiboxh-10000/p/16545060.html