Java多线程初探--基础篇

       最近一段时间在看多线程这方面的资料,感觉多线程在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 生产者与消费者模型

扫描二维码关注公众号,回复: 789136 查看本文章

       下面是一个多线程生产者与消费者的例子,在例子中存在一个资源数组,数组的容量为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操作不能并行执行。

猜你喜欢

转载自hawking-ye.iteye.com/blog/2231752