最近一段时间在看多线程这方面的资料,感觉多线程在Java开发中是非常重要的。图1为Java多线程编程中涉及的几个重要的知识点。总结下主要包含4大块:a.多线程的创建;b.多线程的同步与通信;c.java.util.concurrent包(里面包含并发容器和线程池等);d.Thread对象中重要的成员属性。上面每一点都包括很多内容可以讲述。本文主要从以下两点进行讲述多线程编程基础:1.多线程基本知识;2.多线程是如何实现经典的生产者消费者模型的。后续还会对线程池与ThreadLocal进行总结。
图1 Java多线程编程知识点
基础篇
谈到多线程,我们知道多线程程序运行时要保证线程安全,那什么是线程安全呢? 线程安全就是多个线程并发运行同一段代码时,如果每次运行的结果和单线程运行结果一致则说明线程是安全的。那造成线程不安全的原因是什么呢?在JVM中存在着一个main memory,每个线程都有自己的woking memory,线程对变量执行操作的过程如下:1.从主存拷贝对象变量副本到工作内存;2.执行操作;3.将执行结果写入main memory中。如果多个线程同时操作一个变量时,就有可能导致线程不安全现象的产生。看到这也许我们在想,是否能够将共享的变量定义为volatile就可以解决线程安全的问题,其实并不是如此。
多线程的创建包含有3种方法:1.继承Thread类;2.实现Runnable接口;3.实现Callable接口。其中采用第2种方式和第3种方式创建线程的区别是:后者可以返回线程执行后的值,而前者不能返回线程执行后的值。上面提到线程安全的概念,那么多线程是怎么保证线程安全的呢?在多线程中需要保证线程互斥地访问共享资源。具体同步的方式包括有使用synchronized关键字,该关键字可以在代码块上修饰,也可以加在类成员方法前修饰。
public void test() {
synchronized(this) {
}
}
在代码块中修改。可以锁住this对象也可以锁住该对象中的某个共享的成员变量
public synchronized void test() {
}
在非静态成员方法前修饰,会对对象进行加锁
public synchronized static void test() {
}
在静态成员方法前修饰,会对类进行加锁
在加锁的过程中需要注意死锁情况的出现。线程同步只能保证线程按找一定的顺序并发地访问某个共享资源,但有时线程之间还存在着通信关系。 线程间的通信方法包括: 采用wait/notify的方式进行通信;采用信号量的方式进行通信等等。 此外,多线程编程中也存在乐观锁与悲观锁的概念。其中CAS(Compare And Swap)是一种乐观锁的形式进行线程同步,它的操作对象为volatile类型。
生产者与消费者模型
生成者消费者模型是多线程编程中非常经典的一个例子(如图2所示)。生成者将生成的资源放入队列中,消费者从队列中取出资源。
图2 生产者与消费者模型
下面是一个多线程生产者与消费者的例子,在例子中存在一个资源数组,数组的容量为10。Producer每次产生10个对象,Consumer每次会消费10个对象。运行这段代码之前我们的期望是2个Producer生产的资源都能够被Consumer消费。运行程序,结果符合我们的预期。
/**
* 共享资源定义
*/
public class Resource {
private final int MAX_SIZE = 10;
List<Object> buffer = new ArrayList<Object>();
public synchronized void put(Object object) throws InterruptedException {
while (buffer.size() == MAX_SIZE) {
System.out.println("缓冲区满啦!");
this.wait();
}
buffer.add(object);
this.notifyAll();
System.out.println(Thread.currentThread().getName());
System.out.println("放入一个对象" + "size=" + buffer.size());
}
public synchronized void take() throws InterruptedException {
while (buffer.size() == 0) {
System.out.println("缓冲区空啦!");
this.wait();
}
buffer.remove(0);
this.notify();
System.out.println(Thread.currentThread().getName());
System.out.println("取出一个对象" + "size=" + buffer.size());
}
}
public class Executor {
public static void main(String[] args) {
Executor executor = new Executor();
Resource resource = new Resource();
Thread th1 = new Thread(executor.new Produce(resource));
Thread th2 = new Thread(executor.new Produce(resource));
Thread cs1 = new Thread(executor.new Consumer(resource));
Thread cs2 = new Thread(executor.new Consumer(resource));
th1.start();
th2.start();
cs1.start();
cs2.start();
}
//生产者线程
private class Produce implements Runnable {
Resource resource;
Produce(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
resource.put(new Object());
Thread.sleep(10);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
//消费者线程
private class Consumer implements Runnable {
Resource resource;
Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
resource.take();
Thread.sleep(10);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
}
如果我们将Resource类中的this.wait()和this.notifyAll()改成buffer.wait()与buffer.notifyAll()会出现什么结果,咋一看应该不会出现什么问题。因为buffer作为共享资源,如果buffer为空则把等待该资源的线程放入buffer的阻塞队列中,否则从阻塞队列中唤醒一个线程进行消费该资源。改造后的Resource类如下:
/**
* 共享资源
*/
public class Resource {
private final int MAX_SIZE = 10;
List<Object> buffer = new ArrayList<Object>();
public synchronized void put(Object object) throws InterruptedException {
while (buffer.size() == MAX_SIZE) {
System.out.println("缓冲区满啦!");
buffer.wait();
}
buffer.add(object);
buffer.notifyAll();
System.out.println(Thread.currentThread().getName());
System.out.println("放入一个对象" + "size=" + buffer.size());
}
public synchronized void take() throws InterruptedException {
while (buffer.size() == 0) {
System.out.println("缓冲区空啦!");
buffer.wait();
}
buffer.remove(0);
buffer.notify();
System.out.println(Thread.currentThread().getName());
System.out.println("取出一个对象" + "size=" + buffer.size());
}
}
运行该程序出现了如下错误:
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.lang.Object.notifyAll(Native Method)
at mutilThread.Resource.put(Resource.java:19)
at mutilThread.Executor$Produce.run(Executor.java:29)
at java.lang.Thread.run(Thread.java:695)
产生该错误的原因是线程没有buffer对象的监视器,换句话说只能在同步对象上调用notifyAll方法,而不能在非同步对象上调用notifyAll方法。此外,在上例中put操作与take操作都用synchronized修饰,说明在同一时刻只能由一个线程执行take操作或者put操作,take操作与put操作不能并行执行。