怎么用网站做淘宝客,网站主体负责人必须是法人吗,上海新闻坊,网站改版对seo的影响一.多线程
1.什么是多线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中#xff0c;是进程的实际运作单位 简单理解多线程就是应用软件中相互独立#xff0c;可以同时运行的功能(也可以理解为人体内相互独立#xff0c;但可以同时运行的器官⌓‿⌓)
我们…一.多线程
1.什么是多线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中是进程的实际运作单位 简单理解多线程就是应用软件中相互独立可以同时运行的功能(也可以理解为人体内相互独立但可以同时运行的器官⌓‿⌓)
我们平时常用的Main方法就是主线程
2.多线程的作用
单线程运行时比如我们要创建一个变量程序是需要等待时间的。而使用多线程程序可以在多个线程之间来回运行充分利用等待的时间从而提高CPU的利用效率 二.多线程的三种实现方式
在Java API中对于Thread类的描述中给出了多线程的两种启动方式
⒈Thread类 线程是程序中的执行线程。Java虚拟机允许应用程序并发地运行多个执行线程 下面我们就来结合多线程的启动方式来了解Thread类
⒉多线程的第一种启动方式
将类声明为Thread的子类
该子类应重写Thread类的run方法
接下来可以分配并启动该子类的实例 如图:我们创建两条线程并启用 ⑴此方法中的Thread类
①构造方法 Ⅰ.Thread() Ⅱ. Thread(String name) 其中的参数传递的就是线程的名字默认为Thread-序号
②常用方法
Ⅰ start void start() 使该线程开始执行 start方法是Thread类的基础方法有了它线程才能够启动执行而它又不像流那样需要close关闭当线程结束后它会自动关闭
Ⅱ getName String getName() 返回该线程的名称 当线程我们没有手动命名getName默认返回的是Thread-序号
Ⅲ sleep 在多条线程执行时当一条线程抢占到CPU执行权它的执行时间是不确定的那么它就可能一直占有着当执行完毕后才能轮到下一个线程 比如上图当一条线程执行完毕后才能轮到下一个线程
那我们想要线程轮流执行那么就可以使用sleep让线程睡一会
如图:当我们执行完打印语句后就让线程睡1毫秒将执行权让给另一条线程 如图:看运行结果两条线程就差不多是交替执行而不是一条线程执行到底 Ⅳ setPriority void setPriority(int newPriority) 设置线程的优先级 void getPriority() 获取线程的优先级 在Java中多条线程的执行是随机的线程的优先级分为10个等级(1--10)优先级高的获取到CPU执行权的概率就越高Java默认优先级为5
如图:优先级高的抢占到执行权的概率越大而不是一定是它先执行完这是概率问题 Ⅴ setDaemon void setDaemong(boolean on) 将该线程设置为守护线程 守护线程就是当其他非守护线程执行完毕后守护线程就没有存在的必要了就会陆续结束该守护线程可以不执行完结束
如图:我们将线程1设置为守护线程线程2为非守护线程当线程2执行完毕时线程2会陆续结束可以不会执行完 举个例子当我们在扣扣的聊天窗口传输数据聊天窗口就是非守护线程而传输数据的窗口就是守护线程当我们把聊天窗口关闭时数据传输窗口也会陆续关闭
Ⅵ yield static void yield() 暂停当前正在执行的线程对象并执行其他线程 yield方法可以出让当前的CPU执行权但出让之后该线程仍然可能抢占到CPU执行权
因此除了sleep方法该方法也可以让线程的执行尽可能的均匀些
Ⅶ join join方法设置的线程当该线程抢占到CPU执行权之后在规定的等待时间内就会一直执行完毕后才会让出CPU的执行权
⒊多线程的第二种启用方式
将类声明实现Runable接口
然后实现run方法
最后创建子类对象并传递给Thread对象 如图:我们创建子类对象并传递给线程执行 ⑴此方法中的Thread类
①构造方法 方法传递Runable接口的实现类对象
又因为Runable接口是函数式接口因此我们可以使用匿名内部类的方式实现 ②常用方法
Ⅰ currentThread static void currentThread() 获取当前线程对象 Ⅱ getName
Runable方法启动线程获取线程名字与Thread方法获取不同
在Runable实现类中我们实现的是Runable接口该类与Thread是没有关系的那么我们就不能像第一种启用方式那样直接getName获取到线程的名字了
那怎么解决呢
我们可以利用currentThread方法获取到当前执行线程的对象然后再调用getName方法获取到当前线程的名字 4.多线程的第三种启用方式
在前面的两种启用方式中run方法是没有返回值的
因此我们就需要一种有返回值的启用多线程的方法
FutureTask类可以的get方法可以获取到线程方法中的结果且FutureTast实现了Runable接口可以在第二种方式的基础上启用线程 而其中构造方法可以传递一个Callable接口的实现类对象 而Callable接口只有一个方法call可以返回线程计算的结果 因此第三种启用方式就是在第二种方式的基础上稍加修改
首先创建Callable实现类并重写call方法
然后创建FutureTesk对象接受Callable实现类运行的结果
最后创建Thread类对象并启动 5.三种线程启用方式的对比
对于第一种启用方式操作简单可以直接使用Thread类中的方法。但是正因为它继承了Thread类它就不能再继承其他类了因此它的扩展性较差
对于第二三种启用方式它没有继承任何类因此它的扩展性就强些。但是它的编程相对的就复杂些不能直接使用Thread类中的方法
第一二种启用方式无法获取到方法返回值因此就有了第三种方式可以获取到方法返回值
三.线程的安全问题
在多线程的执行中若我们有一个共享的变量size在随着线程的执行变化着因为线程的执行是随时随机抢占的那么size就会有线程安全问题
如图所示例子: 那么为了维护线程操作共享数据运行时的安全性问题我们可以将这共享数据的代码用锁锁起来当线程进入锁后其他线程在外等候当锁里面的代码执行完毕后其他线程才能抢夺执行
1.同步代码块
格式: synchronized(锁){操作的共享数据} 这里面的锁对象一定要是唯一的只有相同的一把锁我们才可以让多条线程开锁解锁
这把锁可以是任意类型的共享对象
如:static Object onew Object;
但是通常我们会使用本类的字节码文件
类名.class
如图:我们利用本类的字节码文件作为唯一的锁对象 2.同步方法
同步方法就是把synchronized关键字加到方法上表示把这一个方法的所有代码给锁起来
格式: 修饰符 synchronized 返回值类型 方法名(方法参数){} 同步方法的锁对象我们不能自己指定Java给我们指定好了锁对象
当是非静态方法时锁对象是this表示方法调用者
当是静态方式时所对象是当前类的字节码文件
如图:在之前我们学习的StringBuffer中我们同用可以看到同步方法的身影这表示StringBuffer是线程安全的 3. Lock锁
synchronized操作简单但是我们无法进行更多关于锁的操作而Lock相比于synchronized可以进行更广泛的锁定操作允许更灵活的结构可以支持多个相关的Condition对象
⑴Lock类
如图: Lock类是一个接口不能直接实例化我们常用它的实现类ReentrantLock(可重入锁)来实例化 ①成员方法 Ⅰ lock和unlock
lock开锁, unlock解锁这两个方法是最基本的锁同样的锁对象必须是唯一的
利用lock锁有一个小细节
如图:若我们直接lock与unlock就会遇到下面的问题有线程拿着钥匙跑了其他线程结束不了 因此我们需要一个解决办法无论线程怎样执行unlock必须执行。那么我们就可以使用try...finally来包裹unlock,让锁必须释放 Ⅱ newCondition
lock锁通常会与Condition类结合使用来进行一些对于锁的操作
比如在阻塞队列中的使用
https://blog.csdn.net/m0_74808313/article/details/132196171
4.死锁问题
当锁嵌套时通常会遇到死锁问题
如图:当线程1拿到了A锁等待B锁而同时线程2拿到了B锁等待A锁这时就导致了死锁 因此为了防止死锁问题我们好尽量减少锁的嵌套
四.多线程协作 多线程协作就是线程之间相互配合共同完成某项工作 比如一个线程修改了一个对象的值而另一个线程感知到了变化然后进行相应的操作整个过程开始于一个线程而最终执行又是另一个线程。前者是生产者后者是消费者
1.生产者和消费者
生产者和消费者模式是一个十分经典的多线程协作的模式又叫做等待唤醒机制打破了线程的随机机制让多个线程轮流执行
所谓生产者消费者问题实际上主要是包含了两类线程
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
生产者和消费者的关系通常会采用共享的数据区域就像是一个仓库
生产者生产数据之后直接放置在共享数据区中并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据并不需要关心生产者的行为
2.等待唤醒机制的实现
⑴仓库生产者和消费者的逻辑
首先我们需要一个仓库当生产者生产物件后放入仓库消费者消费物件拿出仓库
这个仓库需要有一把锁
当生产者进入时若发现仓库中有物件那么它就会沉睡等待若发现仓库中没有物件那么它就会生产物件并叫醒消费者 当消费者进入时若发现仓库中没有物件那么它就会沉睡等待;若发现仓库中有物件那么它就会消费物件并叫醒生产者 ⑵wait和notify
在Java的Object类中提供了相对应的方法来帮助我们解决线程的协作问题 注: wait和notify必须使用在同步方法或同步代码块内
⑶阻塞队列实现
首先我们需要一个阻塞队列这个阻塞队列就代表仓库
然后我们完成生产者与消费者的逻辑
因为阻塞队列的put与take方法就是生产者与消费者的逻辑因此我们在写生产者与消费者时就直接puttake不用再进行逻辑的实现 如图:我们写完生产者与消费者的逻辑传递阻塞队列查看 如图:查看打印语句发现生产者与消费者是轮流执行的这样就实现了等待唤醒机制的逻辑 细节:因为锁是在put与take方法内部的而打印语句在锁的外面但并不影响共享数据的执行只是不便于我们查看
五.线程状态
在给定的时间点上一个线程只能处于一种状态。这些状态是虚拟机状态它们并没有反映所以操作系统线程状态 六.线程池
在以前我们写的多线程有弊端
当我们需要线程时就创建(NEW)当它运行完后就消失(TERMINAED)这样的话浪费操作系统的资源
因此我们需要线程池来优化
1.线程池核心原理
我们需要一个容器当我们提交线程任务时容器会创建新的线程对象任务执行完毕线程存入到容器到下次直接拿出使用
若提交任务时容器中没有空闲线程且容器满了那么其他线程排队等待
2.线程池实现 static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池 static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池 如图:我们创建一个大小为3的线程池将前三个任务提交上去 可以看到当前排队的线程为0工作中的线程为3而当我们要提交下面的任务时当前面的线程没结束它就会排队等待只有当前面的线程运行完毕他们才能工作 3.自定义线程池
当我们查看newFixedThreadPool时可以看到它的底层是创建了一个ThreadPoolExecutor对象 ThreadPoolExecutor才是真正的线程池对象它相比于前面的线程池来说更加灵活
⑴ThreadPoolExecutor ThreadPoolExecutor(int corePoolSize , int maximumPoolSize , long keepAliveTime , TimeUnit unit , BlockingQueueRunnable workQueue , ThreadFactory threadFactory , RejectedExecutionHandler handler) 用给定的初始参数创建线程池 其中共有7个参数
参数一:核心线程数
参数二:最大线程数量
参数三:等待的空闲时间
参数四:时间单位
参数五:任务队列
参数六:创建线程工厂
参数七:要执行的任务过多时的解决方案
其中当我们提交的线程多于核心线程多出的线程会等待
若线程数量超出最大线程数那么会创建临时线程(最大线程数-核心线程数)让多出的线程工作
若线程数量超出最大线程数临时线程那么会对超出的线程进行处理 其中的处理方式有四种分别被定义为内部类 ⑵线程池实现
线程池的大小并不是我们随意规定的而是需要通过公式计算出来的
①CPU密集型运算
当我们的项目中计算多而读取文件少就要此方式类计算 最大并行数1 最大并行数与我们电脑CPU的型号相关因为操作系统不会把所有的线程给同一个软件因此我们通常利用Java虚拟机来计算最大并行数 ②I/O密集型运算
当我们的项目计算少读取数据多那么就使用此类方式计算 最大并行数×期望CPU利用率×(总时间CPU计算时间) ③代码书写