线程
现代操作系统调度的最小单元是线程,在一个进程当中可以创建多个线程,这些线程拥有各自的计数器,堆栈以及局部变量,并且能够访问共享的内存变量。
处理器在这些线程中高速切换,让使用者感觉到这些线程在同时执行。
线程状态的转换
Runnable: 线程对象创建之后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,等待获取CPU使用权。
运行状态:获取了CPU,执行程序代码
阻塞状态:线程由于某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态。
分为三种:
等待阻塞,运行的线程执行wait()方法,JVM会把该线程放入等待池中。该过程会释放持有的锁
同步阻塞,运行的线程在获取对象的同步锁时,如果同步锁被别的线程占用,JVM会把该线程放入锁池当中
其他阻塞,运行的线程执行sleep()或者join()方法,或者发出了I/O请求的时候,JVM会把线程设置为阻塞状态。当sleep()超时,join()等待线程终止或者超时,或者I/O处理完毕的时候,线程重新进入runnable状态,sleep不会释放持有的锁。
为什么?
更多的处理器核心
程序运行当中能够创建多个线程,但是一个线程只能在一个处理核心中运行,单线程程序运行的时候只能利用一个处理器核心。
而多线程把计算逻辑分配到多个处理器核心上面,让多个处理器核心加入到程序运行。
更快的响应时间
一个面向用户的业务操作,可以把一些数据一致性不强的操作派发给其他线程处理,使得响应用户请求的线程能够尽快处理完成。
线程调度
线程优先级
现代操作系统采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度。时间片多少决定了线程使用处理器资源的多少,而线程优先级决定线程得到分配的处理器资源多少。
线程默认优先级位是5,优先级高的线程分配时间片数量要多于优先级低的线程。设置线程优先级的时候,针对频繁阻塞的线程需要设置较高优先级,而偏重计算的线程设置较低的优先级,确保处理器不会被独占。
线程的优先级有继承关系
线程睡眠
Thread.sleep(long mills)
使得线程转到阻塞状态,结束后转为runnable状态
sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
wait与sleep区别
线程等待
Object类的wait()方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或者notifyAll()方法。
wait()与notify()方法必须要与synchronized一起使用,也就是也就是wait,与notify是针对已经获取了的锁进行操作。
wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其他线程调用对象的notify()唤醒该线程,这样才能继续获取对象锁并继续执行。响应的notify()就是对象锁的唤醒操作。sleep()与wait()二者都可以暂停当前线程,释放CPU的控制权,主要区别是Object.wait()在释放CPU同时,释放了对锁的控制。
该问题为三线程间的同步唤醒操作,主要目的就是ThreadA->ThreadB->ThreadC,每一个线程必须同时持有两个对象锁才能继续执行。一个对象锁是prev,是前一个线程所持有的对象锁。还有一个是自身的对象锁。为了控制执行顺序,先持有prev锁,也就是前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备的时候打印。之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再用prev.wait()释放prev对象锁,终止当前线程。等待循环结束之后再次被唤醒。
线程让步
Thread.yield()方法,暂停当前执行的线程对象,把执行机会让给相同或者更高优先级的线程。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class ThreadYield extends Thread{
//private String name;
public ThreadYield(String name){
super(name);
}
public void run(){
for(int i=1;i<=10;i++){
System.out.println(this.getName()+"----"+i);
if(i==3){
this.yield();
}
}
}
public static void main(String[]args){
ThreadYield thread1 = new ThreadYield("ALex");
ThreadYield thread2 = new ThreadYield("Brecher");
thread1.start();
thread2.start();
}
}
这里结果表示ALEX执行到i=3的时候,会把CPU让出来,这时候BRECHER抢到线程。
也有可能是BRECHER执行到i=3的时候,会把CPU让出来,这时候还是BRECHER抢到线程。
线程加入
join(),等待其他线程终止。在当前线程中调用另一个线程的join()方法,当前线程进入阻塞。知道另外一个线程结束,这个线程回到runnable
join():等待t线程终止
join是Thread类的一个方法,启动线程后直接调用,join()作用是等待该线程终止。该线程是指主线程等待子线程的终止
也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
1 | Thread t = new Athread(); |
为什么要用join?
如果子线程很耗时,主线程往往提前与子线程结束,万一主线程需要用子线程的处理结果,就是主线程需要等待子线程执行完成之后再结束。
如果不用join函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Thread1 extends Thread{
private String name;
public Thread1(String name){
super(name);
this.name = name;
}
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始运行");
for(int i=0;i<5;i++){
System.out.println("子线程"+name+"运行"+i);
try{
sleep((int)Math.random()*10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"线程结束运行");
}
public static void main(String[] args){
System.out.println(Thread.currentThread().getName()+"主线程开始运行");
Thread1 Athread = new Thread1("A");
Thread1 Bthread = new Thread1("B");
Athread.start();
Bthread.start();
System.out.println(Thread.currentThread().getName()+"主线程结束运行");
}
}
结果:
而在main函数中添加join方法
1 | public static void main(String[] args){ |
结果:
线程唤醒
object类的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象中等待,则会选择唤醒其中一个线程。线程调用其中一个wait方法,在对象的监视器上等待。知道当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。
1 |
|
运行代码,打开shell,输入jps,得到ThreadState的进程ID,然后 输入 jstack ID,得到线程消息
当线程创建之后,调用start方法开始运行,当执行wait()的时候进入等待状态,需要依靠其他线程的通知才能够返回到运行状态。
而超时等待状态相当于在等待状态基础上增加了超时限制,一旦过时,返回到运行状态。
如果线程调用同步方法但没有获得锁,线程会进入到阻塞状态。
常见线程名词解释
主线程:main()产生的线程
当前线程:Thread.currentThread()获取的进程
后台线程:为其他线程提供服务的线程,称为守护线程
前台线程:接收后台线程服务的线程
Daemon 线程
这是一种支持型线程,用作程序中后台调度以及支持性工作。
启动和终止线程
构造线程
确定线程所属的线程组,线程优先级,是否是Daemon线程等,线程将在堆内存中等待运行。
启动线程
start()方法将告知JVM,只要线程规划器空闲,应该立即启动start()方法的线程。
安全地终止线程
中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互方式。
同时还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。
main线程通过中断操作和cancel()方法使得countThread得以终止。
1 | import java.util.concurrent.TimeUnit; |
线程间通信
线程开始运行的时候,拥有自己栈空间。java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(对象以及成员变量分配的内存是在共享内存中,但每个线程可以拥有一份拷贝,可以加速程序的执行)
volatile关键字
其他线程对该变量进行改变的时候,可以让所有线程感知到变化。保证所有线程对变量访问的可见性。
synchronized 关键字
主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量访问的可见性与排他性,
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态
线程同步
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
假设P1,P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或者同步方法,P1,P2都可以调用他们
1 | public synchronized void methodAAA() |
上面这个函数当对象P1的不同线程执行这个同步方法的时候,会形成互斥,达到同步的效果。
1 | public void methodAAA(){ |
this指调用这个方法的对象
1 | public void method3(SomeObject so){ |
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;
1.线程同步的目的是为了保护多个线程访问一个资源的时候破坏资源
2.线程同步方法通过锁来实现,每个对象有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象的锁,其他这个对象的线程就无法再访问该对象的其他非同步方法。
3.对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
线程数据传递
通过构造方法
1 |
|
通过变量与方法
1 | public class MyThreadPrint implements Runnable{ |
waiting and notifying
等待与通知机制
notify() 通知一个在对象上等待的线程,使其从wait()方法返回,返回的前提是该线程获得了对象的锁。
wait()调用该方法线程进入WAITING状态,只有等待另外线程的通知或者被中断才会返回,调用wait()后会释放对象的锁。
Thread.join()
每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回
1 |
|