转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/45311707 来自《LiaBin的博客》
1. Java多线程状态
在网上随便搜索的一幅java多线程状态图
各个状态解释如下:
1. 新建状态(New):新创建了一个线程对象。
2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、阻塞在对象等待池中:运行的线程执行wait()方法,JVM会把该线程放入对象等待池中。
(二)、阻塞在对象锁池中:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入对象锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
重点注意事项;
1. sleep()和wait()方法的区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
调用sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,此时线程进入Blocked态,其它线程可以获取CPU时间,该线程指定的时间到了又会自动恢复Runnable状态,此时同其它线程一起竞争CPU时间。同时注意在sleep时间内,如果该线程持有某个对象的锁,那么该线程不会释放对象锁,就是说所有其它在等待该对象锁的线程都没法执行。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待池中,此时该线程阻塞在对象等待池,也就说此时处于阻塞在对象锁池中的其它线程就能被调度获取CPU时间执行。只有针对此对象调用notify()/notifyall()方法后本线程才能进入对象的锁定池中准备,如果该对象其它对象在使用,那么该线程仍然阻塞在对象锁池中;否则进入runnable状态。
2. notify()和notifyall()的区别
官方文档解释:
notifyall(): Wakes up all threads that are waiting on this object's monitor. A thread waits on an object's monitor by calling one of the wait methods.
notify(): Wakes up a single thread that is waiting on this object'smonitor. If any threads are waiting on this object, one of them is chosen to be awakened
一目了然,总结的说notifyall唤醒所有阻塞在该对象等待池中的线程,而notify只是唤醒该对象等待池中的线程的一个,具体是哪个,看CPU的调度
2. 编程实例
1. 生产者消费者模型
package hello.demo;
public class ProduceConsume {
private int maxNum = 100;
private int currentNum = 0;
public ProduceConsume(int maxNum) {
this.maxNum = maxNum;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ProduceConsume test = new ProduceConsume(100);
MyConsumeThread ct1 = new MyConsumeThread("Consume1", test, 40);
MyConsumeThread ct2 = new MyConsumeThread("Consume2", test, 60);
MyConsumeThread ct3 = new MyConsumeThread("Consume3", test, 20);
MyProduceThread pt1 = new MyProduceThread("Produce1", test, 10);
MyProduceThread pt2 = new MyProduceThread("Produce2", test, 40);
MyProduceThread pt3 = new MyProduceThread("Produce3", test, 5);
MyProduceThread pt4 = new MyProduceThread("Produce4", test, 50);
ct1.start();
ct2.start();
ct3.start();
pt1.start();
pt2.start();
pt3.start();
pt4.start();
}
// synchronized修饰普通方法,对象锁
public synchronized void produce(int number) {
while (currentNum + number > maxNum) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
currentNum += number;
System.out.println(Thread.currentThread().getName()
+ "----had produced: " + number + " currentNum:" + currentNum);
this.notifyAll();
}
public synchronized void consume(int number) {
while (currentNum - number < 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
currentNum -= number;
System.out.println(Thread.currentThread().getName()
+ "----had consumed: " + number + " currentNum:" + currentNum);
this.notifyAll();
}
}
class MyProduceThread extends Thread {
private ProduceConsume test;
private int number;
public MyProduceThread(String name, ProduceConsume test, int number) {
// TODO Auto-generated constructor stub
super(name);
this.number = number;
this.test = test;
}
@Override
public void run() {
test.produce(number);
}
}
class MyConsumeThread extends Thread {
private ProduceConsume test;
private int number;
public MyConsumeThread(String name, ProduceConsume test, int number) {
super(name);
this.number = number;
this.test = test;
}
@Override
public void run() {
// TODO Auto-generated method stub
test.consume(number);
}
}
输出结果:此时输出结果不确定
Produce1----had produced: 10 currentNum:10
Produce4----had produced: 50 currentNum:60
Produce3----had produced: 5 currentNum:65
Consume1----had consumed: 40 currentNum:25
Consume3----had consumed: 20 currentNum:5
Produce2----had produced: 40 currentNum:45
此时Consume2消费进程永远阻塞在对象的等待池中,因为最后currentNum:45<60,所以该线程进入对象的等待池,但是后来也没有生产者线程在生产所以不能消费。
2. 实现3个线程依次打印ABC,并且循环10次
import java.util.concurrent.atomic.AtomicInteger;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
AtomicInteger syncObj = new AtomicInteger();
PrintThread pt1 = new PrintThread("A", 0, syncObj);
PrintThread pt2 = new PrintThread("B", 1, syncObj);
PrintThread pt3 = new PrintThread("C", 2, syncObj);
pt1.start();
pt2.start();
pt3.start();
}
}
class PrintThread extends Thread {
private int flag;
private AtomicInteger syncObj;
private String name;
private int count = 0;
public PrintThread(String name, int flag, AtomicInteger syncObj) {
this.flag = flag;
this.syncObj = syncObj;
this.name = name;
}
@Override
public void run() {
while (true) {
// 线程进入该代码段,需要获取syncObj对象锁,如果此时没有持有syncObj对象锁则该线程进入该对象的锁池中
synchronized (syncObj) {
// 关键代码逻辑
if (syncObj.get() % 3 == flag) {
System.out.print(name);
// 该线程执行一次打印任务即进入对象的等待池中,等待被唤醒
syncObj.set(syncObj.get() + 1);
count++;
syncObj.notifyAll();// 同时唤醒其它阻塞在该对象等待池中的线程
if (count == 10) {
break;
}
} else {
try {
syncObj.wait();// 该线程进入对象的等待池中,等待调用syncObj.notifyAll()被唤醒。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
输出结果:ABCABCABCABCABCABCABCABCABCABC
常见问题
1. 最大并发数开多少合适
原则上,可以无限大。但是开线程对内存的消耗很大,所以一般使用线程池管理线程
2. 线程怎么停止
参考《如何正确停止java中的线程》 Asynctask的cancel其实是调用了Thread的interrupt方法终止线程