线程

简介: 多线程

java虚拟机用的是那种内存模型呢?
1.ULT(用户线程)
2.KLT(内核线程)

实现Callable接口
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:ExecutorService ser = Executos.newFixedThreadPool(1);
5.提交任务Future result = ser.submit(t1);
6.获取结果boolean r1 = result.get();
7.关闭服务。ser.shutdownNow();

线程休眠:
1.sleep(时间)指定当前线程阻塞的毫秒数
2.sleep存在异常InterruptedException
3.Sleep时间达到后线程进入就绪状态
4.Sleep可以模拟网络延时,倒计时等。
5.每一个对象都有一个锁,sleep不会释放锁。

线程礼让:yield()
1.礼让线程,让当前正在执行的线程暂停,但不阻塞。
2.将线程从运行状态转为就绪状态
3.让cpu重新调度,礼让不一定成功,看cpu心情。

线程的状态。新生,就绪,运行,阻塞,死亡
1.new(新生):尚未启动的线程处于此状态,Thread t= new Thread();
2.Runnable(运行):在java虚拟机中执行的线程处于此状态
3.Blocked(阻塞):被阻塞等待监视器锁定的线程处于此状态
4.Waiting(阻塞):正在等待另一个线程执行特定动作的线程处于此状态。
5.Time_Waiting(阻塞):正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
6.terminated(死亡):已退出的线程处于此状态。

新生:尚未启动的线程处于此状态,Thread t= new Thread();
就绪:当调用start()方法后,线程立即进入就绪状态,但不意味着立刻调度执行。
运行:进入运行状态,线程才真正执行线程体的代码块。
阻塞:当调用sleep,wait或者同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞时间解除后,重新进入就绪状态,等待cpu调度。
死亡:线程中断或者结束,一旦进入死亡状态,就不能再次启动。

一个线程可以在指定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

线程的优先级:
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1—10。
Thread.MIN_PRIORITY =1;
Thread.MIN_PRIORITY =10;
Thread.NORM_PRIORITY =5;
使用以下方式改变或获取优先级
getPriority().setPriority(int XXX)
优先级的设定建议再start()调度前
优先级低只是意味着获得调度的概率低,并不是优先级就不会被调用了,这都是看cpu的调度。

守护(daemon)线程:
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕 (main() 就是用户线程)
虚拟机不用等待守护线程执行完毕,如,后台记录操作日志,监控日志,垃圾回收等待。(守护线程)

线程同步
多个线程操作同一个资源。
并发:同一个对象被多个线程同时操作。
形成条件:队列+锁。
处理多线程时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这是我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入锁机制Synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

1.一个线程持有锁会导致其他所有需要此锁的线程挂起。
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
3.如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

同步方法:
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是Synchronized关键字,它包括两种用法:Synchronize方法和Synchronize块。
同步方法:public Synchronize void method(int args){}

Synchronize方法控制对“对象”的访问,每个对象对应一把锁,每个Synchronize方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法申明为Synchronize将会影响效率。
方法里面需要修改的内容才需要加锁,锁的太多,浪费资源。

死锁:
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个欧折多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用。
2,请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

线程通信的应用场景:生产者消费者模式。
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,知道仓库中的产品被消费者取走为止。
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
分析:
这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
在生产者消费者问题中,仅有Synchronized是不够的

Synchronized可阻止并发更新同一个共享资源,实现了同步。
Synchronized不能用来实现不同线程之间的消息传递(通信)。

java提供了几个方法解决线程之间的通信问题:
wait() :表示线程一直等待,直到其他线程通知,与slepp不同,wait会释放锁。
wait(long tiomeout) :指定等待的毫秒数。
Notify():唤醒一个处于等待的线程。
notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度。
注意:均为Object类方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。

解决方式1:并发协作模型“生产者/消费者模式”——>管程法
1.生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
2.消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
3.缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
解决方式2:并发协作模型“生产者/消费者模式”——>信号灯法—设置标志位。

线程池:
背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理。

corePoolSize:核心线程数
maximumPoolSize:最大线程数

线程池面试题:
1.单核cpu设定多线程是否有意义?
答:有意义,因为你的线程并不是所有的时间都在占用cpu,有的线程在等待网络的输入,所以让其余线程轮流执行;
2.工作线程数是不是越大越好?
答:不是,线程之间有切换,切换本身开销就挺大。
3.工作线程数(线程池中线程核数量)设置多少合适?
答:公示:

Nthreads = Ncup * Ucpu *(1+W/C)

其中:
Ncup是处理器的核的数目,可以通过Runtime.getRuntime().availableProcessors()得到;
Ucpu是期望的cpu利用率(该值一般在0-1之间);
W/C是等待时间与计算时间的比率。(W:wait的时间/C:计算的时间)

例如:8核的cpu,期望每个cpu的利用率为50%,等待时间是0.5,计算时间0.5;则计算工作线程数
=80.5(1+0.5/0.5)=8;
线程的等待时间怎么来的:压测,做统计,调整,在生产环境要根据日志观察

并发编程的三大特性:
可见行:(visibility)
有序性: ( ordering)
原子性: (atomicity)

1.内存中的一行数据有多大:64个字节,为什么是64byte:
答:当块越大的时候,速度很慢,当块很小的时候,缓存中的命中率又很低。所以经过一系列的实测取了折中值64。

2.有序性:程序真的是按照“顺序”执行的吗?
x=1 x=1
x++ y=1
为了提高执行效率,cpu指令可能会乱序执行。
乱序执行不得影响单线程的最终一致性。

乐观锁:(认为别人没有修改)
CAS: 原来有个值0,好多线程要对0进行递增,每个线程将0读出来,要改变成1,然后把1往回写,看原本的数字是不是还是0,如果是0,则修改为1;
如果发现原来的0已经改变,比如已经被别的线程改成8,则重新读出来,再递增;继续判断。

1.怎么解决cas中ABA过程
Jdk提供了两个类。

自定义线程池:

1.execute()和submit的区别:

 答:submit()有返回值,是Future类型的;execute()没有返回值;submit是异步,execute是同步。

submit()可以传Runnable,Callable; execte()只能传Runnable。
核心线程默认不会死,可以去设置。
第3,4个参数说的是非核心线程数的最长空闲时间。
执行顺序:
corePoolSizw:当进来线程任务的时候,线程池新创建线程来执行任务,当线程池中的线程数且达到核心线程数后,就会把到达的任务放在缓存队列中;
maxPoolSize:当线程数>=核心线程,且队列已满时,线程池会创建新的线程,直至达到最大线程数。
注意:当达到核心线程数,并且队列已经满了的时候,再进来线程任务时,此时会创建新的非核心线程,来执行新进来的任务,并不是先执行队列里的。

相关文章
|
2月前
|
Java Linux API
线程的认识
线程的认识
|
3月前
|
存储 安全 Java
C++线程浅谈
C++线程浅谈
|
8月前
|
Java C语言 Python
线程那些事
线程那些事
29 0
|
10月前
|
算法 Java
线程通过管道通信
线程通过管道通信
|
11月前
|
算法 NoSQL Java
02.关于线程你必须知道的8个问题(上)
大家好,我是王有志,欢迎来到《Java面试都问啥?》。 今天我们来一起看看在面试中,关于线程各大公司大都喜欢问哪些问题。
82 1
02.关于线程你必须知道的8个问题(上)
|
11月前
|
Java Linux 调度
03.关于线程你必须知道的8个问题(中)
大家好,我是王有志,欢迎来到《Java面试都问啥?》。我们书接上回,继续聊Java面试中关于线程的问题。
57 1
03.关于线程你必须知道的8个问题(中)
|
11月前
|
Java Linux 程序员
04.关于线程你必须知道的8个问题(下)
大家好,我是王有志。今天是Java面试中线程问题的最后一部分内容,包括我们来聊同步与互斥,线程的本质,调度,死锁以及线程的优缺点等问题。
91 1
04.关于线程你必须知道的8个问题(下)
|
11月前
|
Java
线程理解
个人学习理解
59 0
|
11月前
|
传感器 存储 自动驾驶
(6)线程
(6)线程
72 0
|
C++
C++ | C++线程
c++创建线程的方式不止一种。
97 0