当前位置: 首页 > article >正文

Java多线程_1

目录

线程的概念

线程和进程的关系和区别

Java的线程和操作系统线程的关系

创建线程

常见方式,Thread常见构造方法

Thread常见属性

join方法

获取当前线程引用

wait,notify使用理解

线程安全

原子性

内存可见性,指令重排序,共享资源竞争

单例模式

1.饿汉模式

2.懒汉模式

懒汉模式细节处理

阻塞队列

模拟实现

线程池

使用原因

标准库中的线程池

Executors创建线程池的⼏种⽅式

ThreadPoolExecutor

基本概念

拒绝策略:

定时器Timer

模拟实现


线程的概念

⼀个线程就是⼀个"执⾏流".每个线程之间都可以按照顺序执⾏⾃⼰的代码.多个线程之间"同时"执⾏
着多份代码.

为了更好利用CPU资源,单核CPU的发展遇到了瓶颈.要想提⾼算⼒,就需要多核CPU.⽽并发编程能更充分利⽤多核CPU资源.有些任务场景需要"等待IO"。

为了让等待IO的时间能够去做⼀些其他的⼯作,也需要⽤到并发编程.

线程和普通程序的区别在于每个线程都是⼀个独⽴的执⾏流, 多个线程之间是"并发"执⾏的.
 


线程和进程的关系和区别

1. 进程是包含线程的.每个进程⾄少有⼀个线程存在,即主线程。

2. 进程和进程之间不共享内存空间.同⼀个进程的线程之间共享同⼀个内存空间。

3. 进程是系统分配资源的最⼩单位,线程是系统调度的最⼩单位

4. ⼀个进程挂了⼀般不会影响到其他进程.但是⼀个线程挂了,可能把同进程内的其他线程⼀起带⾛

5. 虽然多进程也能实现并发编程,但是线程⽐进程更轻量.,创建线程⽐创建进程更快,销毁线程⽐销毁进程更快,调度线程⽐调度进程更快.

Java的线程和操作系统线程的关系


线程是操作系统中的概念.操作系统内核实现了线程这样的机制,并且对⽤⼾层提供了⼀些API供⽤户使⽤Java标准库中Thread可以视为是对操作系统提供的API进⾏了进⼀步的抽象和封装.

创建线程

常见方式,Thread常见构造方法

创建线程的方法:

1.继承Thread类

class MyThread extends Thread{
    public void run(){
        System.out.println("MyThread");
    }
}
public class Demo3 {
    public static void main(String[] args) {
        MyThread t=new MyThread();
        t.start();
    }
}

2.实现Runnable接口,创建Thread类实例,调⽤Thread的构造⽅法时将Runnable对象作为target参数.

class MyRunnable implements  Runnable{
    public void run(){
        System.out.println("run");
    }
}
public class Demo3 {
    public static void main(String[] args) {
       MyRunnable runnable=new MyRunnable();
       Thread t=new Thread(runnable);
       t.start();
    }
}

3.使用匿名内部类

       Thread t1=new Thread(){
           public void run(){
               System.out.println("创建Thread的匿名内部类");
           }
       };
       Thread t2=new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("创建Runnable的匿名内部类");

           }
       });

4.lambda表达式创建Runnable⼦类对象(推荐)

lambda表达式中使用外部变量时,该变量需要是不可修改或者不会去修改的的,否则编译失败

 Thread t3=new Thread(()->{
          System.out.println("lambda");
       });

Thread常见属性

join方法

join()在哪个线程中调用则该线程等待调用者结束再继续执行

join(time)等待毫秒级别,超时不等待

获取当前线程引用

wait,notify使用理解

这两个方法在多线程中是非常重要的方法,这两个方法必须在synchronize(locker){}花括号里面使用,wait,notify这两个方法必须搭配使用,且必须是同一个锁对象,locker.wait(),locker.notify();

wait还有一个带参数的版本,超时则不等待;

notify方法执行后,需要等该线程锁释放掉才会去唤醒wait方法,wait方法一般需要搭配where循环进行多次判断,可以参照下面阻塞队列模拟实现进行理解

线程安全

如果多线程环境下代码运⾏的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。需要进行必要的加锁操作保证线程安全(synchronized关键字)

线程不安全的原因
线程调度是随机的,随机调度使⼀个程序在多线程环境下,执⾏顺序存在很多的变数.
因此程序猿必须保证在任意执⾏顺序下,代码都能正常⼯作.

多个线程修改同一个数据就很可能引发线程安全问题

例如:

原子性

引发上述问题原因是修改操作不是原子性操作,count++这个操作在CPU上执行分三步1. 从内存把数据读到CPU。2. 进⾏数据更新。3. 把数据写回到CPU。多个线程同时执行就很容易出现数据丢失

内存可见性,指令重排序,共享资源竞争

内存可见性问题,多个线程同时对一个共享变量修改会产生线程安全问题,每个线程会对共享变量的值拷贝到工作内存中,对该值修改后再同步到主内存中,同步的时机是随机的,这样就很容易出现问题。

指令重排序,jvm在保证单线程下不影响执行结果的情况下可能会对某些代码的执行顺序做出调整提高效率,但是在多线程下很可能引发很多不确定性的问题

volatile关键字可以防止这两种问题的出现,volatile不保证原⼦性

略.......

单例模式

单例模式能保证某个类在程序中只存在唯⼀⼀份实例,而不会创建出多个实例

通常有两种写法:懒汉模式,饿汉模式

1.饿汉模式

class SingleTon{
    private static SingleTon instance=new SingleTon();
    public static SingleTon getInstance(){
        return instance;
    }
    private SingleTon(){

    }
}

2.懒汉模式

class SingleLazyTon{
    private static volatile SingleLazyTon instance=null;
    private static Object locker=new Object();
    public static SingleLazyTon get(){
        if(instance==null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingleLazyTon();
                }
            }
        }
        return instance;
    }
    private SingleLazyTon(){

    }
}

懒汉模式细节处理

懒汉顾名思义就是只有在调用get方法的时候才去创建实例,同时也出现一些问题

如下图所示,多个线程同时进行时如果有一些线程同时进入到if里面多次创建实例,开销太大,影响效率

所以需要进行加锁操作,如下

为了进一步优化,减小加锁的频率,再加一层判断

加上这层判断后会产生新的问题内存可见性问题即某一个线程创建了对象未及时同步到主内存中,可能会再次加锁影响效率(影响小);指令重排序:创建对象的步骤分为三步,分配内存、调用构造方法创建对象,返回引用。可能先返回引用,再创建对象,而别的线程可能拿到一个只有引用的对象,是一个半成品;综上,需要volatile避免出现以上问题。

阻塞队列

在Java标准库中内置了阻塞队列.如果我们需要在⼀些程序中使⽤阻塞队列,直接使⽤标准库中的即
可。

BlockingQueue是一个接口真正实现的是  ArrayBlockingQueue,LinkedBlockingQueue,ProrityBlockingQueue等

put⽅法⽤于阻塞式的⼊队列,take⽤于阻塞式的出队列,BlockingQueue也有offer,poll,peek等⽅法,但是这些⽅法不带有阻塞特性.

模拟实现

class MyBlockingQueue{
    private volatile int size;
    private int last;
    private int cur;
    private int[] task=new int[100];
    public void put(int elem) throws InterruptedException {
        synchronized (this){
            //where循环进行多次判断
            while(size==task.length){
                this.wait();
            }
            task[last]=elem;
            last++;
            size++;
            if(last==task.length){
                last=0;
            }
            this.notify();
        }

    }
    public int take() throws InterruptedException {
        int ret=task[cur];
        synchronized(this){
            while(size==0){
                this.wait();
            }
            cur++;
            size--;
            if(cur==task.length){
                cur=0;
            }
            this.notify();
        }
        return ret;

    }
}

线程池

使用原因

为了优化线程创建销毁的开销,引入了线程池。

举例:一个公司为了解决一个项目雇了一名员工,任务结束就开除了,每次都这样效率就会很低;

后来雇了一群员工并不会开除,有少的任务,随机派几名去干活,任务多的时候甚至全派出去,谁干得快就多干点........这样就减少了频繁招聘开除所带来的开销。
 

简单使用:

public class Demo2 {
    public static void main(String[] args) {
        ExecutorService pool= Executors.newFixedThreadPool(10);
        Thread t1=new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                int r=i;
                pool.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("执行任务"+r+
"---"+Thread.currentThread().getName());
                    }
                });
            }
        });
        t1.start();
    }
}

执行结果:

在执行结果中我们会发现它在任务全部执行完并没有结束程序,可以明白线程池里面创建的线程是前台线程。可以使用shutdown 能够把线程池里的线程全部关闭,但是不能保证线程池内的任务一定能全部执行完毕。所以,如果需要等待线程池内的任务全部执行完毕,需要调用 awaitTermination 方法。

标准库中的线程池


• 使⽤Executors.newFixedThreadPool(n)能创建出固定包含n个线程的线程池.
• 返回值类型为ExecutorService
• 通过ExecutorService.submit可以注册⼀个任务到线程池中.
 

Executors创建线程池的⼏种⽅式


• newFixedThreadPool:创建固定线程数的线程池
• newCachedThreadPool:创建线程数⽬动态增⻓的线程池
• newSingleThreadExecutor:创建只包含单个线程的线程池.
Executors本质上是ThreadPoolExecutor类的封装.

ThreadPoolExecutor

提供了更多的可选参数,可以进⼀步细化线程池⾏为的设定.(这个更加灵活,推荐)

基本概念

corePoolSize:第一个参数,核心线程数,一旦创建不会销毁(正式员工)


maximumPoolSize:第二个参数,最大线程数,(正式员工和非正式员工总数)


keepAliveTime:第三个参数,非正式允许的空闲时间.


unit:第四个参数,keepaliveTime的时间单位,是秒,分钟,还是其他值.(枚举类型)


workQueue:第五个参数,传递任务的阻塞队列


threadFactory:第六个参数,创建线程的⼯⼚,参与具体的创建线程⼯作.通过不同线程⼯⼚创建出的线程相当于对⼀些属性进⾏了不同的初始化设置.



RejectedExecutionHandler:第七个参数,拒绝策略,如果任务量超出公司的负荷了接下来怎么处理.


拒绝策略:

拒绝策略,在线程池里面当任务队列满的时候并不会阻塞等待,而是执行拒绝策略

AbortPolicy():                  超过负荷,直接抛出异常.
CallerRunsPolicy():         调⽤者负责处理多出来的任务.
DiscardOldestPolicy():    丢弃队列中最⽼的任务.
DiscardPolicy():               丢弃新来的任务.


定时器Timer

定时器也是软件开发中的⼀个重要组件.类似于⼀个"闹钟".达到⼀个设定的时间之后,就执⾏某个指定好的代码.定时器是⼀种实际开发中⾮常常⽤的组件.

⽐如⽹络通信中,如果对⽅500ms内没有返回数据,则断开连接尝试重连.
⽐如⼀个Map,希望⾥⾯的某个key在3s之后过期(⾃动删除).
类似于这样的场景就需要⽤到定时器.
 

举例:

 Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务3");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },1000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },2000);

模拟实现

class MyTimerTask implements Runnable, Comparable<MyTimerTask> {
    private long time;
    private Runnable runnable;
    public void run(){
        runnable.run();
    }
    public MyTimerTask(Runnable runnable,long time){
       this.runnable=runnable;
       this.time=time;
    }
    public long getTime(){
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time-o.time);
        //return (int)(o.time-this.time);
    }



}
class MyTimer{
    private PriorityQueue<MyTimerTask> task=new PriorityQueue<>();
    public MyTimer(){
        Thread t=new Thread(()->{
            while(true){
                synchronized (this) {
                    if (task.isEmpty()) {
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    MyTimerTask r = task.peek();
                    if (r.getTime() > System.currentTimeMillis()) {
                        try {
                            this.wait(r.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        r.run();
                        task.poll();
                    }
                }
            }
        });
        t.start();
    }
    public void schedule(Runnable myTimertask,long delay){
        synchronized (this){
            MyTimerTask timerTask=new MyTimerTask(myTimertask,System.currentTimeMillis()+delay);
            task.offer(timerTask);
            this.notify();
        }
    }
}


http://www.kler.cn/a/369534.html

相关文章:

  • 本地docker-compose仓库搭建以及推送docker镜像到仓库
  • hudi编译安装,使用spark3的maven指令
  • 【设计模式系列】代理模式(八)
  • Python应用指南:利用高德地图API实现路径规划
  • JAVA基础:集合 (学习笔记)
  • 【Pip】初识 Pip:Python 包管理的基本命令详解
  • VUE使用vue-tree-color组件实现组织架构图,并可以动态更新数据
  • Hugging Face 使用指南——并行智算云(10s上手版)
  • 取消element-ui中账号和密码登录功能浏览器默认的填充色,element-ui登录账号密码输入框禁用浏览器默认填充色问题
  • HT7183 带有PWM控制的16V,4.5A高效升压转换器
  • 靓车汽车销售:Spring Boot网站开发全攻略
  • Spring Boot论坛网站开发:最佳实践指南
  • 深度学习:抑制过拟合
  • 开源社区的兴起
  • 安全见闻8,量子力学见闻
  • 诺基亚的裁员风暴
  • 深入探讨全流量回溯分析与网络性能监控系统
  • Springboot整合spring-boot-starter-data-elasticsearch
  • Uni-App-02
  • STM32 从0开始系统学习2
  • sql server 之动态sql
  • python学习笔记:___getattr__
  • Unity 实现音频进度条(可控制)
  • iframe里放的视频,如何采用纯css适配
  • 完美结局 ubuntu开机卡在等待网络连接
  • 【深度学习】合合信息:生成式AI时代的内容安全与系统构建