网站后台管理系统的重要技术指标,网络工程师和网站开发员,石牌桥网站建设,wordpress只准许用户访问个人中心多线程与线程池一、多线程安全与应用1、程序、进程与线程的关系2、创建多线程的三种方式#xff08;1#xff09;继承Thread类创建线程【不推荐】#xff08;2#xff09;实现Runnable接口创建线程#xff08;3#xff09;Callable接口创建线程3、线程的生命周期4、初识线…
多线程与线程池一、多线程安全与应用1、程序、进程与线程的关系2、创建多线程的三种方式1继承Thread类创建线程【不推荐】2实现Runnable接口创建线程3Callable接口创建线程3、线程的生命周期4、初识线程同步并使用sychronized实现线程同步1初识线程同步2sychronized的锁对象5、死锁产生的原因与解决方式6、初识线程池及其基本使用1Runnable接口的弊端2线程复用的理念ThreadPool线程池JUC支持的线程池种类二、线程池1、线程池——治理线程的最大法宝1什么是“池”2线程池3线程池适合应用的场合2、创建和停止线程池3、线程池应该手动创建还是自动创建1四种线程池自动创建弊端2正确的创建线程池的方法3线程池里的线程数量设定为多少比较合适**4常见线程池的特点**4、拒绝策略与钩子方法5、线程池实现原理6、 **线程池五种状态**三、ThreadLocal2、典型场景23、ThreadLocal的两个作用**4、ThreadLocal主要方法**5、Thread、ThreadLocal以及ThreadLocalMap三者之间的关系6、ThreadLocal注意点1、内存泄漏2、Value的泄漏3、如何避免内存泄漏一、多线程安全与应用
1、程序、进程与线程的关系
进程是程序运行的实例 当一个程序执行进入内存运行时即变成一个进程 进程的资源是彼此隔离的其他进程不允许访问 线程是进程内执行的“任务” 线程是进程内的一个“基本任务”每个线程都有自己的功能是CPU分配与调度的基本单位 一个进程内可以包含多个线程反之一个线程只能隶属于某一个线程 进程内至少拥有一个“线程”这个线程叫“主线程”主线程消亡则进程结束 单核CPU通过时间片使所有线程达到一个并发执行的效果 多核CPU达到并行执行的效果
2、创建多线程的三种方式
1继承Thread类创建线程【不推荐】
package org.example;import java.util.Random;public class ThreadExample1 {class Runner extends Thread {Overridepublic void run() {Integer speed new Random().nextInt(10);for (int i 1; i 10; i) {System.out.println(第 i 秒: this.getName() 跑到: (i * speed));}}}public void start() {Runner runnerA new Runner();runnerA.setName(参赛者A);Runner runnerB new Runner();runnerB.setName(参赛者B);runnerA.start();runnerB.start();}// 主线程存在进程存在主线程消失进程消失public static void main(String[] args) {new ThreadExample1().start();}
}
当调用thread.start()方法时线程对象A和B中的run方法执行权移交给了操作系统由操作系统的线程调度策略来决定先执行哪个方法
2实现Runnable接口创建线程
package org.example;import java.util.Random;public class ThreadExample2 {class Runner implements Runnable {Overridepublic void run() {Integer speed new Random().nextInt(10);for (int i 1; i 10; i) {System.out.println(第 i 秒: Thread.currentThread().getName() 跑到: (i * speed));}}}public void start() {Runner runnerA new Runner();Thread threadA new Thread(runnerA);threadA.setName(参赛者A);Runner runnerB new Runner();Thread threadB new Thread(runnerB);threadB.setName(参赛者B);threadA.start();threadB.start();}// 主线程存在进程存在主线程消失进程消失public static void main(String[] args) {new ThreadExample2().start();}
}
3Callable接口创建线程
package org.example;import java.util.Random;
import java.util.concurrent.*;public class ThreadExample3 {// callable线程执行后将结果返回class Runner implements CallableInteger {public String name;Overridepublic Integer call() throws Exception {Integer speed new Random().nextInt(10);Integer result 0;for (int i 1; i 10; i) {System.out.println(第 i 秒: this.name 跑到: (i * speed));result i * speed;}return result;}}public void start() throws ExecutionException, InterruptedException {ExecutorService executorService Executors.newFixedThreadPool(3);Runner threadA new Runner();threadA.name 参赛者A;Runner threadB new Runner();threadB.name 参赛者B;Runner threadC new Runner();threadC.name 参赛者C;FutureInteger submit1 executorService.submit(threadA);FutureInteger submit2 executorService.submit(threadB);FutureInteger submit3 executorService.submit(threadC);executorService.shutdown();System.out.println(threadA.name 累计跑了 submit1.get() 米);System.out.println(threadB.name 累计跑了 submit2.get() 米);System.out.println(threadC.name 累计跑了 submit3.get() 米);}// 主线程存在进程存在主线程消失进程消失public static void main(String[] args) throws ExecutionException, InterruptedException {new ThreadExample3().start();}
}3、线程的生命周期
Thread类定义的线程六种状态
public enum State {/*** Thread state for a thread which has not yet started.*/NEW,/*** Thread state for a runnable thread. A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.* A thread in the blocked state is waiting for a monitor lock* to enter a synchronized block/method or* reenter a synchronized block/method after calling* {link Object#wait() Object.wait}.*/BLOCKED,/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* ul* li{link Object#wait() Object.wait} with no timeout/li* li{link #join() Thread.join} with no timeout/li* li{link LockSupport#park() LockSupport.park}/li* /ul** pA thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called {code Object.wait()}* on an object is waiting for another thread to call* {code Object.notify()} or {code Object.notifyAll()} on* that object. A thread that has called {code Thread.join()}* is waiting for a specified thread to terminate.*/WAITING,/*** Thread state for a waiting thread with a specified waiting time.* A thread is in the timed waiting state due to calling one of* the following methods with a specified positive waiting time:* ul* li{link #sleep Thread.sleep}/li* li{link Object#wait(long) Object.wait} with timeout/li* li{link #join(long) Thread.join} with timeout/li* li{link LockSupport#parkNanos LockSupport.parkNanos}/li* li{link LockSupport#parkUntil LockSupport.parkUntil}/li* /ul*/TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*/TERMINATED;}4、初识线程同步并使用sychronized实现线程同步
1初识线程同步
代码中的同步机制
sychronized同步锁关键字的作用就是利用一个特定的对象设置一个锁绣球在多线程游客并发访问的时候同时只允许一个线程游客可以获得这个锁执行特定的代码迎娶新娘。执行后释放锁继续由其他线程争抢。
2sychronized的锁对象
sychronized代码块任意对象即可sychronized方法this当前对象【实际使用更多】sychronized静态方法该类的字节码对象
package org.example;public class SyncSample {class Printer {Object lock new Object();public void print() throws InterruptedException {// 代码块synchronized (lock) {Thread.sleep(1000);System.out.println(你);Thread.sleep(1000);System.out.println(好);Thread.sleep(1000);System.out.println(北);Thread.sleep(1000);System.out.println(京);}}// 方法【实际使用更广泛】public synchronized void print2() throws InterruptedException {// 代码块Thread.sleep(1000);System.out.println(你);Thread.sleep(1000);System.out.println(好);Thread.sleep(1000);System.out.println(北);Thread.sleep(1000);System.out.println(京);}// 方法【实际使用更广泛】public synchronized void print3() throws InterruptedException {// 代码块Thread.sleep(1000);System.out.println(你);Thread.sleep(1000);System.out.println(好);Thread.sleep(1000);System.out.println(北);Thread.sleep(1000);System.out.println(京);}}class PrintTask implements Runnable {private Printer printer;Overridepublic void run() {try {printer.print2();} catch (InterruptedException e) {throw new RuntimeException(e);}}}public void start() {Printer printer new Printer();for(int i 0; i 10; i) {PrintTask printTask new PrintTask();printTask.printer printer;Thread thread new Thread(printTask);thread.start();}}public static void main(String[] args) {new SyncSample().start();}
}5、死锁产生的原因与解决方式
死锁是在多线程情况下最严重的问题在多线程对公共资源文件、数据等进行操作时彼此不释放自己的资源而去试图操作其他线程的资源而形成交叉引用就会产生死锁。
解决死锁最根本的建议是:
尽量减少公共资源的引用用完马上释放公共资源增加超时失败机制
6、初识线程池及其基本使用
并发是伴随着多核处理器的诞生而产生的为了充分利用硬件资源诞生了多线程技术。但是多线程又存在资源竞争的问题引发了同步和互斥的问题JDK1.5推出的java.util.concurrent并发工具包来解决这些问题。
1Runnable接口的弊端
Runnable新建线程性能差线程缺乏统一管理可能无限制的新建线程相互竞争严重时会占有过多系统资源导致死机或内存溢出
2线程复用的理念
ThreadPool线程池
重用存在的线程减少线程对象创建、消亡的开销线程总数可控提高资源的利用率提供额外功能定时执行、定期执行、监控等
JUC支持的线程池种类
在Java.util.concurrent中提供了工具类Executors调度器对象来创建线程池可创建的线程池有四种
1、FixedThreadPool定长线程池
public static void main(String[] args) {// 创建一个定长线程池// 定长线程池的特点是固定线程总数空闲线程用于执行任务如果线程都在使用后续任务则处理等待状态ExecutorService threadPool Executors.newFixedThreadPool(10);for(int i 0; i 100; i) {// 执行runnable对象final int index i;threadPool.execute(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() --- index);}});}// 需要返回值使用submit callable接口实现threadPool.shutdown();}2、CachedThreadPool可缓存线程池
// 创建可缓存线程池ExecutorService threadPool Executors.newCachedThreadPool();// 可缓存线程池的特点无限大如果线程中没有可用的线程则创建有空闲则利用起来for(int i 0; i 100; i) {// 执行runnable对象final int index i;threadPool.execute(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() --- index);}});}// 需要返回值使用submit callable接口实现threadPool.shutdown();3、SingleThreadPool单线程池
4、ScheduledThreadPool调度线程池
public static void main(String[] args) {// 创建可调度线程池定时执行任务ScheduledExecutorService threadPool Executors.newScheduledThreadPool(5);threadPool.scheduleAtFixedRate(new Runnable() {Overridepublic void run() {System.out.println(new Date() 延迟执行1秒每三秒执行一次);}}, 1, 3, TimeUnit.SECONDS);}二、线程池
1、线程池——治理线程的最大法宝
1什么是“池”
需要的时候可以快速使用不用重新收集
2线程池
如果不使用线程池每个任务都新开一个线程处理加快响应速度合理利用CPU和内存统一管理
3线程池适合应用的场合
批量计算任务、服务器处理请求、excel解析 实际上在开发中如果需要创建5个以上的线程那么就可以使用线程池来管理
2、创建和停止线程池
1线程池构造方法的参数
参数名类型含义corePoolSizeint核心线程数maxPoolSizeint最大线程数keepAliveTimelong保持存活时间workQueueBlockingQueue任务存储队列threadFactorythreadFactory当线程池需要新的线程的时候会使用ThreadFactory来创建新的线程HandlerRejectExecutionHandler当线程池无法接受你所提交的任务的拒绝策略
corePoolSize指的是核心线程数线程池在完成初始化后默认情况下线程池中并没有任何线程线程池会等待有任务到来时再创建新线程去执行任务最大量maxPoolSize在核心线程数的基础上额外增加的线程数的上限添加线程规则 最开始线程总数小于corePoolSize时即使有线程处于空闲状态新任务到来时也会创建一个新线程直到线程数等于corePoolSize再来任务时会把任务放入队列去等待直到队列也满了如果此时线程数小于maxPoolSize则会再创建新线程来执行任务如果队列已满并且线程数已经扩大到等于maxPoolSize时再尝试添加任务时会被拒绝
2增减线程的特点
通过设置corePoolSize和maxPoolSize相同就可以创建固定大小的线程池线程池希望保持较少的线程数并且只有在负载变得很大时才增加它通过设置maxPoolSize为很高的值可以允许线程池容纳任意数量的并发任务只有在队列填满时才创建多于corePoolSize的线程如果使用的是无界队列那么线程数就不会超过corePoolSize
3线程存活时间keepAliveTime 如果线程池当前的线程数多于corePoolSize那么如果多于的线程空闲时间超过keepAliveTime它们就会被终止
4创建线程ThreadFactory 默认使用Executors.defaultThreadFactory() 创建出来的线程都在同一个线程组 如果自己指定ThreadFactory那么就可以改变线程名、线程组、优先级是否是守护线程等 通常情况下使用默认的线程工厂基本上就可以满足绝大多数的需要
5工作队列 有3种最常见的队列类型
直接交换SynchronousQueue无界队列LinkedBlockingQueue有界队列ArrayBlockingQueue
3、线程池应该手动创建还是自动创建
1四种线程池自动创建弊端
newFixedThreadPool容易造成大量内存占用可能会导致OOMnewSingleThreadExecutor当请求堆积的时候可能会占用大量的内存CachedThreadPool可缓存线程池 特点具有自动回收多余线程的功能 弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE这可能会创建数量非常多的线程甚至导致OOMnewScheduledThreadPool
2正确的创建线程池的方法
根据不同的业务场景设置线程池参数比如内存有多大给线程取什么名字等等
3线程池里的线程数量设定为多少比较合适 CPU密集型加密、计算hash等最佳线程数为CPU核心数的1-2倍 耗时IO型读写数据库、文件、网络读写等最佳线程数一般会大于CPU核心数很多倍 参考Brain Goetz推荐的计算方法线程数 CPU核心数*1 平均等待时间/平均工作时间4常见线程池的特点 FixedThreadPool 核心线程池和最大线程数相同队列满后无法再增加线程 CachedThreadPool 具有自动回收多余线程的功能队列没有容量线程数可以很多 ScheduledThreadPool 支持定时及周期性任务执行的线程池 SingleThreadExecutor 只会用唯一的工作线程来执行任务用到的场景并不多
5停止线程池的正确方法 1、shutdown 运行这个方法之后并不一定会停止事实上这个方法仅仅是初始化整个关闭过程。运行这个方法会把正在执行的和队列中等待的都执行完毕之后再关闭但是不会再增加任务。 2、isShutdown 3、isTerminated线程都结束了返回true 4、awaitTermination 5、shutdownNow
4、拒绝策略与钩子方法
任务太多怎么拒绝
拒绝时机 当Executor关闭时提交新任务会被拒绝以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时
4种拒绝策略
AbortPlicy抛出异常DiscardPolicy默默丢弃DiscardOlderPolicy丢弃掉老的CallerRunsPolicy
钩子方法给线程池加点料
每个执行任务前后日志、统计
5、线程池实现原理
1线程池组成部分
线程池管理器工作线程任务队列任务接口Task
2Executor家族
线程池、ThreadPoolExecutor、ExecutorService、Executor、Executors等这么多和线程池先关的类都是什么关系Executor是一个接口里面只有一个方法executeExecutorService继承Executor增加了一些新的方法拥有初步管理线程池的方法Executors工具类帮我们快速创建线程池用的 3线程池实现任务复用的原理
相同线程执行不同任务runWorker 方法进入到源码
6、 线程池五种状态 1execute方法执行任务 2使用线程池的注意点
手动创建线程池线程数合理考虑多个线程池间的影响
三、ThreadLocal
2、典型场景2
用ThreadLocal保存一些业务内容用户权限信息、从用户系统获取到的用户名、userId等这些信息在同一个线程内相同但是不同的线程使用的业务内容是不相同的在线程生命周期内都通过这个静态ThreadLocal实例的get()方法取的自己set过的那个对象避免了将这个对象作为参数传递的麻烦强调同一个请求同一个线程内不同方法间的共享不需要重写initialValue()方法但是必须手动调用set()方法
public class ThreadLocalSample2 {public static void main(String[] args) {Service1 service1 new Service1();service1.process();new Service2().process();}
}class Service1 {public void process() {User user new User(三三);UserHolderContext.holder.set(user);}
}class Service2 {public void process() {UserHolderContext.holder.get();}
}class UserHolderContext {public static ThreadLocalUser holder new ThreadLocal();
}
class User {String name;public User(String name) {}public String getName() {return name;}public void setName(String name) {this.name name;}
}3、ThreadLocal的两个作用
1、让某个需要用到的对象在线程间隔离每个线程都有自己独立的对象 2、在任何方法中都可以轻松获取到该对象
场景一initialValue 在ThreadLocal第一次get的时候把对象初始化出来对象的初始化时机可以由我们控制
场景二set ThreadLocal里面对象的生成时机不由我们控制例如拦截器生成的用户信息用ThreadLocal.set直接放到ThreadLocal中以便后续使用
使用ThreadLocal带来的好处 达到线程安全 不需要加锁提高执行效率 更高效地利用内存、节省开销 相比于每个任务都新建一个SimpleDateFormat显然用ThreadLocal可以节省内存和开销 免去传参的繁琐 –不需要每次都传同样的参数 –ThreadLocal使得代码耦合度更低、更优雅
4、ThreadLocal主要方法
1initialValue() 初始化
该方法会返回当前线程对应的“初始值”这是一个延迟加载的方法只有在调用get的时候才会触发当线程第一次使用get方法访问变量时将调用此方法每个线程最多调用一次此方法但如果已经调用了remove()后再调用get()则可以再次调用此方法如果不重写本方法这个方法会返回null。一般使用匿名内部类的方法来重写initialValue()方法
2void set(T t)为这个线程设置一个新值
3T get()得到这个线程对应的Value如果是首次调用get()则会调用initialize来得到这个值
4void remove()删除对应这个线程的值
5、Thread、ThreadLocal以及ThreadLocalMap三者之间的关系 6、ThreadLocal注意点
1、内存泄漏
某个对象不再使用但是占有的内存无法回收
2、Value的泄漏
1ThreadLocalMap的每个Entry都是一个对key的弱引用同时每个Entry都包含一个value的强引用 2正常情况下当一个线程终止保存在ThreadLocal里的value会被垃圾回收因为没有任何强引用了 3但是如果线程不终止需要保持很久那么key对应的value就不能被回收这种情况下就会发生内存泄漏就可能出现OOM
3、如何避免内存泄漏
调用remove方法就会删除对应的Entry对象可以避免内存泄漏所以使用ThreadLocal之后应该调用remove()方法
【注学习来源——慕课网】