• 2022-08-02~04 第四小组 修身课 学习笔记(every day)


    目录

    创建线程方法

    线程的常用方法

    用户线程和守护线程

    线程状态观测

    线程优先级

     Synchronized

    互斥锁

    死锁

    LockSupport工具类

    Lock接口的实现类ReentrantLock

      线程池

     总结


    并发:同一个时刻,多个任务交替执行,造成一种“貌似相同”的错觉,简单的说,单核cpu实现的多任务就是并发

    并行:同一个时刻,多个任务同时执行,多核cpu可以实现并行

    创建线程方法

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

    1. package com.jinjie;
    2. /*
    3. 实现线程的第一种方式
    4. 编写一个类,直接继承java.lang.Thread,重写run方法
    5. */
    6. public class Test07 {
    7. public static void main(String[] args) {
    8. //这里是main方法,这里的代码属于主线程,在主栈中运行。
    9. //新建一个分支线程对象
    10. MyThread myThread=new MyThread();
    11. //启动线程
    12. //start()方法作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
    13. //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了.线程就启动了
    14. //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
    15. //run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
    16. myThread.start();
    17. //这里的代码还是运行在主线程中
    18. for(int i=0;i<100;i++){
    19. System.out.println("主线程:"+i);
    20. }
    21. }
    22. }
    23. class MyThread extends Thread{
    24. @Override
    25. public void run() {
    26. //编写程序,这段程序运行在分支线程中(分支栈)
    27. for(int i=0;i<100;i++){
    28. System.out.println("分支线程:"+i);
    29. }
    30. }
    31. }

    2.实现Runnable接口,重写run方法

    1. /*
    2. 实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
    3. */
    4. public class Test08 {
    5. public static void main(String[] args) {
    6. MyRunnable m=new MyRunnable();
    7. Thread t=new Thread(m);
    8. t.start();
    9. for(int i=0;i<100;i++){
    10. System.out.println("主线程:"+i);
    11. }
    12. }
    13. }
    14. //这并不是一个线程,是一定可运行的类
    15. class MyRunnable implements Runnable{
    16. @Override
    17. public void run() {
    18. for(int i=0;i<100;i++){
    19. System.out.println("分支线程:"+i);
    20. }
    21. }
    22. }

    实现Callable接口

    1. package demo04;
    2. import org.apache.commons.io.FileUtils;
    3. import java.io.File;
    4. import java.io.IOException;
    5. import java.net.URL;
    6. import java.util.concurrent.*;
    7. public class TestCallable implements Callable {
    8. private String url;
    9. private String name;
    10. public TestCallable(String url,String name){
    11. this.url=url;
    12. this.name=name;
    13. }
    14. @Override
    15. public Boolean call() {
    16. WeDownloader01 weDownloader01=new WeDownloader01();
    17. weDownloader01.downloader(url,name);
    18. System.out.println(name);
    19. return true;
    20. }
    21. public static void main(String[] args) throws ExecutionException, InterruptedException {
    22. TestCallable testCallable = new TestCallable("https://img-blog.csdnimg.cn/2021101218320261.png","3.jpg");
    23. //创建执行服务:
    24. ExecutorService ser= Executors.newFixedThreadPool(1);
    25. //提交执行:
    26. Future result1=ser.submit( testCallable);
    27. //获取结果:
    28. boolean r1=result1.get();
    29. //关闭服务:
    30. // ser.shutdownNow();
    31. }
    32. }
    33. //WeDownloader downloader
    34. class WeDownloader01{
    35. public void downloader(String url,String name){
    36. try {
    37. FileUtils.copyURLToFile(new URL(url),new File(name));
    38. } catch (IOException e) {
    39. e.printStackTrace();
    40. }
    41. }
    42. }

     

    线程的常用方法

    setName 设置线程名称

    getName 返回该线程的名称

    start 线程开始执行

    run 调用线程对象的run方法

    currentThread 静态方法,获取正在执行的线程

    setPriority 更改线程的优先级

    getPriority 获取线程的优先级

    sleep 在指定的毫秒数内让当前的正在执行的线程休眠     释放资源,不释放锁

    interrupt 中断线程

    yield 线程的礼让,让出cpu,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功

    join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务

    Object类对段线程的支持
    wait(): 释放资源,释放锁
    wait(long timeout):当线程进入等待状态
    notify():唤醒正在等待的下一个线程
    notifyAll()唤醒正在等待的所有线程

     

    用户线程和守护线程

    用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

    守护线程:一般为工作线程服务的,当所有的用户线程结束,守护线程自动结束

    常见的守护线程:垃圾回收机制

    1. public class Ch04 {
    2. public static void main(String[] args) throws InterruptedException {
    3. MyDaemonThread myDaemonThread = new MyDaemonThread();
    4. Thread thread = new Thread(myDaemonThread);
    5. thread.setDaemon(true);//我们希望main线程结束后,子线程自动结束。只需要子线程设置为守护线程即可
    6. thread.start();
    7. for (int i = 0; i < 10; i++) {
    8. System.out.println("努力工作");
    9. Thread.sleep(1000);
    10. }
    11. }
    12. }
    13. class MyDaemonThread implements Runnable{
    14. @Override
    15. public void run() {
    16. for (; ; ) {
    17. try {
    18. Thread.sleep(2000);
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }
    22. System.out.println("快乐聊天");
    23. }
    24. }
    25. }

        


    线程状态观测

    Thread.State

    线程状态。线程可以处于以下状态之一:

    NEW

    尚未启动的线程处于此状态

    RUNNABLE

    在Java虚拟机中执行特定的线程处于此状态

    WAITING

    正在等待另一个线程 执行动作达到指定等待时间的线程处于此状态

    TIMED_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

    TERMINATED

    已退出的线程处于此状态

    线程优先级

    Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行

    线程的优先级用数字表示,范围从1~10

            Thread.MIN_PRIOITY=1;

            Thread.MAX_PRIORTY=10;

            Thread.NORM_PRIORITY=5;

    使用以下方式改变或获取优先级

            getPRiority(). setPriority(int xxx)

     


     Synchronized

    线程同步机制

    当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成    操作,其他线程才能对该内存地址进行操作


    互斥锁

    1.在java语言中,引入了对象互斥锁的概念,来保证数据操作的完整性

    2.每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问对象

    3.关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问

    4.同步的局限性:导致程序的执行效率要降低

    5.同步方法(非静态的)的锁可以是this,也可以是其他对象

    6.同步方法(静态的)的锁为当前类本身

    注意事项和细节

    同步方法如果没有static修饰,默认锁对象为this

    如果方法使用static修饰,默认锁对象为:当前类.class

    实现的落地步骤:

    需要先分析上锁的代码

    选择同步代码块或同步方法

    要求多个线程的锁为对象的同一个即可


    死锁

    多线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题

     

    1. package demo04;
    2. //死锁:多个线程互相抱着对方需要的资源,然后形成僵持
    3. public class DeadLock {
    4. public static void main(String[] args) {
    5. Makeup t1=new Makeup(0,"小红");
    6. Makeup t2=new Makeup(2,"小花");
    7. t1.start();
    8. t2.start();
    9. }
    10. }
    11. //口红
    12. class Lipstick{
    13. }
    14. //镜子
    15. class Mirror{
    16. }
    17. class Makeup extends Thread{
    18. //需要的资源只有一份,用static来保证只有一份
    19. static Lipstick lipstick=new Lipstick();
    20. static Mirror mirror=new Mirror();
    21. int choice;//选择
    22. String girlName;//使用化妆品的人
    23. public Makeup(int choice,String girlName){
    24. this.choice=choice;
    25. this.girlName=girlName;
    26. }
    27. //化妆,互相持有对方的锁,就是需要拿到对方的资源
    28. private void makeup() throws InterruptedException {
    29. if(choice==0){
    30. synchronized (lipstick){//获得口红的锁
    31. System.out.println(this.girlName+"获得口红的锁");
    32. Thread.sleep(1000);
    33. //一秒后想获得镜子
    34. synchronized (mirror){
    35. System.out.println(this.girlName+"获得镜子的锁");
    36. }
    37. }
    38. }else {
    39. synchronized (mirror){//获得镜子的锁
    40. System.out.println(this.girlName+"获得镜子的锁");
    41. Thread.sleep(2000);
    42. //两秒后想获得口红
    43. synchronized (lipstick){
    44. System.out.println(this.girlName+"获得口红的锁");
    45. }
    46. }
    47. }
    48. }
    49. @Override
    50. public void run() {
    51. try {
    52. makeup();
    53. } catch (InterruptedException e) {
    54. e.printStackTrace();
    55. }
    56. }
    57. }

    LockSupport工具类


    线程阻塞的工具类。,所有的方法都是静态方法,可以让线程在任意位置阻塞,
    阻塞之后也有唤醒的方法。
    park:停车。如果我们把Thread看成一辆车的话,park就是让车停下
    unpark:就是让车启动然后跑起来
    这里的park和unpark其实实现了wait和notify的功能。
    区别:
    1.park不需要获取某个对象的锁(不释放锁)
    2.因为中断park不会抛出InterruptedException异常,需要在park之后自行判断中断状态,
        然后做额外的处理。
    总结:
    1.park和unpark可以实现wait和notify的功能,但是并不和wait和notify交叉使用。
    2.park和unpark不会出现死锁。
    3.blocker的作用看到阻塞对象的信息


    Lock接口的实现类ReentrantLock


    ReentrantLock,可重入锁。
    实现了Lock接口
    synchronized和Lock的区别:
    1.Lock是一个接口,synchronized是一个关键字,是由底层(C)语言实现的。
    2.synchronized发生异常时,会自动释放线程占用的锁不会发生死锁。
         Lock发生异常,若没有主动释放,极有可能占用资源不放手,需要在finally中手动释放锁。
    3.Lock可以让等待锁的线程响应中断,使用synchronized只会让等待的线程一直等待下去,不能响应中断
    4.Lock可以提高多个线程进行读操作的效率。

    1. class Ticket implements Runnable {
    2. private static final ReentrantLock lock = new ReentrantLock();
    3. private static Integer count = 100;
    4. String name;
    5. public Ticket(String name) {
    6. this.name = name;
    7. }
    8. @Override
    9. public void run() {
    10. while(Ticket.count > 0){
    11. lock.lock();
    12. try {
    13. try {
    14. Thread.sleep(100);
    15. } catch (InterruptedException e) {
    16. throw new RuntimeException(e);
    17. }
    18. if(count > 0){
    19. System.out.println(name + "正在卖票,剩余:" + count + "张!");
    20. count--;
    21. // count = count - 1;
    22. }
    23. }finally {
    24. lock.unlock();
    25. }
    26. }
    27. // while(true) {
    28. // try{
    29. // lock.lock();
    30. // if(count > 0){
    31. // try {
    32. // Thread.sleep(150);
    33. // } catch (InterruptedException e) {
    34. // throw new RuntimeException(e);
    35. // }
    36. // System.out.println(name + "正在卖票,剩余:" + count + "张!");
    37. // count--;
    38. // }else {
    39. // System.out.println("票已售空!");
    40. // break;
    41. // }
    42. // }finally {
    43. // lock.unlock();
    44. // }
    45. // }
    46. }
    47. }
    48. public class Ch03 {
    49. public static void main(String[] args) {
    50. Ticket t1 = new Ticket("窗口一");
    51. Ticket t2 = new Ticket("窗口二");
    52. Ticket t3 = new Ticket("窗口三");
    53. new Thread(t1).start();
    54. new Thread(t2).start();
    55. new Thread(t3).start();
    56. }
    57. }

    Lock以下功能是synchronized不具备的!
    ReentrantReadWriteLock:
     对于一个应用而言,一般情况下读操作远远多于写的操作,如果仅仅是读的操作没有写的操作,
         数据又是线程安全,读写锁给我们提供了一种锁,读的时候可以很多线程一起读,但是不能有线程写,
         写是独占的,当有线程在执行写的操作,其他线程既不能读,也不能写。
     在某些场景下能极大的提升效率!!! 

    1. public class Ch04 {
    2. private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    3. private static int count = 1;
    4. public static void main(String[] args) {
    5. Runnable read = () -> {
    6. // 创建了一个读锁
    7. ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    8. readLock.lock();
    9. try {
    10. Thread.sleep(2000);
    11. System.out.println("我在读数据:" + count);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }finally {
    15. readLock.unlock();
    16. }
    17. };
    18. Runnable write = () -> {
    19. // 创建了一个写锁
    20. ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    21. writeLock.lock();
    22. try {
    23. Thread.sleep(2000);
    24. System.out.println("我在写数据:" + count++);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. } finally {
    28. writeLock.unlock();
    29. }
    30. };
    31. for (int i = 0; i < 100; i++) {
    32. Random random = new Random();
    33. int flag = random.nextInt(100);
    34. System.out.println("生成的随机整数:" + flag);
    35. if(flag > 20){
    36. new Thread(read).start();
    37. }else {
    38. new Thread(write).start();
    39. }
    40. }
    41. }
    42. }

    lock锁的原理cas和aqs
    synchronized是由C语言实现的,只能作为关键字来使用
    java提供了一些并发的编程的包,底层的实现原理cas和aqs
    并发编程三大特性:
    1.原子性:原子操作可以是一个步骤,也可以是多个步骤,但是顺序不能乱,
         也不可以被切割只执行其中的一部分,将整个操作视为一个整体。
         原子性不仅仅是多行代码,也可能是多条指令。
    2.可见性
    3.有序性
     synchronized lock:可以保证原子性、可见性、有序性。
    CAS:compare and swap,比较并交换。JDK11改成了compare and set。
     思路:就是给一个元素赋值的时候,先看看内存里的那个值到底变没变。

    java.util.concurrent.atomic下
    JUC并发编程包
    1.原子类Atomic
     基本类型
     AtomicInteger:整型原子类
     AtomicLong:长整型原子类
     AtomicBoolean:布尔型原子类
     数组类型
     AtomicLongArray:长整型数组原子类
     AtomicIntegerArray:整型数组原子类
     AtomicReference:引用数据类型原子类  


      线程池


      为什么要使用线程池
     (1)降低资源消耗。通过重复利用已创建的线程降低创建和销毁线程造成的资源消耗
     (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
     (3)提过线程的可管理性。线程比较稀缺的资源,如果无限制的创建,不仅会消耗系统资源
          还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
     *
     JDK自带的四种线程池通过Executors提供的。
     1.newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活
          若无可回收,创建新线程。
     2.newFixedThreadPool:创建一个定长的线程池,可以控制线程最大并发数,超出的线程会
     3.newScheduledThreadPool:创建一个定长的线程池,支持定时及周期性任务执行
     4.newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行
          所有的任务按照指定顺序执行
     *
     这四种线程池的初始化都调用了同一个构造器:
     ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue workQueue,
                       ThreadFactory threadFactory,
                       RejectedExecutionHandler handler)
     参数的意义(重要):
     corePoolSize:线程池里线程的数量,核心线程池大小
     maximumPoolSize:指定了线程池里的最大线程数量
     keepAliveTime:当线程池线程数量大于corePoolSize,多出来的空闲线程,多长时间被销毁
     unit:时间单位
     workQueue:任务队列,用于存放提交但是尚未被执行的任务
     threadFactory:线程工厂,用来创建线程,线程工厂就是我们new线程的
     handler:拒绝策略,是将任务添加到线程池中时,线程池拒绝该任务多采取的相应的措施。
     *
     常见的工作队列
     ArrayBlockingQueue:基于数组的有界阻塞队列。FIFO。
     LinkedBlockingQueue:基于链表的有界阻塞队列。FIFO
     *
     线程池提供了四种拒绝策略:
     AbortPolicy:直接抛出异常,默认的策略。
     CallerRunPolicy:用调用者所在的线程来执行任务
     DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务
     DiscardPolicy:直接丢弃任务

    1. public class Ch01 {
    2. public static void main(String[] args) {
    3. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    4. Runnable taskOne = () -> {
    5. System.out.println(Thread.currentThread().getName() + "taskOne...");
    6. };
    7. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
    8. ExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    9. ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    10. for (int i = 0; i < 40; i++) {
    11. fixedThreadPool.submit(taskOne);
    12. }
    13. }
    14. }

     自定义线程池

    1. public class Ch02 {
    2. private static final AtomicInteger poolNumber = new AtomicInteger(1);
    3. private final ThreadGroup group;
    4. private final AtomicInteger threadNumber = new AtomicInteger(1);
    5. private final String namePrefix;
    6. Ch02(String name){
    7. SecurityManager s = System.getSecurityManager();
    8. group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
    9. namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";
    10. }
    11. Ch02(){
    12. this("default");
    13. }
    14. public Thread newThread(Runnable r){
    15. // 就是在创建线程
    16. Thread t = new Thread(group,r,namePrefix + threadNumber.getAndIncrement(),0);
    17. if(t.isDaemon()){
    18. t.setDaemon(false);
    19. }
    20. if(t.getPriority() != Thread.NORM_PRIORITY){
    21. t.setPriority(Thread.NORM_PRIORITY);
    22. }
    23. return t;
    24. }
    25. public static void main(String[] args) {
    26. Ch02 ch02 = new Ch02();
    27. ch02.newThread(()->{
    28. System.out.println("自定义线程池创建的线程...");
    29. }).start();
    30. }
    31. }

     总结

    创建线程的4种方式*****
    2.线程同步(synchronized,ReentrantLock,ReentrantReadWriteLock)*****
    3.线程之间的通信(wait,notify,notifyAll)*****
    4.线程类的常用方法***
    指令重排,线程争抢,可见性,原子性,volatile关键字

  • 相关阅读:
    高精度气象模拟软件WRF(Weather Research Forecasting)
    阿里云一键登录(号码认证服务)
    SpringBoot实现定时发送邮件功能
    说几句得罪人的大实话
    群聊比单聊,凭什么复杂这么多?
    Mybatis-Plus复杂语句多级嵌套分组带分页查询
    MIL图像处理那些事:直角坐标和极坐标间变换MimPolarTransform
    展开说说:Android Fragment完全解析-卷一
    CSS进阶篇——具体性 (specificity)
    【2022】【论文笔记】基于激光直写氧化石墨烯纸的超薄THz偏转——
  • 原文地址:https://blog.csdn.net/weixin_45777469/article/details/126108229