进程
每个应用程序在运行期间,操作系统为应用程序分配一个独立的内存空间,称为进程;多个进程之间的数据是相互隔离的; 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 | 返回当前正在运行的线程 |
实现线程的方法
创建自己的线程有两种方式;
-
用一个类继承Thread,不推荐;java是单继承的特性
-
用一个类实现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的票
存在的问题:
-
票号有重复的
-
总共100张票被卖了102次
为了理解为什么出现如上的问题,我们需要理解线程的调度策略;
线程的调度策略
-
时间片策略
-
抢占式策略
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
多个线程在执行期间,默认以抢占式策略来尝试获取到CPU的使用权;在多线程环境中,同一个时间点只有一个线程被执行,但是CPU为每个线程分配的使用时间非常的短暂,因此给我们感官上,多个线程同时在执行;
抢占式策略类似于在食堂排队吃饭;默认情况下按照抢占式策略,谁的动作快谁就先完成打饭;如果大家动作都一样,就看谁的块头大(优先级);
线程的优先级:默认为5,最高为10,最低为1;通过Thread类中的setPriority(int)设置优先级;
线程的生命周期
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态 死亡:线程完成了它的全部工作或线程被提前强制性地中止
线程的同步
是为了防止多个线程访问一个数据对象时,对数据造成的破坏
线程同步,其实就是排队,然后一个一个对共享资源进行
操作,而不是同时进行操作
只有共享资源的读写访问才需要同步
线程同步能够保证多线程中共享资源的数据安全问题(有的线程读,有的线程写);
默认情况下,线程是按照时间片策略来执行;但是使用了同步以后,线程的执行时间就不由时间片策略管理,而是交给线程本身;因此被同步的资源,前一个线程没有释放它的控制(锁),后一个线程就会一直等待;
线程同步的两种方式
-
同步代码块
/*同步代码块存在于方法以内*/ public void run(){ synchronized(对象){//共享锁 //需要同步的代码; } }
-
同步方法
同步方法必须位于共享资源类中;不能定义到其他的地方;因为同步方法同样需要加锁,同步方法加锁的对象为共享资源对象本身;
/*哪个线程得到对同步方法使用权限时,方法只要没结束,别的线程就无法访问同步方法*/ 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