广西商城网站建设,seo专员的工作内容,wordpress超联系,免费搭建个人服务器作者 | 永远在路上【】责编 | 胡巍巍出品 | CSDN博客线程线程的概念#xff0c;百度是这样解释的#xff1a;线程(英语#xff1a;Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中#xff0c;是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的… 作者 | 永远在路上【】责编 | 胡巍巍出品 | CSDN博客线程线程的概念百度是这样解释的线程(英语Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流一个进程中可以并发多个线程每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(Lightweight Processes)但轻量进程更多指内核线程(Kernel Thread)而把用户线程(User Thread)称为线程。1.1 线程与进程的区别进程指在系统中正在运行的一个应用程序程序一旦运行就是进程进程——资源分配的最小单位。线程系统分配处理器时间资源的基本单元或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。也就是进程可以包含多个线程而线程是程序执行的最小单位。1.2 线程的状态NEW线程刚创建RUNNABLE: 在JVM中正在运行的线程其中运行状态可以有运行中RUNNING和READY两种状态由系统调度进行状态改变。BLOCKED线程处于阻塞状态等待监视锁可以重新进行同步代码块中执行WAITING : 等待状态TIMED_WAITING: 调用sleep() join() wait()方法可能导致线程处于等待状态TERMINATED: 线程执行完毕已经退出1.3 Notify和Wait Notify和Wait 的作用首先看源码给出的解释这里翻译了一下Notify唤醒一个正在等待这个对象的线程监控。如果有任何线程正在等待这个对象那么它们中的一个被选择被唤醒。选择是任意的发生在执行的酌情权。一个线程等待一个对象通过调用一个{code wait}方法进行监视。Notify()需要在同步方法或同步块中调用即在调用前线程也必须获得该对象的对象级别锁Wait导致当前线程等待直到另一个线程调用{link java.lang.Object#notify()}方法或{link java.lang.Object#notifyAll()}方法。换句话说这个方法的行为就像它简单一样执行调用{code wait(0)}。当前线程必须拥有该对象的监视器。线程释放此监视器的所有权并等待另一个线程通知等待该对象的监视器的线程唤醒通过调用{code notify}方法或{code notifyAll}方法。然后线程等待直到它可以重新取得监视器的所有权然后继续执行。Wait()的作用是使当前执行代码的线程进行等待它是Object类的方法该方法用来将当前线程置入预执行队列中并且在Wait所在的代码行处停止执行直到接到通知或被中断为止。在调用Wait方法之前线程必须获得该对象的对象级别锁即只能在同步方法或同步块中调用Wait方法。Wait和Sleep的区别它们最大本质的区别是Sleep()不释放同步锁Wait()释放同步锁。还有用法的上的不同是Sleep(milliseconds)可以用时间指定来使他自动醒过来如果时间不到你只能调用Interreput()来强行打断Wait()可以用Notify()直接唤起。这两个方法来自不同的类分别是Thread和Object最主要是Sleep方法没有释放锁而Wait方法释放了锁使得其他线程可以使用同步控制块或者方法。1.4 Thread.sleep() 和Thread.yield()的异同相同 Sleep()和yield()都会释放CPU。不同Sleep()使当前线程进入停滞状态所以执行Sleep()的线程在指定的时间内肯定不会执行yield()只是使当前线程重新回到可执行状态所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。Sleep()可使优先级低的线程得到执行的机会当然也可以让同优先级和高优先级的线程有执行的机会yield()只能使同优先级的线程有执行的机会。1.5 补充死锁的概念死锁指两个或两个以上的进程(或线程)在执行过程中因争夺资源而造成的一种互相等待的现象若无外力作用它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁这些永远在互相等待的进程称为死锁进程。死锁产生的四个必要条件(缺一不可)互斥条件顾名思义线程对资源的访问是排他性当该线程释放资源后下一线程才可进行占用。请求和保持简单来说就是自己拿的不放手又等待新的资源到手。线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求而此时资源R2被其他线程T2占用于是该线程T1也必须等待但又对自己保持的资源R1不释放。不可剥夺在没有使用完资源时其他线性不能进行剥夺。循环等待一直等待对方线程释放资源。我们可以根据死锁的四个必要条件破坏死锁的形成。1.6 补充并发和并行的区别并发是指在某个时间段内多任务交替的执行任务。当有多个线程在操作时把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行。在一个时间段的线程代码运行时其它线程处于挂起状。并行是指同一时刻同时处理多任务的能力。当有多个线程在操作时CPU同时处理这些线程请求的能力。区别就在于CPU是否能同时处理所有任务并发不能并行能。1.7 补充线程安全三要素原子性Atomic包、CAS算法、Synchronized、Lock。可见性Synchronized、Volatile(不能保证原子性)。有序性Happens-before规则。1.8 补充如何实现线程安全互斥同步Synchronized、Lock。非阻塞同步CAS。无需同步的方案如果一个方法本来就不涉及共享数据那它自然就无需任何同步操作去保证正确性。1.9 补充保证线程安全的机制Synchronized关键字LockCAS、原子变量ThreadLocl简单来说就是让每个线程对同一个变量都有自己的独有副本每个线程实际访问的对象都是自己的自然也就不存在线程安全问题了。VolatileCopyOnWrite写时复制随着CPU核心的增多以及互联网迅速发展单线程的程序处理速度越来越跟不上发展速度和大数据量的增长速度多线程应运而生充分利用CPU资源的同时极大提高了程序处理速度。创建线程的方法继承Thread类public class ThreadCreateTest {public static void main(String[] args) {new MyThread().start(); }}class MyThread extends Thread {Overridepublic void run() { System.out.println(Thread.currentThread().getName() \t Thread.currentThread().getId()); }}实现Runable接口public class RunableCreateTest {public static void main(String[] args) { MyRunnable runnable new MyRunnable();new Thread(runnable).start(); }}class MyRunnable implements Runnable {Overridepublic void run() { System.out.println(Thread.currentThread().getName() \t Thread.currentThread().getId()); }}通过Callable和Future创建线程public class CallableCreateTest {public static void main(String[] args) throws Exception {// 将Callable包装成FutureTaskFutureTask也是一种Runnable MyCallable callable new MyCallable(); FutureTask futureTask new FutureTask(callable);new Thread(futureTask).start();// get方法会阻塞调用的线程 Integer sum futureTask.get(); System.out.println(Thread.currentThread().getName() Thread.currentThread().getId() sum); }}class MyCallable implements CallableInteger {Overridepublic Integer call() throws Exception { System.out.println(Thread.currentThread().getName() \t Thread.currentThread().getId() \t new Date() \tstarting...);int sum 0;for (int i 0; i 100000; i) { sum i; } Thread.sleep(5000); System.out.println(Thread.currentThread().getName() \t Thread.currentThread().getId() \t new Date() \tover...);return sum; }}线程池方式创建实现Runnable接口这种方式更受欢迎因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下这需要多继承(而Java不支持多继承但可以多实现啊)只能实现接口。同时线程池也是非常高效的很容易实现和使用。实际开发中阿里巴巴开发插件一直提倡使用线程池创建线程原因在下方会解释所以上面的代码我就只简写了一些Demo。2.1 线程池创建线程线程池顾名思义线程存放的地方。和数据库连接池一样存在的目的就是为了较少系统开销主要由以下几个特点降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗(主要)。提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源如果无限制地创建不仅会消耗系统资源还会降低系统的稳定性。Java提供四种线程池创建方式newCachedThreadPool创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程。newFixedThreadPool创建一个定长线程池可控制线程最大并发数超出的线程会在队列中等待。newScheduledThreadPool创建一个定长线程池支持定时及周期性任务执行。newSingleThreadExecutor创建一个单线程化的线程池它只会用唯一的工作线程来执行任务保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。通过源码我们得知ThreadPoolExecutor继承自AbstractExecutorService而AbstractExecutorService实现了ExecutorService。public class ThreadPoolExecutor extends AbstractExecutorServicepublic abstract class AbstractExecutorService implements ExecutorService2.2 ThreadPoolExecutor介绍实际项目中用的最多的就是ThreadPoolExecutor这个类而《阿里巴巴Java开发手册》中强制线程池不允许使用Executors去创建而是通过New ThreadPoolExecutor实例的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险。我们从ThreadPoolExecutor入手多线程创建方式先看一下线程池创建的最全参数。 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {if (corePoolSize 0 || maximumPoolSize 0 || maximumPoolSize keepAliveTime 0)throw new IllegalArgumentException();if (workQueue null || threadFactory null || handler null)throw new NullPointerException();this.corePoolSize corePoolSize;this.maximumPoolSize maximumPoolSize;this.workQueue workQueue;this.keepAliveTime unit.toNanos(keepAliveTime);this.threadFactory threadFactory;this.handler handler; }参数说明如下corePoolSize线程池的核心线程数即便线程池里没有任何任务也会有corePoolSize个线程在候着等任务。maximumPoolSize最大线程数不管提交多少任务线程池里最多工作线程数就是maximumPoolSize。keepAliveTime线程的存活时间。当线程池里的线程数大于corePoolSize时如果等了keepAliveTime时长还没有任务可执行则线程退出。Unit这个用来指定keepAliveTime的单位比如秒TimeUnit.SECONDS。BlockingQueue一个阻塞队列提交的任务将会被放到这个队列里。threadFactory线程工厂用来创建线程主要是为了给线程起名字默认工厂的线程名字pool-1-thread-3。handler拒绝策略当线程池里线程被耗尽且队列也满了的时候会调用。2.2.1BlockingQueue对于BlockingQueue个人感觉还需要单独拿出来说一下。BlockingQueue阻塞队列有先进先出(注重公平性)和先进后出(注重时效性)两种常见的有两种阻塞队列ArrayBlockingQueue和LinkedBlockingQueue队列的数据结构大致如图队列一端进入一端输出。而当队列满时阻塞。BlockingQueue核心方法1. 放入数据put2. 获取数据take。常见的两种Queue2.2.2 ArrayBlockingQueue基于数组实现在ArrayBlockingQueue内部维护了一个定长数组以便缓存队列中的数据对象这是一个常用的阻塞队列除了一个定长数组外ArrayBlockingQueue内部还保存着两个整形变量分别标识着队列的头部和尾部在数组中的位置。一段代码来验证一下 package map;import java.util.concurrent.*;public class MyTestMap {// 定义阻塞队列大小private static final int maxSize 5;public static void main(String[] args){ ArrayBlockingQueue queue new ArrayBlockingQueue(maxSize);new Thread(new Productor(queue)).start();new Thread(new Customer(queue)).start(); } }class Customer implements Runnable {private BlockingQueue queue; Customer(BlockingQueue queue) {this.queue queue; } Overridepublic void run() {this.cusume(); }private void cusume() {while (true) {try {int count (int) queue.take(); System.out.println(customer正在消费第 count 个商品);// 只是为了方便观察输出结果 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }class Productor implements Runnable {private BlockingQueue queue;private int count 1; Productor(BlockingQueue queue) {this.queue queue; } Overridepublic void run() {this.product(); }private void product() {while (true) {try {queue.put(count); System.out.println(生产者正在生产第 count 个商品); count; } catch (InterruptedException e) { e.printStackTrace(); } } } }//输出如下/**生产者正在生产第1个商品生产者正在生产第2个商品生产者正在生产第3个商品生产者正在生产第4个商品生产者正在生产第5个商品customer正在消费第1个商品*/2.2.3 LinkedBlockingQueue基于链表的阻塞队列内部也维护了一个数据缓冲队列。需要我们注意的是如果构造一个LinkedBlockingQueue对象而没有指定其容量大小。LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE)这样的话如果生产者的速度一旦大于消费者的速度也许还没有等到队列满阻塞产生系统内存就有可能已被消耗殆尽了。2.2.4 LinkedBlockingQueue和ArrayBlockingQueue的主要区别ArrayBlockingQueue的初始化必须传入队列大小LinkedBlockingQueue则可以不传入。ArrayBlockingQueue用一把锁控制并发LinkedBlockingQueue俩把锁控制并发锁的细粒度更细。即前者生产者消费者进出都是一把锁后者生产者生产进入是一把锁消费者消费是另一把锁。ArrayBlockingQueue采用数组的方式存取LinkedBlockingQueue用Node链表方式存取。2.2.5handler拒绝策略Java提供了4种丢弃处理的方法当然你也可以自己实现主要是要实现接口RejectedExecutionHandler中的方法。AbortPolicy不处理直接抛出异常。CallerRunsPolicy只用调用者所在线程来运行任务即提交任务的线程。DiscardOldestPolicyLRU策略丢弃队列里最近最久不使用的一个任务并执行当前任务。DiscardPolicy不处理丢弃掉不抛出异常。2.2.6线程池五种状态 private static final int RUNNING -1 private static final int SHUTDOWN 0 private static final int STOP 1 private static final int TIDYING 2 private static final int TERMINATED 3 RUNNING在这个状态的线程池能判断接受新提交的任务并且也能处理阻塞队列中的任务。SHUTDOWN处于关闭的状态该线程池不能接受新提交的任务但是可以处理阻塞队列中已经保存的任务在线程处于RUNNING状态调用shutdown()方法能切换为该状态。STOP线程池处于该状态时既不能接受新的任务也不能处理阻塞队列中的任务并且能中断现在线程中的任务。当线程处于RUNNING和SHUTDOWN状态调用shutdownNow()方法就可以使线程变为该状态。TIDYING在SHUTDOWN状态下阻塞队列为空且线程中的工作线程数量为0就会进入该状态当在STOP状态下时只要线程中的工作线程数量为0就会进入该状态。TERMINATED在TIDYING状态下调用terminated()方法就会进入该状态。可以认为该状态是最终的终止状态。回到线程池创建ThreadPoolExecutor我们了解了这些参数再来看看ThreadPoolExecutor的内部工作原理判断核心线程是否已满是进入队列否创建线程判断等待队列是否已满是查看线程池是否已满否进入等待队列查看线程池是否已满是拒绝否创建线程2.3深入理解ThreadPoolExecutor进入Execute方法可以看到 public void execute(Runnable command) {if (command null)throw new NullPointerException();int c ctl.get();//判断当前活跃线程数是否小于corePoolSize,如果小于则调用addWorker创建线程执行任务if (workerCountOf(c) if (addWorker(command, true))return; c ctl.get(); }//如果不小于corePoolSize则将任务添加到workQueue队列。if (isRunning(c) workQueue.offer(command)) {int recheck ctl.get();if (! isRunning(recheck) remove(command)) reject(command);else if (workerCountOf(recheck) 0) addWorker(null, false); }//如果放入workQueue失败则创建线程执行任务如果这时创建线程失败(当前线程数不小于maximumPoolSize时)就会调用reject(内部调用handler)拒绝接受任务。else if (!addWorker(command, false)) reject(command); }AddWorker方法创建Worker对象同时也会实例化一个Thread对象。在创建Worker时会调用threadFactory来创建一个线程。然后启动这个线程。2.3.1线程池中CTL属性的作用是什么看源码第一反应就是这个CTL到底是个什么东东有啥用一番研究得出如下结论CTL属性包含两个概念 private final AtomicInteger ctl new AtomicInteger(ctlOf(RUNNING, 0));private static int ctlOf(int rs, int wc) { return rs | wc; }runState即rs 表明当前线程池的状态是否处于RunningShutdownStopTidying。workerCount即wc表明当前有效的线程数。我们点击workerCount即工作状态记录值以RUNNING为例RUNNING -1 COUNT_BITS;即-1无符号左移COUNT_BITS位进一步我们得知COUNT_BITS位29因为Integer位数为31位(2的五次方减一) private static final int COUNT_BITS Integer.SIZE - 3;既然是29位那么就是Running的值为1110 0000 0000 0000 0000 0000 0000 0000 |||31~29位那低28位呢就是记录当前线程的总线数啦 // Packing and unpacking ctlprivate static int runStateOf(int c) { return c ~CAPACITY; }private static int workerCountOf(int c) { return c CAPACITY; }private static int ctlOf(int rs, int wc) { return rs | wc; }从上述代码可以看到workerCountOf这个函数传入ctl之后是通过CTLCAPACITY操作来获取当前运行线程总数的。也就是RunningState|WorkCountCAPACITY算出来的就是低28位的值。因为CAPACITY得到的就是高3位(29-31位)位0低28位(0-28位)都是1所以得到的就是ctl中低28位的值。而runStateOf这个方法的话算的就是RunningState|WorkCountCAPACITY高3位的值因为CAPACITY是CAPACITY的取反所以得到的就是高3位(29-31位)为1低28位(0-28位)为0所以通过运算后所得到的值就是高3为的值。简单来说就是ctl中是高3位作为状态值低28位作为线程总数值来进行存储。2.3.2 shutdownNow和shutdown的区别看源码发现有两种近乎一样的方法shutdownNow和shutdown设计者这么设计自然是有它的道理那么这两个方法的区别在哪呢shutdown会把线程池的状态改为SHUTDOWN而shutdownNow把当前线程池状态改为STOP。shutdown只会中断所有空闲的线程而shutdownNow会中断所有的线程。shutdown返回方法为空会将当前任务队列中的所有任务执行完毕而shutdownNow把任务队列中的所有任务都取出来返回。2.3.3 线程复用原理final void runWorker(Worker w) { Thread wt Thread.currentThread(); Runnable task w.firstTask; w.firstTask null; w.unlock(); // allow interrupts boolean completedAbruptly true;try {while (task ! null || (task getTask()) ! null) { w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted. This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() runStateAtLeast(ctl.get(), STOP))) !wt.isInterrupted()) wt.interrupt();try { beforeExecute(wt, task); Throwable thrown null;try { task.run(); } catch (RuntimeException x) { thrown x; throw x; } catch (Error x) { thrown x; throw x; } catch (Throwable x) { thrown x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task null; w.completedTasks; w.unlock(); } } completedAbruptly false; } finally { processWorkerExit(w, completedAbruptly); } }就是任务在并不只执行创建时指定的firstTask第一任务还会从任务队列的中自己主动取任务执行而且是有或者无时间限定的阻塞等待以保证线程的存活。默认的是不允许。2.4 CountDownLatch和CyclicBarrier区别countDownLatch是一个计数器线程完成一个记录一个计数器递减只能只用一次。CyclicBarrier的计数器更像一个阀门需要所有线程都到达然后继续执行计数器递增提供Reset功能可以多次使用。3. 多线程间通信的几种方式提及多线程又不得不提及多线程通信的机制。首先要短信线程间通信的模型有两种共享内存和消息传递以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析题目有两个线程A、BA线程向一个集合里面依次添加元素abc字符串一共添加十次当添加到第五次的时候希望B线程能够收到A线程的通知然后B线程执行相关的业务操作。3.1使用volatile关键字package thread;/** * * author hxz * description 多线程测试类 * version 1.0 * data 2020年2月15日 上午9:10:09 */public class MyThreadTest {public static void main(String[] args) throws Exception { notifyThreadWithVolatile(); }/** * 定义一个测试 */private static volatile boolean flag false;/** * 计算I当I5时通知线程B * throws Exception */private static void notifyThreadWithVolatile() throws Exception { Thread thc new Thread(线程A){Overridepublic void run() {for (int i 0; i 10; i) {if (i 5) { flag true;try { Thread.sleep(500L); } catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace(); }break; } System.out.println(Thread.currentThread().getName() i); } } }; Thread thd new Thread(线程B) {Overridepublic void run() {while (true) {// 防止伪唤醒 所以使用了whilewhile (flag) { System.out.println(Thread.currentThread().getName() 收到通知); System.out.println(do something);try { Thread.sleep(500L); } catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace(); }return ; } } } }; thd.start(); Thread.sleep(1000L); thc.start(); }}个人认为这是基本上最好的通信方式因为A发出通知B能够立马接受并Do Something。原文链接https://blog.csdn.net/weixin_44104367/article/details/104481510【End】《原力计划【第二季】- 学习力挑战》正式开始即日起至 3月21日千万流量支持原创作者更有专属【勋章】等你来挑战推荐阅读 ☞深度好文 | 中间人攻击、ARP欺骗背后的原理及漏洞还原☞全方位解析阿里云核心技术竞争力CSDN 独家在线峰会来了☞用于小型图形挖掘研究的瑞士军刀空手道俱乐部的图表学习Python库☞罗永浩欲直播带货京东说可以帮忙联系☞MySQL数据库无完整备份删库除了跑路还能怎么办☞Libra新编程语言 Move 的所有权模型灵感来源原来是它……你点的每一个在看我认真当成了喜欢