最近写多篇关于多线程的博客,这里做个总结。线程从创建到消亡的过程中,可能会经历五种状态:
- New:线程刚被创建。
- Runnable:线程的start方法被调用后所处的状态,该状态下,线程才有了竞争时间片的可能,即可运行态。
- Running:线程正在执行run方法
- Dead:执行完run方法,或者被stop
- Blocked:线程放弃CPU使用权,进入阻塞状态。该状态下,线程可能重新被赋予CPU使用权进入Runnable状态,也可能直接消亡。阻塞状态可以细分成三种:
- 等待阻塞:Runnable状态的的中运行Object的wait方法,被JVM放入等待池。
- 同步阻塞:线程竞争同步锁失败,被JVM放入锁池。
- 其他:线程执行sleep、Join等方法,被JVM置为阻塞状态,等待JVM重置状态为Runnable。
Java中实现多线程的方式主要有三种:继承Thread类、实现Runnable接口与使用线程池。
继承Thread类与实现Runnable接口
需要重写Thread类的run方法,或者实现Runnable的run方法,通过调用线程的start方法,启动线程。直接上例子:
package test.thread;
import java.util.LinkedList;
import java.util.Queue;
public class ThreadTest1 {
public static void main(String[] args) {
final Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < 1000; i++) {
queue.add(i);
}
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
synchronized (queue) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName() + ": " + queue.poll());
}
}
}
});
threads[i].start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (queue) {
queue.notifyAll();
}
}
}
上面例子,在主线程与10个子线程中都用到了共享变量queue。通过调用queue的wait方法阻塞子线程,等待主线程调用notifyAll方法时,重新进入Runnable状态,保证了线程安全。需要注意object的wait、notify、notifyAll等方法需要放在同步块中。
同样的功能,我们也可以这样实现:
package test.thread;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest2 {
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
final Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < 1000; i++) {
queue.add(i);
}
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
condition.await();
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName() + ": " + queue.poll());
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}
});
threads[i].start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
condition.signalAll();
} finally {
lock.unlock();
}
}
}
上面例子用到了ReentrantLock,相比synchronized关键字提供更加精细的同步锁功能。使用时需要注意要把锁的unlock方法,置于finally块中,避免线程异常中断,锁不被释放。
我们还可以这样实现:
package test.thread;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;
public class ThreadTest3 {
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(1);
final Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < 1000; i++) {
queue.add(i);
}
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire(1);
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName() + ": " + queue.poll());
}
} catch (Exception e1) {
e1.printStackTrace();
} finally {
semaphore.release(1);
}
}
});
threads[i].start();
}
}
}
这个例子中,我们用到了信号量。当线程获得semaphore时,如果semaphore的内部计数值大于0,就会减少计数值并允许访问共享资源。
使用线程池
实现线程池主要通过Executors工具类以及ThreadPoolExecutor线程池实现类。ThreadPoolExecutor是线程池的核心功能,这里先挖个坑,后面有时间再整理。Executors提供了四种线程池:
newCachedThreadPool
该方法创建一个可缓存的线程。线程池无线大,可以复用已经执行完成的线程。
newFixedThreadPool
该方法创建一个定长的线程。可以实现并发线程个数,超过并发线程个数的线程进入等待队列。
newScheduledThreadPool
该方法创建一个定长的线程,支持周期性任务的执行。
newSingleThreadExecutor
该方法创建一个单线程化的线程池,同一时间只有一个线程在执行任务。