Java线程
Posted hnu你深
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程相关的知识,希望对你有一定的参考价值。
目录
3、Java中的四种线程池 . ExecutorService
一、线程与进程
进程
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间 。
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
二、线程调度
分时调度
- 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
- 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
三、同步与异步
同步:排队执行 , 效率低但是安全。
异步:同时执行 , 效率高但是数据不安全。
四、并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
五、Java线程的使用
1、继承Thread
运行结果:
- 第一次:
- 第二次:
程序线程执行的时序图:
2、实现Runnable
运行结果:
实现 Runnable与继承Thread比较
3、通过匿名内部类实现
运行结果:
六、Thread常用方法
1、设置和获取线程名称
2、线程休眠sleep
每隔一秒输出一次:
3、线程阻塞
https://blog.csdn.net/sunshine_2211468152/article/details/87299708
https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B%E9%98%BB%E5%A1%9E/2233470?fr=aladdin
4、线程中断
修改run方法,发现中断标记后自杀:(可以先释放占用的资源后自杀)
七、线程的六种不同状态
详见:https://blog.csdn.net/pange1991/article/details/53860651
八、守护线程
线程:分为守护线程和用户线程
- 用户线程:当一个进程不包含任何的存活的用户线程时,进程结束。
- 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
代码示例:
代码改造:
运行结果:
九、线程安全
1、为什么会出现线程安全问题?
实例:
运行结果出现了余票为负的情况
分析:
线程1 | 线程2 | 线程3 | count |
while(count>0) | 就绪 | 1 | |
system.out.println("正在准备卖票") | 就绪 | 1 | |
Thread.sleep(1000) | while(count>0) | 1 | |
system.out.println("正在准备卖票") | 1 | ||
Thread.sleep(1000) | 1 | ||
while(count>0) | 1 | ||
system.out.println("正在准备卖票") | 1 | ||
Thread.sleep(1000) | 1 | ||
count-- | 0 | ||
system.out.println("出票成功,余额:"+count) | 0 | ||
while(count>0) | 0 | ||
count-- | -1 | ||
system.out.println("出票成功,余额:"+count) | -1 | ||
count-- | -2 | ||
system.out.println("出票成功,余额:"+count) | -2 |
2、 解决方案一:同步代码块sychronized
要看同一把锁!
把该任务分配给三个子线程,争夺同一个Object对象o的锁:排队执行
3、 解决方案二:同步方法
1、同步方法的锁是?
非静态方法:this
静态方法:类名.class
2、一个类里面有多个同步方法?
只要一个同步方法运行,其他同步方法就不能运行
4、解决方案三:显式锁
线程分配到同一个Ticket任务,争夺同一把锁l
5、Java公平锁和非公平锁
锁Lock分为公平锁和非公平锁。
- 公平锁:表示线程获取锁的顺序是按照加锁的顺序来分配的,及先来先得,先进先出的顺序。
- 非公平锁:表示获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定能拿到锁,有可能一直拿不到锁,所以结果不公平。
公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式
以下内容转自链接:https://www.imooc.com/article/284585
(1)什么是非公平锁?
如上图,现在线程1加了锁,然后线程2尝试加锁,失败后进入了等待队列,处于阻塞中。然后线程1释放了锁,准备来唤醒线程2重新尝试加锁。
注意一点,此时线程2可还停留在等待队列里啊,还没开始尝试重新加锁呢!然而,不幸的事情发生了,这时半路杀出个程咬金,来了一个线程3!线程3突然尝试对ReentrantLock发起加锁操作,此时会发生什么事情?
很简单!线程2还没来得及重新尝试加锁呢。也就是说,还没来得及尝试重新执行CAS操作将state的值从0变为1呢!线程3冲上来直接一个CAS操作,尝试将state的值从0变为1,结果还成功了!一旦CAS操作成功,线程3就会将“加锁线程”这个变量设置为他自己。给大家来一张图,看看这整个过程:
明明人家线程2规规矩矩的排队领锁呢,结果你线程3不守规矩,线程1刚释放锁,不分青红皂白,直接就跑过来抢先加锁了。这就导致线程2被唤醒过后,重新尝试加锁执行CAS操作,结果毫无疑问,失败!
原因很简单啊!因为加锁CAS操作,是要尝试将state从0变为1,结果此时state已经是1了,所以CAS操作一定会失败!一旦加锁失败,就会导致线程2继续留在等待队列里不断的等着,等着线程3释放锁之后,再来唤醒自己,真是可怜!先来的线程2居然加不到锁!
同样给大家来一张图,体会一下线程2这无助的过程:
上述的锁策略,就是所谓的非公平锁!
如果你用默认的构造函数来创建ReentrantLock对象,默认的锁策略就是非公平的。在非公平锁策略之下,不一定说先来排队的线程就就先会得到机会加锁,而是出现各种线程随意抢占的情况。那如果要实现公平锁的策略该怎么办呢?也很简单,在构造ReentrantLock对象的时候传入一个true即可:
ReentrantLock lock = new ReentrantLock(true)
此时就是说让他使用公平锁的策略,那么公平锁具体是什么意思呢?
(2)什么是公平锁?
咱们重新回到第一张图,就是线程1刚刚释放锁之后,线程2还没来得及重新加锁的那个状态。
同样,这时假设来了一个线程3,突然杀出来,想要加锁。
如果是公平锁的策略,那么此时线程3不会跟个愣头青一样盲目的直接加锁。他会先判断一下:咦?AQS的等待队列里,有没有人在排队啊?如果有人在排队的话,说明我前面有兄弟正想要加锁啊!如果AQS的队列里真的有线程排着队,那我线程3就不能跟个二愣子一样直接抢占加锁了。因为现在咱们是公平策略,得按照先来后到的顺序依次排队,谁先入队,谁就先从队列里出来加锁!所以,线程3此时一判断,发现队列里有人排队,自己就会乖乖的排到队列后面去,而不会贸然加锁!
同样,整个过程我们用下面这张图给大家直观的展示一下:
上面的等待队列中,线程3会按照公平原则直接进入队列尾部进行排队。接着,线程2不是被唤醒了么?他就会重新尝试进行CAS加锁,此时没人跟他抢,他当然可以加锁成功了。然后呢,线程2就会将state值变为1,同时设置“加锁线程”是自己。最后,线程2自己从等待队列里出队。
整个过程,参见下图:
这个就是公平锁的策略,过来加锁的线程全部是按照先来后到的顺序,依次进入等待队列中排队的,不会盲目的胡乱抢占加锁,非常的公平。
6、Java线程死锁
(1)死锁的定义
多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
(2)死锁产生的原因
1、系统资源的竞争
通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。
2、进程推进顺序非法
进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞。
从上面两个例子中,我们可以得出结论,产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。另一个原因是默认的锁申请操作是阻塞的。
3、死锁产生的必要条件:
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
(1)互斥:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
(2)不剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
(3)请求和保持:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(4)循环等待:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有,如图1所示。
(3)产生死锁的实例
看下面一段代码:
public class DeadLock {
public static void main(String[] args) {
Police p = new Police();
CulPrit c = new CulPrit();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread {
private CulPrit c;
private Police p;
public MyThread(CulPrit c,Police p) {
this.c = c;
this.p = p;
}
@Override
public void run() {
p.say(c);
}
}
static class Police {
public synchronized void say(CulPrit c) {
System.out.println("警察:你放走人质,我放走你");
c.fun();
}
public synchronized void fun() {
System.out.println("警察:罪犯放走了人质,警察也放走了罪犯");
}
}
static class CulPrit{
public synchronized void say(Police p){
System.out.println("罪犯:你放走我,我放走人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯:警察放走了罪犯,罪犯也放走了人质");
}
}
}
运行结果:警察和罪犯互相等待对方的回复fun,但是警察的say占用了this锁,罪犯的say也占用了this锁。
运行多次后出现了另外一种输出:此时在子线程执行p.say(c)之前主线程抢先执行了p.fun(),程序正常结束。
该部分参考链接:
https://www.cnblogs.com/xiaoxi/p/8311034.html
十、Java多线程通信问题
Object类的一些方法:
void | notify() | 唤醒正在此对象监视器上等待的单个线程。 |
---|---|---|
void | notifyAll() | 唤醒等待此对象监视器的所有线程。 |
void | wait() | 导致当前线程等待它,通常是 通知或 中断 。 |
void | wait(long timeoutMillis) | 导致当前线程等待它,通常是 通知或 中断 ,或者直到经过一定量的实时。 |
void | wait(long timeoutMillis, int nanos) | 导致当前线程等待它,通常是 通知或 中断 ,或者直到经过一定量的实时。 |
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
/**
* 多线程通信问题, 生产者与消费者问题
* @param args
*/
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
}
public synchronized void get(){
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
}
}
}
运行结果:
服务员端走的菜口味与厨师设置的口味不一样。
分析:i%2==0时厨师做完老干妈小米粥,还没等到服务员端走菜,又“回手掏” 开始了做菜,这次i%2==1厨师setName("煎饼果子")然后睡眠100ms这时被服务员线程抢占了锁,服务员把菜端走了,因此菜品的名称是“煎饼果子”,口味却是”香辣味“。
这里厨师是生产者线程,而服务员是消费者线程,生产者生产一个单位的产品还未结束,就被消费者线程抢先运行,导致了产品的数据错乱。
对Food进行修改:
运行结果:菜品数据皆正确
十一、带返回值的线程Callable
1、Runnable 与 Callable
2、Callable使用步骤
3、Runnable 与 Callable的相同点
4、Runnable 与 Callable的不同点
5、Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
6、FutureTask类
构造器 | 描述 |
---|---|
FutureTask(Runnable runnable, V result) | 创建一个 |
FutureTask(Callable<V> callable) | 创建一个 |
变量和类型 | 方法 | 描述 |
---|---|---|
protected void | done() | 当此任务转换到状态 |
V | get() | 如果需要等待计算完成,然后检索其结果。 |
V | get(long timeout, TimeUnit unit) | 如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。 |
protected boolean | runAndReset() | 执行计算而不设置其结果,然后将此未来重置为初始状态,如果计算遇到异常或被取消则无法执行此操作。 |
protected void | set(V v) | 将此future的结果设置为给定值,除非已设置或已取消此未来。 |
protected void | setException(Throwable t) | 导致此未来报告带有给定throwable的 |
String | toString() | 返回此FutureTask的字符串表示形式。 |
实例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class DemoCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Integer j = futureTask.get();//获取返回值
System.out.println(j);
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implements Callable{
@Override
public Integer call() throws Exception {
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(i);
}
return 100;
}
}
}
运行结果:可以看到在子线程运行结束以后主线程才执行后续的代码
十二、Java线程池
1、线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
2、线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
3、Java中的四种线程池 . ExecutorService
(1) 缓存线程池
/*** 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中加入新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
(2) 定长线程池
/**
* 定长线程池。
* (长度是指定的数值)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
Executorservice service = Executors.newFixedThreadPool(2) ;
service.execute(new Runnable() {
@override
pub1ic void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName();
}
});
service.execute(new Runnable() {
@override
pub1ic void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName()) ;
}
});
3. 单线程线程池
效果与定长线程池创建时传入数值1效果-致.
/**
* 单线程线程池.
* 执行流程:
* 1. 判断线程池的那个线程是否空闲
* 2. 空闲则使用
* 3. 不空闲, 则等待池中的单个线程空闲后使用
*/
ExecutorService service = Executors.newsingleThreadexecutor();
service.execute(new Runnable(){
@override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName()) ;
}
});
4. 周期性任务定长线程池
pub1ic static void main(String[] args) {
/**
* 周期任务定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
* 周期性任务执行时:
* 定时执行,当某个时机触发时,自动执行某任务.
*/
SchedledExecutorservice service = Executors.newscheduledThreadPool(2) ;
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
/*service.schedule(new Runnable(){
@override
public void run() {
System.out.println("俩人相视一笑~嘿嘿嘿");
},5,TimeUnit.SECONDS);
*/
/**
*周期执行
*参数1. runnable类型的任务
*参数2. 时长数字(延迟执行的时长)
*参数3. 周期时长(每次执行的间隔时间)
*参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable(){
@override
public void run(){
System.out.println("俩人相视一笑~嘿嘿嘿") ;
},5,2,TimeUnit.SECONDS);
}
十三、Lambda表达式
使用方法:
面向对象的一般代码:
使用Lambda表达式:
以上是关于Java线程的主要内容,如果未能解决你的问题,请参考以下文章