首先,java创建线程的方式有两种,一个是继承Thread类,一个是实现Runnable接口,这两种方式线程执行完后没有返回值。
第一种,继承Thread类:
写一个Thread类的子类MyThread
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
其实,Thread类也是实现了Runnable接口的,启动线程时调用Thread类的start()方法,此时它会执行run()方法,也就是我们在子类MyThread中重写的run()方法。这种继承的方式虽然简单,但是因为java不支持多继承,因此当一个类已经继承了一个父类时,无法再用这种方式实现多线程,所以还有另外一种更灵活的方式。
第二种,实现Runnable接口:
public class MyThread extends FatherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
我们看到,Runnable接口并没有任何对线程的支持,我们还必须创建Thread类的实例,通过Thread类的构造函数来实现将MyThread的实例传给Thread实例。调用thread的start()方法启动线程,此时thread会执行自身的run()方法,由于如下代码,最终调用的是我们在MyThread中实现的run()方法。
public void run() {
if (target != null) {
target.run();
}
}
其次,线程的主要方法sleep(),join(),yield(),wait(),notify()?前三个方法是Thread类中的方法,后两个是Object类中的方法。它们对线程有什么样的影响呢?
线程的五种状态如图所示:
1.sleep方法:
让当前线程暂停指定的时间,线程睡眠时进入阻塞状态,到期自动苏醒,并返回到就绪状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
2.yield方法:
线程是存在优先级的,设置范围在1~10之间,默认是5。JVM线程调度程序是基于优先级的抢先调度机制。当线程池中的线程具有相同的优先级时,调度程序的JVM有两种可能的调度方式:要么选择一个线程运行直到它运行完成或阻塞,要么时间分片,使线程池内的每个线程有均等的运行机会。所以,依赖于优先级往往不是那么靠谱的。
yield方法会暂停当前正在运行的线程,该线程进入就绪状态,并执行其他线程,是一种让步行为。yield为其他具有相同优先级的线程提供了获得运行的机会,使得这些线程能适当的轮询,但不排除让步的线程被再次选中运行。
3.join方法:
暂停当前线程,直到join的线程运行完成,再继续执行。作用在于将异步执行的线程合并为同步(顺序)执行的线程。在join的源码实现中,使用了wait方法来将线程阻塞。
4.wait方法:
wait方法的使用必须在同步的范围内,否则就会抛出IllegalMonitorStateException异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒。wait方法是一个本地方法,其底层是通过一个叫做监视器锁的对象来完成的,java中只能通过Synchronized关键字获取到monitor对象的所有权。
5.notify/notify All方法:
同样的,notify方法的使用也必须在同步的范围内,wait是通过对象的monitor对象来实现的,所以只要在同一对象上调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程。notify和notifyAll的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程。
有一个典型的生产者与消费者问题,可以帮助我们深入理解并发编程中的线程协作。
/**
* 产品:苹果
*/
public class Apple {
int id;
Apple(int id){
this.id = id;
}
public String toString(){
return "Apple Id : " + id;
}
}
/**
* 生产者
*/
public class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss){
this.ss=ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Apple apple = new Apple(i);
ss.push(apple);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消费者
*/
public class Consumer implements Runnable {
SyncStack ss = null;
Consumer (SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Apple apple = ss.pop();
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 盛放苹果的栈容器
*/
public class SyncStack {
int index = 0;//个数
Apple[] arrApple = new Apple[6];
public synchronized void push(Apple apple){
while(index == arrApple.length ){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
arrApple[index] = apple;
System.out.println("生产了" + apple);
index++;
}
public synchronized Apple pop(){
while (index == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
System.out.println("消费了:" + arrApple[index]);
return arrApple[index];
}
public static void main(String[] args){
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(c).start();
}
}
解释一下这个经典问题:
容器中最多盛放6个苹果,生产者的线程每生产一个苹果时,会notify等待容器对象锁的线程,告诉它(们)容器中有苹果了,可以拿。至于消费者的线程什么时候拿这个苹果,得看JVM调度以后,也就是消费者线程获得对象锁,被CPU运行的时候。如果生产者生产的速度比消费者消费的速度快,也就是会出现容器放满苹果的现象,此时,生产者无法再继续生产苹果,发生阻塞事件,容器对象调用wait方法,生产者释放对容器对象的锁,进入阻塞状态,并等待被notify。消费者的线程开始执行,消费一个苹果,解除生产者的阻塞条件,此时调用notify方法,唤醒(所有)等待容器对象锁的线程,告诉它(们)容器中有空位了,可以放。等到消费者线程退出执行,生产者线程即可运行,如此协作。