Java Thread
1,线程的基本概念
线程是一个程序内部的顺序控制流。
线程和进程的区别:(资源分配和处理器分配的基本单元)
①,每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销;
②,线程可以看成是轻量级进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小;
③,多进程:操作系统中能同时运行多个任务(程序);
④,多线程:在同一应用程序中有多个顺序流同时执行;
Java线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法(public static void main(){})所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
注意:
要注意方法调用和运行线程的区别。方法调用,主程序会等方法运行完成程序再继续往下走;运行线程:相当于新建一个分支,主程序与线程并行执行。
2,线程的创建和启动
第一种:定义线程类实现Runnable接口
Thread thread = new Thread(target);//target为Runnable接口类型
Runnable中只有一个方法: public void run();用以定义线程运行体。
使用Runnable接口可以为多个线程提供共享的数据。在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法:
public static Thread currentThread()获取当前线程的引用。
例如:
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();//线程启动
t.run();//方法调用
}
class MyThread implements Runnable {
public void run() {
...
}
}
第二种:定义一个Thread的子类,并重写其run方法,如:
class MyThread extends Thread {
public void run() {...}
}
然后生成该类的对象:MyThread thread = new MyThread(...);
例如:
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
class MyThread extends Thread {
public void run() {
...
}
}
3,线程的状态
3.1,线程的状态类型:
①,新建状态(New):创建了一个线程对象,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源;
②,就绪状态(Runnable):在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会;
③,运行状态(Running):就绪状态的线程获取了CPU,执行程序代码;
④,阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1),等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池(waitting queue)中;
(2),同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中;
(3),其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超市、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;
⑤,挂起状态(Suspend):可以通过调用suspend方法(已过时)将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用resume方法(已过时)恢复线程运行;
⑥,死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
3.2,线程状态转换图
3.3,阻塞和挂起的区别:
操作系统中挂起和阻塞的区别如下:
(1),挂起是一种主动行为,因此恢复也应该要主动完成,而阻塞则是一种被动行为,是在等待事件或资源时任务的表现,你不知道他什么时候被阻塞(pend),也就不能确切 的知道他什么时候恢复阻塞。而且挂起队列在操作系统里可以看成一个,而阻塞队列则是不同的事件或资源(如信号量)就有自己的队列;
(2),阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放CPU,如果任务优先级高就永远轮不到其他任务运行,一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试;
(3),阻塞(pend)是task主动去等一个事件或消息,suspend是直接悬挂task,以后这个task和你没任何关系,任何task间的通信或者同步都和这个suspended task没任何关系了,除非你resume task;
(4),任务调度是操作系统来实现的,任务调度时,直接忽略挂起状态的任务,但是会顾及处于pend下的任务,当pend下的任务等待的资源就绪后,就可以转为ready了。ready只需要等待CPU时间,当然,任务调度也占用开销,但是不大,可以忽略。可以这样理解,只要是挂起状态,操作系统就不在管理这个任务了;
(5),挂起是主动的,一般需要用挂起函数进行操作,若没有resume的动作,则此任务一直不会ready。而阻塞是因为资源被其他任务抢占而处于休眠态。两者的表现方式都是从就绪态里“清掉”,即对应标志位清零,只不过实现方式不一样。
3.4,wait和sleep 的区别
(1),这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
(2),最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
(3),使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
(4),sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
3.5,线程控制的基本方法
isAlive() | 判断线程是否还“活着”,即线程是否还未终止 |
getPriority() | 获得线程的优先级数值 |
setPriority(int newPriority) | 设置线程的优先级数值 |
sleep(long millis) sleep(long millis, int nanos) |
将当前线程睡眠指定时间 |
join() | 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行 |
yield() | 让出CPU,当前线程进入就绪队列等待调度 |
wait() | 当前线程进入线程等待池(wait pool) |
notify() notifyAll() |
唤醒对象的wait pool中的一个/所有等待线程 |
3.5.1,线程优先级
Java提供一个线程调度器来监控程序中就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。线程的优先级用数字标志,范围1~10,缺省优先级为5
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
使用以下方法获得或这只线程对象的优先级:
public final void setPriority(int paramInt) {
checkAccess();
if ((paramInt > 10) || (paramInt < 1)) {
throw new IllegalArgumentException();
}
ThreadGroup localThreadGroup;
if ((localThreadGroup = getThreadGroup()) != null) {
if (paramInt > localThreadGroup.getMaxPriority()) {
paramInt = localThreadGroup.getMaxPriority();
}
setPriority0(this.priority = paramInt);
}
}
public final int getPriority() {
return this.priority;
}
3.5.2,sleep/join/yield方法
(1),sleep:可以调用Thread的静态方法sleep使得当前线程休眠,由于是静态方法,sleep可以直接由类名直接调用。
例子:
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try {
Thread.sleep(10000);//主线程睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();//打断子线程(不推荐的方法,还有stop())
//thread.flag = false;
}
}
class MyThread extends Thread {
boolean flag = true;
public void run() {
while(flag) {
System.out.println("====" + new Date() + "====");
try {
sleep(1000);
} catch (InterruptedException e) {
return;//sleep时出现异常则线程直接结束
}
}
}
}
结果:
====Tue Jun 14 11:05:30 CST 2016====
====Tue Jun 14 11:05:31 CST 2016====
====Tue Jun 14 11:05:32 CST 2016====
====Tue Jun 14 11:05:33 CST 2016====
====Tue Jun 14 11:05:34 CST 2016====
====Tue Jun 14 11:05:35 CST 2016====
====Tue Jun 14 11:05:36 CST 2016====
====Tue Jun 14 11:05:37 CST 2016====
====Tue Jun 14 11:05:38 CST 2016====
====Tue Jun 14 11:05:39 CST 2016====
源码:
public static native void sleep(long paramLong) throws InterruptedException;
public static void sleep(long paramLong, int paramInt)
throws InterruptedException {
if (paramLong < 0L) {
throw new IllegalArgumentException("timeout value is negative");
}
if ((paramInt < 0) || (paramInt > 999999)) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if ((paramInt >= 500000) || ((paramInt != 0) && (paramLong == 0L))) {
paramLong += 1L;
}
sleep(paramLong);
}
(2),join:合并线程
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread("abcde");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("I am main thread");
}
}
}
class MyThread extends Thread {
MyThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("I am " + getName());
}
}
}
结果:
I am abcde
I am abcde
I am abcde
I am main thread
I am main thread
I am main thread
源码:
public final void join() throws InterruptedException {
join(0L);
}
public final synchronized void join(long paramLong)
throws InterruptedException {
long l1 = System.currentTimeMillis();
long l2 = 0L;
if (paramLong < 0L) {
throw new IllegalArgumentException("timeout value is negative");
}
if (paramLong == 0L) {
while (isAlive()) {
wait(0L);
}
}
while (isAlive()) {
long l3 = paramLong - l2;
if (l3 <= 0L) {
break;
}
wait(l3);
l2 = System.currentTimeMillis() - l1;
}
}
(3),yield:让出CPU,给其他线程执行的机会
例子:
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
t1.start();
t2.start();
}
}
class MyThread extends Thread {
MyThread(String name) {
super(name);
}
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
if(i%10 == 0) {
yield();
}
}
}
}
结果:
每当被10整除的时候会yield
...
t1:14
t2:70
t1:15
...
...
t2:72
t1:20
t2:73
...
t2:76
t1:30
t2:77
...
源码:
public static native void yield();
4,线程同步
在Java语言中,引入了对象互斥锁的概念,保证共享数据错做的完整性,每个对象对应于一个可称为”互斥锁“的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
例一:
public class Test implements Runnable {
Timer timer = new Timer();
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
private static int num = 0;
public synchronized void add(String name) {
// synchronized (this) {
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(name + ", 你是第" + num + "个使用timer的线程");
// }
}
}
结果一(可能):
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程
加上synchronized控制
结果二:
t1, 你是第1个使用timer的线程
t2, 你是第2个使用timer的线程
例二:
public class Test implements Runnable {
public int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
@Override
public void run() {
System.out.println("flag = " + flag);
if(flag == 1) {
synchronized (o1) {//获得对象o1锁
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {//获得对象o2锁
System.out.println(1);
}
}
}
if(flag == 0) {
synchronized (o2) {//获得对象o2锁
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {//获得对象o1锁
System.out.println(0);
}
}
}
}
public static void main(String[] args) {
Test test1 = new Test();
Test test2 = new Test();
test1.flag = 1;
test2.flag = 0;
Thread t1 = new Thread(test1);
Thread t2 = new Thread(test2);
t1.start();
t2.start();
}
}
结果:
flag = 0
flag = 1
分别在等待获得o1,o2对象锁的,程序死锁,无法结束
例三:
哲学家吃饭问题
5,生产者消费者问题
public class Test {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(c).start();
new Thread(p).start();
new Thread(p).start();//多加个生产者线程
}
}
class WoTou {
int id;
WoTou(int id) {
this.id = id;
}
public String toString() {
return "WoTou:" + id;
}
}
class SyncStack {
int index = 0;
WoTou[] arrWT = new WoTou[5];
//push
public synchronized void push(WoTou wt) {
while(index == arrWT.length) {
try {
this.wait();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();//唤醒其他所有线程
arrWT[index] = wt;
index++;
}
//pop
public synchronized WoTou pop() {
while (index == 0) {
try {
this.wait();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();//唤醒其他所有线程
index--;
return arrWT[index];
}
}
//生产者
class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss) {
this.ss = ss;
}
@Override
public void run() {
for(int i=1; i<= 10; i++) {
WoTou wt = new WoTou(i);//produce
ss.push(wt);
System.out.println("生产了:" + wt);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
WoTou wt = ss.pop();
System.out.println("消费了:" + wt);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
生产了:WoTou:1
消费了:WoTou:1
生产了:WoTou:1
生产了:WoTou:2
消费了:WoTou:2
生产了:WoTou:2
消费了:WoTou:2
生产了:WoTou:3
生产了:WoTou:3
生产了:WoTou:4
生产了:WoTou:4
消费了:WoTou:4
生产了:WoTou:5
消费了:WoTou:5
生产了:WoTou:5
生产了:WoTou:6
消费了:WoTou:5
消费了:WoTou:6
生产了:WoTou:6
生产了:WoTou:7
消费了:WoTou:6
消费了:WoTou:7
生产了:WoTou:8
生产了:WoTou:9
消费了:WoTou:8