首页 > 其他分享 >多线程

多线程

时间:2022-09-22 14:44:32浏览次数:58  
标签:同步 Thread corePoolSize 创建 线程 多线程 public

进程

每个应用程序在运行期间,操作系统为应用程序分配一个独立的内存空间,称为进程;多个进程之间的数据是相互隔离的; windows查看后台进程命令

tasklist

linux查看后台进程命令

ps -aux

线程

进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的; 充分利用CPU多核心的优势,进一步提高程序性能。

多线程的优点

1.可以更好的实现并行

2.恰当地使用线程时,可以降低开发和维护的开销,并且能够提高复杂应用的性能。

3.CPU在线程之间开关时的开销远比进程要少得多。因开关线程都在同一地址空间内,只需要修改线程控制表或队列,不涉及地址空间和其他工作。

4.创建和撤销线程的开销较之进程要少。

Java在语言级提供了对多线程程序设计的支持

多线程操作会增加程序的执行效率。各线程之间切换执行,时间比较短,看似是多线程同时运行,但对于执行者CPU来说,某一个时刻只有一个线程在运行 分时系统的特点:

实现多线程

java中,每个线程都是一个Thread类的对象;Thread类中常用的两个方法:run()start();

run():方法是实现单个线程的主体,我们在实现多线程时,需要重写run方法,并在方法体以内完成我们要做的事情;run()不是给我们来调用,而是让多个线程在JVM中运行时,由CPU来控制线程的切换,以及对run方法的调用;

start():使线程进入到可执行状态,等待cpu为线程分配执行时所需要的资源;

Thread类

构造方法

方法描述
Thread() 创建一个新的线程对象
Thread(Runnable target) 创建一个新的线程,线程的实现位于Runnable对象中
Thread(Runnable target,String name) 创建一个新的线程,通过Runnable对象实现,并为线程命名
Thread(String name) 创建一个线程,并为其指定名称

方法摘要

方法描述
getId():long 返回线程的唯一标识符
getName():String 返回线程的名称
getPriority():int 返回线程的优先级;默认优先级为5,最低1,最高为10
getState():Thread.State 返回线程的状态
join():void 等待当前线程结束
run():void 实现线程的主体方法,不需要我们手动调用
setDaemon(boolean) 是否将线程设置为守护线程
setPriority(int) 为线程设置优先级;默认优先级为5,最低1,最高为10
static sleep(long) 使线程休眠指定的毫秒数,不会失去线程的锁
start():void 使线程进入到可执行状态
static yield():void 使线程放弃对CPU的使用权,从新进入到可执行状态
static currentThread():Thread 返回当前正在运行的线程

实现线程的方法

创建自己的线程有两种方式;

  1. 用一个类继承Thread,不推荐;java是单继承的特性

  2. 用一个类实现Runnable接口,推荐;java是多实现的特性

public class Thread1 extends Thread{
    /*
    * 继承Thread类,必须重写父类中的run方法
    * 程序在运行期间,jvm负责调用run方法中的代码实现多线程的切换
    * */
    @Override
    public void run() {
        Common.test1();
    }
}
/*实现Runnable接口*/
public class Thread2 implements Runnable {
    @Override
    public void run() {
        Common.test2();
    }
}

线程的调度策略

通过线程模拟铁路售票;开启3个线程销售100张票;

public class Ticket implements Runnable{
    private int ticket = 100; //模拟总共有100张票
    @Override
    public void run() {
        /*
        * 只要余票大于0,就要继续卖
        * */
        while (ticket > 0) {
            //得到当前正在运行的线程的名称
            String name = Thread.currentThread().getName();
            System.out.println( name + "卖出了票号" + ticket + "的票");
            this.ticket--;
        }
    }
}
/*测试类*/
public class Client {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket,"A窗口");
        Thread t2 = new Thread(ticket,"B窗口");
        Thread t3 = new Thread(ticket,"C窗口");
​
        t1.start();
        t2.start();
        t3.start();
    }
}
A窗口卖出了票号100的票
C窗口卖出了票号100的票
B窗口卖出了票号100的票
...
C窗口卖出了票号1的票
A窗口卖出了票号99的票
B窗口卖出了票号36的票

存在的问题:

  1. 票号有重复的

  2. 总共100张票被卖了102次

为了理解为什么出现如上的问题,我们需要理解线程的调度策略;

线程的调度策略

  1. 时间片策略

  2. 抢占式策略

同优先级线程组成先进先出队列(先到先服务),使用时间片策略

对高优先级,使用优先调度的抢占式策略

 

多个线程在执行期间,默认以抢占式策略来尝试获取到CPU的使用权;在多线程环境中,同一个时间点只有一个线程被执行,但是CPU为每个线程分配的使用时间非常的短暂,因此给我们感官上,多个线程同时在执行;

抢占式策略类似于在食堂排队吃饭;默认情况下按照抢占式策略,谁的动作快谁就先完成打饭;如果大家动作都一样,就看谁的块头大(优先级);

线程的优先级:默认为5,最高为10,最低为1;通过Thread类中的setPriority(int)设置优先级;

线程的生命周期

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态

新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态 死亡:线程完成了它的全部工作或线程被提前强制性地中止

 

线程的同步

是为了防止多个线程访问一个数据对象时,对数据造成的破坏

线程同步,其实就是排队,然后一个一个对共享资源进行

操作,而不是同时进行操作

只有共享资源的读写访问才需要同步

线程同步能够保证多线程中共享资源的数据安全问题(有的线程读,有的线程写);

默认情况下,线程是按照时间片策略来执行;但是使用了同步以后,线程的执行时间就不由时间片策略管理,而是交给线程本身;因此被同步的资源,前一个线程没有释放它的控制(锁),后一个线程就会一直等待;

 

线程同步的两种方式

  1. 同步代码块

    /*同步代码块存在于方法以内*/
    public void run(){
        synchronized(对象){//共享锁
             //需要同步的代码;
        }
    }
  2. 同步方法

    同步方法必须位于共享资源类中;不能定义到其他的地方;因为同步方法同样需要加锁,同步方法加锁的对象为共享资源对象本身;

    /*哪个线程得到对同步方法使用权限时,方法只要没结束,别的线程就无法访问同步方法*/
    public synchronized void test(){ //同步方法
        
    }

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。 死锁出现时:程序出现假死现象;没有结果,但也不会结束。

解决方法: 专门的算法、原则 尽量减少同步资源的定义

wait()和sleep()有什么区别

这两个方法都能使线程进入阻塞状态; sleep():是Thread类中的静态方法。 在不会失去共享资源锁的情况下阻塞指定时间;当时间到了以后,继续执行; wait() : 是Object类中的实例方法;当前线程失去共享资源锁的情况下进入阻塞状态;当被别的线程调用notify()或者notifyAll()时唤醒,进入到就绪状态;

线程之间的通信

线程进行协同工作时,通过wait(),notify(),notifyAll()这几个方法进行。 wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。线程进入阻塞状态。(失去共享资源的锁) notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待,进入就绪状态。 notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

 

可重入锁

从java1.5开始出现可重入锁java.util.concurrent.locks.ReentrantLock

synchronized的区别:

  • synchronized是jvm从语法层面支持的写法

  • ReentrantLock是一个java类,通过CAS和AQS实现线程的同步。内部维护的是一个双端线程队列。

ReentrantLock能够实现公平锁和非公平锁。

使用synchronized同步时:

public synchronized void test(){
    //同步方法的开始
    
    //同步方法的结束
}

使用ReentrantLock同步时:

ReentrantLock lock = new ReentrantLock();
lock.lock(); //手动加锁
    //需要同步的代码
lock.unlock(); //手动的解锁

内建锁隐式支持重入锁,Synchronized通过获取自增,释放自减的方式实现重入

1.重入锁实现原理

重入锁的特点:

1)线程获取锁时,如果已经获取锁的线程是当期线程直接再次获取;

2)由于锁会被获取N次,因此锁只有被释放N次之后才算真正释放成功

2.公平锁与非公平锁

公平锁:锁的获取顺序一定满足时间上的绝对顺序,等待时间最长的线程一定最先获取到锁

 

Reentrantlock默认使用非公平锁 对比:公平锁保证每次获取锁均为同步队列的第一个节点,保证了请求资源时间上的绝对顺序,但是效率

较低,需要频繁的进行上下文切换。

非公平锁会降低性能开销,降低一定得上下文切换,但是可能导致其他线程永远无法获取到锁,造成线程“饥饿”现象。

通常来讲,没有特定公平性要求,尽量选择非公平锁

线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

通过Executors工具类中的方法创建线程池

方法描述
newFixedThreadPool(int nThreads) 创建一个具有默认大小的线程池
newSingleThreadExecutor() 创建只有1个线程的线程池
newCachedThreadPool() 创建一个具有缓存的线程池
newScheduledThreadPool(int corePoolSize) 创建一个延时的线程池
//创建了一个具有4个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(4);
//创建了一个具有1个线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
//创建具有缓存的线程池,适合短时间内,多个执行时间比较短的任务
//线程池初始化大小为0,当任务到达时,从线程池中尝试获取一个线程使用
//如果线程池中没有可用线程,则创建一个线程并放入线程池
//当线程池中的线程空闲时间达到60秒钟,则这线程将从线程池中删除。
ExecutorService pool = Executors.newCachedThreadPool();

线程池具有的方法描述

  • execute(Runnable): 执行线程任务,不返回结果

  • submit(Runnable): 执行线程任务,返回特定的结果

在Java中可以通过线程池来达到这样的效果。

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

 下面解释下一下构造器中各个参数的含义:

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

  • maximumPoolSize:线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。

  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性

  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响

  • threadFactory:线程工厂,主要用来创建线程

  • handler:表示当拒绝处理任务时的策略;当线程池中的线程数已经达到了最大允许的线程数,并且队列已满,则新的任务会导致拒绝策略的执行。默认抛出java.util.concurrent.RejectedExecutionException异常

Executor和ExecutorService接口

这两个接口定义了线程池具备的方法。

 

 

 

ThreadPoolExecutor是一个实现类,实现了ExecutorService接口。ExecutorService接口继承了Executor接口。

Executor接口中的方法

public interface Executor {

    /*让线程池执行一个不返回结果的任务*/
    void execute(Runnable command);
}

 

ExecutorService接口中的方法

public interface ExecutorService {
    /*同样也是将任务提交给线程池执行,但是需要返回一个结果*/
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
}

 

 

标签:同步,Thread,corePoolSize,创建,线程,多线程,public
From: https://www.cnblogs.com/huang2979127746/p/16719197.html

相关文章

  • 【Linux】多线程中fork与互斥锁---子进程会复制继承父进程里锁的状态
    摘自:https://blog.csdn.net/xiaoxiaoguailou/article/details/121617142问题提出:我们有这样一个问题:在一个多线程程序中创建子进程并且让子线程和子进程去获取一把全局变......
  • JAVA多线程-学习笔记
    1.1概述程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。进程(Porcess):是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位。线......
  • 多线程
    一.Java构建线程的方式继承Thread实现Runnable实现Callable线程池方式推荐手动创建线程池二.线程池的7个参数publicThreadPoolExecutor(intcorePoolSiz......
  • 【Redis】Redis是单线程还是多线程
     Redis6.0版本之前的单线程指的是其网络I/O和键值对读写是由一个线程完成的Redis6.0引入的多线程指的是网络请求过程采用了多线程,而键值对读写命令仍然是单线程处......
  • 多线程04
    小结1.继承Thread类子类继承Thread类具备多线程能力启动线程:子类对象.start()不建议使用,因为继承是单继承2.实现Runnable接口......
  • 多线程
    一、线程概念    进程是正在运行的程序,是系统资源调度的基本单位,一个进程至少有一个线程,线程中可以共享内存资源   例如:进程执行多件事情,例如一遍听音乐,一......
  • Java 多线程中的任务分解机制-ForkJoinPool,以及CompletableFuture
    简介ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后......
  • SSD, Redis多线程与云服务器架构,PC处理器
    SSD,Redis多线程与云服务器架构,PC处理器参考文献链接https://mp.weixin.qq.com/s/T-ZTn4_oGwhXSpPNg0_wOwhttps://mp.weixin.qq.com/s/qqTgnG3ndeUiZXdnmzQUfQhttps:/......
  • C#多线程 操作UI问题
    C#多线程操作UI的简单写法:KeledoSuperPlus privateTaskSchedulermpr_ts_UIContext;privatevoidbutton1_Click(objectsender,EventArgse){......
  • Python多线程编程——threading模块
    本文参考:https://blog.csdn.net/youngwyj/article/details/124720041https://blog.csdn.net/youngwyj/article/details/124833126目录前言threading模块1.简介2.创建线......