学习心得🎀🎀

了解了线程和进程有什么区别,线程启动也是要调用方法,两个线程一起执行时,还会出现概率问题,还可能出现bug,可以用 volatile 可以避免冲突,还能具有可见性。需要理解一下这些东西

                                                                          心情😁😁

关于线程来讲,好多知识,可见性那里没太听懂,问了周围的同学,讲了两三遍,听明白了,感觉心情好了许多
 

 

 创建线程:

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

(1)继承Thread类,并且重写run方法。Thread类中的run方法不是抽象方法,Thread类也不是抽象类。MyThread当继承了Thread类之后,它就是一个独立的线程。

要让线程启动。调用线程的start方法。

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

(1)实现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);
//        start
        MyThread2 myThread2 = new MyThread2();
        // 如果想要让线程启动,必须调用Thread类中的start方法
        // 问题:实现了Runnable接口,找不到start方法了?


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


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

(2)使用箭头函数(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);
    }
}

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

 


 

守护线程:

Java中提供两种类型的线程

1.用户线程

2.守护程序线程

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

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

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

QQ,主程序就是用户线程。

创建守护线程

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

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

public class Ch05 extends Thread {

    @Override
    public void run() {
        super.run();
    }

    public static void main(String[] args) {

        Ch05 ch05 = new Ch05();
        // 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 {
            // 线程休眠1秒
            Thread.sleep(i);
            System.out.println("哈哈哈...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


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

关于插队,看看线程怎么执行,还有一些概率问题

public class Ch07 {


    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100);
                    System.out.println("这是线程1---->" + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100);
                    System.out.println("这是线程2---->" + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        t1.start();
        t2.start();


        try {
            // t1插队
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("----------------------");
        // 分割线出现的位置,join方法的本意阻塞主线程
    

 


 

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、无效态,此缓存无效,需要从主内存中重新读取

【指令重排】:

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

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

可见性:

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

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

窗口出票100张,两个线程不会出现同一个数字的票:

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

 

 


 


posted on 2022-08-02 19:41  骐琳  阅读(18)  评论(0编辑  收藏  举报

你点我就回上面去了ヾ(≧O≦)〃嗷~