进程 线程 协程

进程

进程有自己独立的内存空间,因此只要物理内存足够大,并且是64位,一般不会有内存不足的担忧。但是进程之间的同步麻烦。

线程

线程占用同一个进程的地址空间,所以有一个共享对象同步的问题,模型较复杂。

状态

https://www.jianshu.com/p/ec94ed32895f

NEW:此时的线程还没有调用start方法的状态

RUNNABLE:此时的线程处于可运行状态,但可能没有被CPU调度。因此这个阶段可以划分为Ready和Running两个子状态

BLOCKED:此时的线程不参与竞争CPU,直到线程进入Runnable状态。常见的就是获取synchronized或locked锁时没有获取到就会进入该状态。

WAITING:执行wait,join或者LockSupport.part方法时,就会进入该状态

TIMED_WAITING:跟WAITING状态的区别在于,该状态是有一定的时效,超过指定时间就会结束该状态。调用Thread.join(long),Thread.sleep(long),Object.wait(long),LockSupport.parkNanos(),LockSupport.parkUtil()都能进入该状态。

TERMINATED:线程结束之后的状态。

上面的是JVM的线程状态,跟操作系统的状态略有不同

RUNNABLE在操作系统中又划分为就绪和运行状态,因为这对CPU是有区别的,对JVM就没有,都是交给CPU运行了。

BLOCKED、WAITING和TIMED_WAITING对CPU来说都是休眠状态,都是不能参与CPU的竞争。

wait、notify和notifyAll

对象的锁池和等待池

锁池:多个线程调用同一个synchronized对象的代码,后调用的线程就会等待对象的锁,此时就会进入对象的锁池中进行等待。

等待池:调用了对象的wait方法之后,调用线程就会进入对象的等待池中,等待池的线程不参与CPU的竞争。

等待其他线程调用notify方法,将其从等待池移动到锁池中,然后等调用notify方法的同步代码执行完毕,该线程释放了锁,锁池中的某个线程就会获取到这个对象锁。

notify只会将等待池中的一个线程移动到锁池中,而notifyAll则会将等待池中的所有线程移动到锁池中,虽然最终都是只有一个锁池中的线程获取到了CPU,但是notifyAll可以使所有等待池中的线程都参与CPU的竞争。

因此说使用notifyAll可以减少死锁,因为它可以时所有等待的线程都参与CPU竞争,而notify则只会让一个参与竞争,并且锁的竞争时非公平性的,进入锁池的线程会先进行插队,可以优先获取CPU,只有获取不到CPU时才会进入队列进行排队。而没有竞争到CPU的线程要重新进入等待池。

yield和sleep

yield就是让当前线程让出CPU的使用,进行一次CPU的调度,但是当前线程线程也参与调度,可能调度之后仍然时该线程获得CPU的使用,与sleep(0)较类似。但是这在一些循环中还是很有用的。

线程同步

使用synchronized关键字或者ReentrantLock都能实现同步

线程通信

https://my.oschina.net/u/3272058/blog/3068524

实现线程执行的流程控制,实现方法有几种:

synchronized

synchronized的wait、notify和notifyAll方法

ReentrantLock

ReentrantLock的condition对象的await、signal和signalAll方法,跟上面的概念和使用是一一对应的。

CountDownLatch

内有await和countDown方法

CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。采用计数的方式,设定一个初始数,等待其他线程的线程调用await等待其他线程执行完毕,其他线程在执行完毕后调用countDown方法后就计数减一,等到计数变为0时,await被唤醒,等待的线程继续往下执行。

CyclicBarrier

方法有await和reset方法,也是采用计数器,同时调用await方法阻塞的线程达到指定数目后才开始同时向下执行,并且可以自动重置进行循环使用。

Semaphore

主要方法是acquire和release。

信号量用于控制指定资源的最大访问线程数,还可以用来管理各种池。

通过线程通信的经典应用是实现生产者消费者模型,当然Java里面已经有实现好的生产消费队列,比如ArrayBlockingList,该队列在内部是使用ReentrantLock实现的。

协程

协程并不是操作系统内核态的实现,而是在用户态实现的,因此谁都可以实现协程的方案。是单线程运行的。

现在的网络请求中,大部分的时间都出现在IO等待上,而并不是cpu计算上,因此多线程进程并不能提高相应的时间。并且多线程模型比较复杂,并且线程切换也需要额外的开销。并且现在的异步IO代码并不直观。协程就是来解决这个问题的。

协程就是一个单线程,内部使用事件循环切换处理不同的事件,虽然是单线程,也能让不同事件的IO和CPU操作实现并行处理。大大降低模型的复杂度,提高CPU的利用率。

将事件提交到事件循环中:在python中是通过syncio.create_task方法处理的,类似于线程池中的submit方法

定义事件处理方法:使用async修饰普通方法实现,类似于线程池中定义Runnable接口的类。

事件切换:使用awati关键字修饰调用的方法实现。await的字面意思就是异步等待调用方法执行完毕,这个方法一般用来修饰IO耗时的方法调用,调用该方法之后就触发一次调度,等到该方法处理完成之后再调度回来。有点像线程池中的future对象,但又不像。

适用范围:IO密集性的、计算密集性的应该使用多线程或者多进程。

posted on 2020-05-18 11:04  simple_孙  阅读(166)  评论(0编辑  收藏  举报

导航