Java SE(十九):线程


Java SE

包含。


一、多线程

什么是进程

进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)
进程(process)是一块包含了某些资源的内存区域,操作系统利用进程把它的工作划分为一些功能单元
进程中所包含的一个或多个执行单元称为线程(thread),进程还拥有一个私人的虚拟地址空间,该空间仅

线程使用场合

线程通常用于在一个程序中需要同时完成多个任务的情况。我们可以将每个任务定义为一个线程,使他们得以一同工作
也可以用于在单一线程中可以完成,但是使用多线程可以更快的情况。比如下载文件

线程的基本原理

Java语言的优势之一就是线程处理较为简单
一般操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序被称为一个进程,当一个程序运行时,内部可能包含多个顺序执行流,每个顺序执行流就是一个线程
程序:指令+数据的byte序列,如:qq.exe
进程:正在运行的程序,是程序动态的执行过程(运行与内存中)
线程:在进程内部,并发运程的过程(Java中的方法可以看做线程)
并发:进程是并发运行的,OS将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,微观上进程走走停停,宏观上都在运行,这种都运行的现象叫并发,但是不是绝对意义上的“同时发生”

Thread类简介

Thread类代表线程类型
任何线程对象都是Thread类(子类)的实例
Thread类是线程的模板(封装了复杂的线程开启等操作,封装了操作系统的差异性)
只要重写run方法即可现实具体线程

Thread类创建线程

创建一个具体线程,需要继承于Thread类
覆盖run方法(就是更新运行过程),实现用户自己的过程
创建线程实例(一个线程)
使用线程实例的start()方法启动线程,启动以后线程会尽快的去并发执行run()

Runnable实现线程

创建一个类实现Runnable接口,重写run方法
以实现了Runnable接口的类的实例对象作为创建Thread类对象的构造函数的参数

Thread.currentThread方法

Thread的静态方法currentThread方法可以用于获取运行当前代码片段的线程
Thread current = Thread.currentThread();

获取线程信息

Thread提供了获取线程信息的相关方法:
long getId():返回该线程的标识符
String getName():返回该线程的名称
int getPriority():返回线程的优先级
Thread.state getState():获取线程的状态
boolean isAlive():测试线程是否处于活动状态
boolean isDaemon():测试线程是否为守护线程
boolean isInterrupted():测试线程是否已经中断

线程优先级

线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程序的改善线程获取时间片的机率
线程的优先级被划分为10级,值分别为1~10,其中1最低,10最高。线程提高了3个常量来表示最低,最高,以及默认优先级:

Thread.MIN_PRIORITY
Thread.MAX_PRIORITY
Thread.NORM_PRIORITY
void setPriority(int priority):设置线程的优先级

sleep方法

Thread.sleep(times)使当前线程从Running放弃处理器进入Block状态,休眠times毫秒,再返回到Runnable
该方法声明抛出一个InterruptException,所以在使用该方法时需要捕获这个异常

yield方法

Thread的静态方法yield:
static void yield()
该方法用于使用当前线程主动让出当次cup时间片回到Runnable状态,等待分配时间片

interrupt方法

一个线程可以提前唤醒另外一个sleep Block的线程interrupt()打断/中断

join方法

Thread的方法join:void join()
该方法用于等待当前线程结束
该方法声明抛出InterruptException

守护线程

守护线程
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出
该方法必须在启动线程前调用

线程同步的概念

线程同步,可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下开,示意B运行;B依言执行,再将结果给A;A再继续操作。所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法
1)异步 并发,各干自己的。如:一群人上卡车
2)同步 步调一致的处理。如:一群人上公交车

Synchronized关键字

多个线程并发读写同一个临界资源时候会发生“线程并发安全问题”
常见的临界资源:

  • 多线程共享实例变量
  • 静态公共变量

使用同步代码块解决线程并发安全问题
synchronized(同步监视器){}
同步监视器是一个任意对象实例,是一个多个线程之间的互斥的锁机制,多个线程要使用同一个“监视器”对象实现同步互斥
常见写法:synchronized(this){}
如果方法的全部过程需要同步,可以简单使用synchronized修饰方法,相当于整个方法的synchronized(this)
尽量减少同步范围,提高并发效率

线程安全API与非线程安全API

StringBuffer是同步的synchronized append();即:多个线程不能同时访问此方法
StringBuilder不是同步的append();
Vector和Hashtable是同步的
ArrayList和HashMap不是同步的
获取线程安全的集合方式:
Collections.synchronizedList()获取线程安全的List集合
Collections.synchronizedMap()获取线程安全的Map

wait和notify简介

多线程之间需要协调工作
例如,浏览器的一个显示图片的,displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还没下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行
以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的

Socket原理

客户端-服务端(c/s)模型简介

在C/S模式下,客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务
例如:在一个酒店中,顾客想服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户,这就是一种C/S工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器。这种系统分工和协同工作的方式就是C/S的工作方法
客户端部分:为每个用户所专有的,负责执行前台功能
服务器部分:由多个用户共享的信息与功能,招待后台服务

Socket数据访问

客户端Socket与服务器端Socket对应,都包含输入、输出流
客户端的socket.getInputStream()连接与服务器socket.getOutputStream()
客户端的socket.getOutputStream()连接与服务器socket.getInputStream()

多线程服务器端

Server端多线程框架

服务器端无限循环接收客户端的访问
每连接都能产生一对新的Socket的实例
为每个客户端连接创建一个独立线程,处理客户端请求

线程池

在Tcp服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能

线程池技术原理

线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程
任务是提交给整个线程池
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务

ExecutorService简介

ExecutorService提供了管理终止的方法,以及可以跟踪一个或多个异步任务执行状况而生成Future的方法
可以关闭ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭ExecutorService
它只有一个直接实现类ThreadPoolExecutor和间接实现类SchedubledThreadPoolExecutor

使用ExecutionrService实现线程池

ExecutorService是java提供的用于管理线程池的类
线程池有两个主要作用:控制线程数量、重用线程
当一个程序中若创建大量线程,并在任务结束后销毁,会给系统带来过渡消耗资源,以及过渡切换线程的危险,从而可能导致系统崩溃,为此我们应使用线程池类解决这个问题

线程池有以下几种实现策略:

Executor.newCachedThreadPool()创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用他们
Executors.newFixedThreadPool(int nThreads)创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程
Executors.newScheduledThreadPool(int corePoolSize)
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
Executors.newSingleThreadExecutor()创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程

缓冲队列

在服务器开发中通常的做法是把逻辑处理线程和I/O处理线程分离
逻辑处理线程:对接收的包进行逻辑处理
I/O处理线程:网络数据的发送和接收,连接的建立和维护
通常逻辑处理线程和I/O处理线程是通过数据队列来交换数据,就是生产者–消费者模型
这个数据队列是多个线程在共享,每次访问都需要加锁,因此如何减少互斥/同步的开销就显得尤为重要

缓冲队列技术原理

双缓冲数据就是两个队列一个负责从里写入数据,一个负责读取数据,当逻辑线程读完数据后负责将自己的队列和I/O线程的队列进行交换
需要加锁的地方有两个从队列中写入数据和两个队列进行交换时。如果是一块缓冲区,读、写操作是不分离的,双缓冲区起码节省了单缓冲区时读部分操作互斥/同步的开销
两个缓冲区分别对应着两个互斥锁locka、lockb。生产者消费者要控制那个缓冲区先要取得对应的锁

缓冲队列线程安全问题

大多数情况下,生产者控制着某个队列进行写操作,消费者控制着另外一个队列进行读操作,也就是说,逻辑线程和I/O线程进行着独占操作。这样就大大降低了互斥/同步的开销
当消费者将自己的队列(对应locka)读完,立即释放对locka的控制,等待控制lockb。一旦生产者释放lockb,消费者立即控制lockb,开始读取lockb对应的队列数据。同时生产者控制刚才locka开始写操作。这样就完成队列的交换

BlockingDeque编程技术

ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小,其所含的对象是以FIFO(先入先出)顺序排序的
LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定,其所含的对象是以FIFO(先入先出)顺序排序的
PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序
SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的

使用BlockingQueue

BlockingQueue是双缓冲队列
在多线程并发时,若需要使用队列,我们可以使用Queue,但是要解决一个问题就是同步,但同步操作会降低并发Queue操作的效率
BlockingQueue内部使用两条队列,可允许两个线程同时向队列一个做存储,一个做取出操作。在保证并发安全的同时提高了队列的存取效率


猜你喜欢

转载自blog.csdn.net/qq_45138120/article/details/125357004