`
hawking.ye
  • 浏览: 5503 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Java多线程初探--基础篇

    博客分类:
  • 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 生产者与消费者模型

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

 

  • 大小: 220.3 KB
  • 大小: 52.7 KB
分享到:
评论

相关推荐

    JAVA学习手册CHM版

    全书共20章,分为4篇进行介绍,第1篇为基础篇,包括Java开发前奏、搭建Java开发环境、 扎实Java语言基本语法、掌握算法和流程控制、使用Eclipse开发工具、探秘面向对象程序设计、初探Java数组;第2篇为核心篇,包括...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    3.4 小结:基本数据类型—— Java中一切数据和运算的基础 63 3.5 习题 65 第4章 Java中的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 ...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    3.4 小结:基本数据类型—— Java中一切数据和运算的基础 63 3.5 习题 65 第4章 Java中的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 ...

    Android应用开发揭秘pdf高清版

    第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命周期 3.3 Android程序U设计 3.4 小结 第4章 用户界面开发 4.1 用户界面开发...

    asp.net知识库

    技术基础 New Folder 多样式星期名字转换 [Design, C#] .NET关于string转换的一个小Bug Regular Expressions 完整的在.net后台执行javascript脚本集合 ASP.NET 中的正则表达式 常用的匹配正则表达式和实例 经典正则...

    android开发揭秘PDF

    第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命周期 3.3 Android程序U设计 3.4 小结 第4章 用户界面开发 4.1 用户界面开发...

    《Android应用开发揭秘》源码

     第二部分 基础篇  第3章 Android程序设计基础  3.1 Android程序框架  3.1.1 Android项目目录结构  3.1.2 Android应用解析  3.2 Android的生命周期  3.3 Android程序U设计  3.4 小结  第4章 用户界面开发 ...

    《Android应用开发揭秘》附带光盘代码.

     第二部分 基础篇  第3章 Android程序设计基础  3.1 Android程序框架  3.1.1 Android项目目录结构  3.1.2 Android应用解析  3.2 Android的生命周期  3.3 Android程序U设计  3.4 小结  第4章 用户界面开发 ...

Global site tag (gtag.js) - Google Analytics