Java多线程介绍

1、进程与线程
进程指的是内存中运行的应用程序,每一个进程都有一个独立的内存空间,一个软件可以有多个进程,也可以是一个单进程
而线程指的是一个进程中的多条执行路径,同一个进程中的不同线程是共享一个内存空间的,每个线程之间都可以自由切换和并发执行,一个进程至少有一个线程,当进程内的线程全部执行完后,进程会被回收释放掉。
2、线程调度
在宏观上看,同一个进程内的不同线程是可以同时执行的,但在微观上看,线程是交替执行的,单个线程执行的时间取决于该线程能够抢到多少个时间戳(每个时间戳的时间是固定的),抢到的时间戳越多,累计所能执行的时间也就越久,所能抢到的时间戳数量取决于线程的调度,调度方法可以包括:
(1)分时调度
分时调度的调度方式是:所有线程轮流使用CPU的使用权,均分每个线程占用CPU的时间,也就是说所有线程每次分到的时间戳是一样的,而且是轮流分配使用时间,不分优先级。分时调度的优点在于,每个线程都能分到时间戳,不会出现因为分不到时间戳而一直等待的状态。
(2)抢占式调用
抢占式调度的调度方式是:为每个线程设定一个优先级,优先级越高则越容易得到CPU的使用权,优先级相同的线程则随机选择一个,Java使用的是抢占式调度。抢占式调度的有点在于,比较重要的或对实时性要求比较高的线程能够更快地执行,使资源分配更加的合理,但是缺点是优先级低的线程很难抢到时间戳,有可能出现线程长时间执行不完的情况。
线程调度并不会提高进程的运行速度,但是可以提高进程的运行效率,例如不会出现一个线程被阻塞而导致其他所有线程都无法运行的情况,也不会很影响像对于实时性要求较高的线程的性能。
3、同步与异步,并发与并行
同步指的是进程中不同线程排队轮流执行,这种方式会降低效率(不是速率)但是安全,不会出现多个线程同时访问和操作同一个数据而产生冲突。
异步指的是进程中不同线程同时执行,与同步刚好相反,这种方式效率高但是不安全。
并发指的是两个或多个事件在一个时间段内发生,例如人一天内发生了吃饭、睡觉、上班,则这三件事在是在这一天内并发执行的。
并行指的是两个或多个事件在同一个时刻发生,例如人可以在同一时刻呼吸和眺望远方。
4、Java的线程创建方法
(1)继承Thread
Thread类是Java用于实现线程的类,需要新建一个线程,则需要在需要开启线程的那个类中继承Thread类,可以通过重写Thread类的run()方法进行自定义需要运行的内容,当调用run()方法时,会为run()方法内的代码创建新线程,需要注意的是,main函数本身就是一个线程,且默认是主线程(守护线程),Java中线程可以分为用户线程和守护线程,守护线程用于守护用户线程,在用户线程执行完过后才会释放掉,用户线程是我们程序员手动添加的线程,当用户线程执行完后会结束所有守护线程关闭进程,如果需要将用户线程变成主线程,可以通过Thread类中的setDeamon(true)的方式将当前线程设置为主线程。创建线程的方式如下:
在这里插入图片描述
在这里插入图片描述

其中的sleep()方法可用于延时操作,以毫秒为单位,可以增加个参数用于精确到微妙,运行结果如下:
在这里插入图片描述

(2)Runnable类
Runnable接口是用于辅助Thread类进行多线程操作的,它的操作方式如下:

在这里插入图片描述
在这里插入图片描述

运行结果如下:

在这里插入图片描述

可以将Runnable接口的实现类当做一个任务,然后对线程传入该任务进行创建新线程,这么做的好处有以下几点:
1、通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务,更能体现出多线程的效果;
2、可以避免单继承所带来的局限性,通过实现接口的方式可以再继承其他类,而直接继承Thread类会导致无法继承其他类;
3、任务与线程是分离的,提高了程序的健壮性
4、后续的线程池技术不支持直接使用Thread类,需要Runnable接口辅助,Runnable也是最常用的实现多线程的方式。
另外还可以在Thread构造函数的参数中加个参数用于命名当前线程方便阅读。
5、线程安全问题
线程安全问题指的是,当同一个进程的多个线程运行时,可能出现多个线程同时访问一个数据(即异步),从而导致数据错乱问题,例如经典的卖票问题,每次卖票前判断票数是否为0,当票数不为零时执行买票操作,可是如果当票数为1时,多个线程同时判断票数,会导致多个线程同时执行买票操作,然后出现余票不足的情况。为了解决这方面的问题,引入了以下三种方法可以解决:
(1)同步代码块
同步代码块可以实现代码块的同步机制,保证同一时间只有一个线程访问和执行代码块的内容,操作方式如下:
在这里插入图片描述

需要注意的是,需要实现同步的代码必须放在synchronized的代码块内,对象参数o用于标记代码块是否正在执行,如果正在执行则其他访问的线程需要等待正在执行的线程执行完毕才能抢同步代码块的使用权,如果计算机性能太好可能会出现刚执行完的线程,一执行完就再次抢到该同步代码块的使用权,然后导致其它线程经常抢不到票的情况。
(2)同步方法
同步方法指的是实现了同步功能的方法,需要实现方法的同步,可以在方法名前通过增加synchronized字段声明该方法为同步方法,同步方法的效果与同步代码块效果相同,在同一时间只能有一个线程使用这个方法,操作方法如下:

在这里插入图片描述
在这里插入图片描述

同步方法的优点在于,方便代码的封装,使代码可读性提高,是比较常用的实现同步操作的方式。
(3)显式锁Lock
显式锁Lock类也是实现同步操作的一种类,显式锁指的是这个锁是可见的,前面两种实现同步的锁都是隐式锁,是不可见的,用户可以通过Lock类中的lock()方法和unlock方法对代码块进行上锁,当代码块被上锁时,其他线程无法访问该代码块,操作方法如下:
在这里插入图片描述

这种通过上锁的方式实现的同步,更加的直观且方便。
另外,以上三种实现同步的方式都是不公平锁,不公平锁指的是所有线程需要抢到锁的控制权才能占用锁内的内容,优先级高的更容易抢到锁,而公平锁则是所有线程轮流取得锁的占用权,设置公平锁的方式是在创建对象时在ReentrantLock()的括号内写上true,则表示该锁为公平锁。
(4)线程死锁
线程死锁,讲得形象点,就比如说最近经常见到的买口罩问题,消费者因为没有口罩所以需要进入诊所购买,但是诊所需要戴口罩才能进入,然后导致消费者无法买到口罩,销售者也无法销售口罩,线程死锁则是线程之间发生这种死锁的现象。
(5)生产者与消费者问题
生产者与消费者问题相比大多程序员都听过,这问题指的是,当消费者需要在生产者生产了产品过后才能进行消费,而生产者则需要等待消费者消费完过后才能进行生产产品,举个例子,例如饭店上菜,厨师需要做好菜过后将装菜的盘子交给服务员,然后服务员将盘子交给消费者,消费者用完餐过后,服务员将盘子收回,然后交给厨师继续做菜,在实现这个程序的过程中,会出现一种情况,当厨师类对象线程正在设置(做菜)菜类对象的属性(例如菜名和味道)时,会出现当厨师只设置完菜名而还没开始设置味道时就被服务员线程获取菜名和味道,导致出现bug,为了解决这种问题,可以在菜类的属性中添加一个boolean类型的flag变量,当厨师可以做菜时将flag设置为true,然后上完菜过后将flag设置为false,然后通过Thread类的wait()函数使厨师休眠,然后通过notify()函数唤醒服务员,然后服务员的操作刚好相反。

6、线程池
当用户创建的线程只需要实现一些比较简单的命令的时候,会出现,相对于命令执行的时间,创建线程和关闭线程所消耗的时间比需要执行的指令消耗的时间多得多,因此就引入了线程池这个概念,一个线程池相当于一个数组,一个元素代表一个线程,线程池可以分为四种:
(1)缓存线程池
缓存线程池会在用户需要线程时直接使用线程池内的空闲线程,当缓存线程池内的空闲线程不够时,会创建新的线程添加到缓存线程池中,但线程池中的一部分线程长时间没用过后会被清理掉,缓存线程池的创建方法如下:
在这里插入图片描述

缓存线程池在同时执行三个线程时会在线程池添加三个线程,当线程执行完后会进入空闲状态,空闲状态的线程可以直接使用而不需要重新创建。(使用了匿名内部类方式创建的任务)
(2)定长线程池
定长线程池的线程池大小是固定的,且需要在创建时设置好线程个数,详细操作如下:
在这里插入图片描述

定长线程池在设置好长度过后就无法再改变了,因此在创建时需要设置好合理的大小。
(3)单线程线程池
单线程线程池相当于大小为1的定长线程池,操作方式与前面的差不多:
在这里插入图片描述

(4)周期定长线程池
周期定长线程池有两种,一种是只设置初次运行在多长时间后执行:

在这里插入图片描述

另一种不仅可以设置初次运行延迟时间,还可以设置线程每次次执行之间的时间间隔:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值