Java并发编程实践 重庆大学 学习笔记 I

第一章Java并发编程实践基础

进程通常由三部分组成。一部分是程序,一部分数据集合,另一部分被称为进程控制块(ProcessControlBlock,简记PCB)。

package simplethread;

public class MyThread1 extends Thread {
    public MyThread1(String name) {
        super(name);//传递线程的名字
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for (int i = 0; i < 5; i++) { //创建5个线程
            new MyThread1("thread" + i).start();
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {//输出线程名字和i
            System.out.println(this.getName() + ":" + i);
        }
    }
}

1.2.3 线程池

JDK提供的线程池一般分为3步:1)创建线程目标对象,可以是不同的,例如程序中的Runnner;2)使用Executors创建线程池,返回一个ExecutorService类型的对象;3)使用线程池执行线程目标对象,exec.execute(run),最后,结束线程池中的线程,exec.shutdown()。

package threadPoolTest;

import java.util.concurrent.ExecutorService;    //创建新线程的线程池
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String args[]) throws InterruptedException {

        // 在线程池中创建2个线程 -> 创建一个可重用固定线程数的线程池
        ExecutorService exec = Executors.newFixedThreadPool(2);

        // 创建100个线程目标对象
        for (int index = 0; index < 100; index++) {
            Runnable run = new Runner(index);
            // 执行线程目标对象
            exec.execute(run);
        }

        // shutdown
        exec.shutdown();
    }
}

// 线程目标对象
class Runner implements Runnable {
    int index = 0;
    public Runner(int index) {
        this.index = index;
    }

    @Override
    public void run() {
        long time = (long) (Math.random() * 1000);
        // 输出线程的名字和使用目标对象及休眠的时间
        System.out.println("线程:" + Thread.currentThread().getName() + "(目标对象" + index + ")" + ":Sleeping " + time + "ms");

        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {  }
    }
}

1.3 线程的基本控制

package threadJoinTest;

public class ThreadJoin extends Thread {
    static int result = 0;

    public ThreadJoin(String name) {
        super(name);
    }

    public static void main(String[] args) {
        System.out.println("主线程执行");

        Thread t = new ThreadJoin("计算线程");
        t.start();
        System.out.println("result:" + result);

        try {
            long start = System.nanoTime();

            t.join();   //wait for t finish

            long end = System.nanoTime();

            System.out.println((end - start) / 1000000 + "毫秒后:" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();    //打印异常栈轨迹Stack Trace
        }
    }

    @Override
    public void run() {
        System.out.println(this.getName() + "开始计算...");

        try {
            Thread.sleep(4000);
            System.out.println("run is over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        result = (int) (Math.random() * 10000);
        System.out.println(this.getName() + "结束计算:");
    }   
}

第二章构建线程安全应用程序

如果其他线程企图访问一个处于不可用状态的对象,该对象将不能正确响应从而产生无法预料的结果,如何避免这种情况发生是线程安全性的核心问题

一个类在可以被多个线程安全调用时就是线程安全的

2.2. Servlet的线程安全性

  1. 无状态Servlet
    2 有状态Servlet

import java.io.PrintWriter; //打印格式化对象的表示到文本输出流
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class webTest
 */
@WebServlet("/webTest")
public class webTest extends HttpServlet {
    private static final long serialVersionUID = 1L;

    int result = 0;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public webTest() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        String s1 = request.getParameter("num1");
        String s2 = request.getParameter("num2");

        if (s1 != null && s1 != null) {
            result = Integer.parseInt(s1) * Integer.parseInt(s2);
        } try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }       

        PrintWriter out = response.getWriter();
        out.print(result);
        out.close();
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
    }

}

运行

http://localhost:8080/test/StatefulServlet?num1=5&num2=80

要解决线程不安全性,其中一个主要的方法就是取消Servlet的实例变量,变成无状态的Servlet。另外一种方法是对共享数据进行同步操作。使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段

2.3. 同步与互斥

2.4. 同步与volatile

在确定内存访问如何排序以及合适,可以确保他们可见时所使用的规则被称为Java编程语言的内存模型

2.5. 活性

2.6. ThreadLocal变量

,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

JDK 5以后提供了泛型支持,ThreadLocal被定义为支持泛型:
public class ThreadLocal<T> extends Object

ThreadLocal vs 线程同步机制
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

2.7. 高级并发对象

第三章 使用JDK并发包构建程序

3.1 java.util.concurrent概述

3.2 原子量
是无锁算法(nonblocking algorithms),这些无锁算法使用低层原子化的机器指令,例如使用compare-and-swap(CAS)代替锁保证并发情况下数据的完整性

3.2.1 锁同步法

3.2.2 比较并交换

比较并交换(Compare And Swap)”或 CAS 的原语


CAS 操作包含三个操作数—— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作

说明CAS的工作原理(为了便于说明,用同步语法表示)。
package jdkapidemo;

public class SimulatedCAS {
    private int value;
    public synchronized int getValue() {
        return value;
    }

    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        if (value == expectedValue)
            value = newValue;

    return value;
    }
}
乐观并发控制
基于 CAS 的并发算法称为“无锁定算法”,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。

3.3 并发集合

3.3.1 队列Queue与BlockingQueue

3.4 同步器
3.4.1 Semaphore

3.4.2 Barrier

import java.text.SimpleDateFormat;
import java.util.Date; 
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors;

public class cyclicBarrierTest {
    // 徒步需要的时间: Shen
    private static int[] timeForWalk = { 5, 8, 15 }; 
    //自驾游
    private static int[] timeForSelf = { 1, 3, 4 };
    // 旅游大巴
    private static int[] timeForBus = {2, 4, 6};

    //时间格式化
    static String nowTime(){
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(new Date()) + ": ";
    }

    static class Tour implements Runnable {
        private int[] m_timeForUse;
        //一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
        private CyclicBarrier m_barrier;
        private String m_tourName;

        public Tour(CyclicBarrier barrier, String tourName, int[] timeUsed) {
            this.m_timeForUse = timeUsed;
            this.m_tourName = tourName;
            this.m_barrier = barrier;
        }

        public void run() {
            try{
                Thread.sleep(m_timeForUse[0] * 1000);
                System.out.println(nowTime() + m_tourName + "Reached Shenzhen");
                m_barrier.await();//

                Thread.sleep(m_timeForUse[1] * 1000);               
                System.out.println(nowTime() + m_tourName + "Reached Guangzhou");
                m_barrier.await();//到达中转

                Thread.sleep(m_timeForUse[2] * 1000);       
                System.out.println(nowTime() + m_tourName + " Reached Chongqing");
                m_barrier.await();
            } catch (InterruptedException e) {
            } catch (BrokenBarrierException e) {
            }
        }
    }

    public static void main(String[] args) { // 三个旅行
        Runnable runner = new Runnable() {
            //@Override是伪代码,表示重写
            @Override
            public void run() {
                System.out.println("We all are here.");
            }
        };

        //CyclicBarrier(int,Runnable): 当await的数量到达了设定的数量后,首先执行该Runnable对象   
        CyclicBarrier barrier = new CyclicBarrier(3,runner);
        //使用线程池
        ExecutorService exec = Executors.newFixedThreadPool(3);

        exec.submit(new Tour(barrier, "WalkTour", timeForWalk));
        exec.submit(new Tour(barrier, "SelfTour", timeForSelf));
        exec.submit(new Tour(barrier, "BusTour", timeForBus));
        exec.shutdown();
    }   
}

3.4.3 CountDownLatch

import java.util.concurrent.CountDownLatch;

public class Player  implements Runnable {

    private int id;
    private CountDownLatch begin;
    private CountDownLatch end;

    public Player(int i, CountDownLatch begin, CountDownLatch end) {
        // TODO Auto-generated constructor stub
        super();
        this.id = i;
        this.begin = begin;
        this.end = end;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try{
            begin.await();        //等待begin的状态为0
            Thread.sleep((long)(Math.random()*100));    //随机分配时间,即运动员完成时间
            System.out.println("Play"+id+" arrived.");
        }catch (InterruptedException e) {
            // TODO: handle exception
            e.printStackTrace();
        }finally{
            end.countDown();    //使end状态减1,最终减至0
        }
    }
}
/**
CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,
每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行
CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。
利用这种特性,可以让主线程等待子线程的结束。下面以一个模拟运动员比赛的例子加以说明。
*/

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class CountLatch {

    private static final int PLAYER_AMOUNT = 5;
    public CountLatch() {

     }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //对于每位运动员,CountDownLatch减1后即结束比赛
        CountDownLatch begin = new CountDownLatch(1);
        //对于整个比赛,所有运动员结束后才算结束
        CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);
        Player[] plays = new Player[PLAYER_AMOUNT];

        for(int i=0;i<PLAYER_AMOUNT;i++)
            plays[i] = new Player(i+1,begin,end);

        //设置特定的线程池,大小为5
        ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);
        for(Player p:plays)
            exe.execute(p);            //分配线程

        System.out.println("Race begins!");
        begin.countDown();  //启动命令
        try{
            end.await();            //等待end状态变为0,即为比赛结束
        }catch (InterruptedException e) {
            // TODO: handle exception
            e.printStackTrace();
        }finally{
            System.out.println("Race ends!");
        }
        exe.shutdown();
    }

}

3.4.5 Future和Future work

接口public interface Future<V>

3.5 显示锁
3.5.1 ReentrantLock

3.6 Fork-Join框架

关于线程池,参考 http://www.cnblogs.com/zrtqsk/p/3784049.html
http://www.cnblogs.com/zrtqsk/p/3776328.html
这里写图片描述

if (当前这个任务工作量足够小)
    直接完成这个任务
else
    将这个任务或这部分工作分解成两个部分
    分别触发(invoke)这两个子任务的执行,并等待结果
Java7引入了Fork Join的概念,而此书成书较早,尚无正式Fork-join示例。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo {
    public static void main(String[] args) {
        long beginTime = System.nanoTime();     
        System.out.println("The sum from 1 to 1000 is " + sum(1, 1000));
        System.out.println("Time consumed(nano second) By recursive algorithm : " + (System.nanoTime() - beginTime));


        beginTime = System.nanoTime();   
        System.out.println("The sum from 1 to 1000000000 is " + sum1(1, 1000000000));
        System.out.println("Time consumed(nano second) By loop algorithm : " + (System.nanoTime() - beginTime));


        ForkJoinDemo app = new ForkJoinDemo();
        //orkJoinPool是一个可以执行ForkJoinTask的ExcuteService,
        //与ExcuteService不同的是它采用了work-stealing模式:
        //所有在池中的线程尝试去执行其他线程创建的子任务,这样就很少有线程处于空闲状态,非常高效。
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //ForkJoinTask代表一个需要执行的任务
        CountTask task = app.new CountTask(1,1000000000);

        beginTime = System.nanoTime();
        Future<Long> result = forkJoinPool.submit(task);
        try{
            System.out.println("The sum from 1 to 1000000000 is " + result.get());        
        }
        catch(Exception e){
            e.printStackTrace();
        }

        System.out.println("Time consumed(nano second) By ForkJoin algorithm : " + (System.nanoTime() - beginTime));
    }

    private static long sum1(long start, long end) {
        long s = 0l;

        for(long i=start; i<= end; i++){
            s += i;
        }

        return s;
    }

    private static long sum(long start, long end){
        if(end > start){
            return end + sum(start, end-1);
        }
        else{
            return start;
        }
    }

    //待执行任务
    private class CountTask extends RecursiveTask<Long>{
        private static final int THRESHOLD = 10000;
        private int start;
        private int end;

        public CountTask(int start, int end){
            this.start = start;
            this.end = end;
        }

         //重载 protected void compute() 方法
        protected Long compute(){
            //System.out.println("Thread ID: " + Thread.currentThread().getId());

            Long sum = 0l;

            if((end -start) <= THRESHOLD){
                sum = sum1(start, end);
            }
            else{
                int middle = (start + end) / 2;
                CountTask leftTask = new CountTask(start, middle);
                CountTask rightTask = new CountTask(middle + 1, end);
                leftTask.fork();
                rightTask.fork();

                Long leftResult = leftTask.join();
                Long rightResult = rightTask.join();

                sum = leftResult + rightResult;
            }

            return sum;
        }
    }   
}

第4章 使用开源软件构建并发应用程序
4.1 开源软件Amino介绍
Amino是Apache旗下的开源软件。http://amino-cbbs.sourceforge.net/

4.4 Amino使用的模式和调度算法

在Amino开源代码中提供了Master-Worker工厂摸式

第5章 数据冲突及诊断工具MTRAT

Java的数据有两种基本类型内存分配模式(不算虚拟机内部类型,详细内容参见虚拟机规范):运行时栈和堆两种。由于运行时栈是线程所私有的,它主要用来保存局部变量和中间运算结果,因此它们的数据是不可能被线程之间所共享的。内存堆是创建类对象和数组地方,它们是被虚拟机内各个线程所共享的,因此如果一个线程能获得某个堆对象的引用,那么就称这个对象是对该线程可见的。

编写线程安全的代码,本质上就是管理对状态(state)的访问,而且通常这些状态都是共享的、可变的。

5.1.5 ThreadLocal

5.2使用阻塞队列的生产者-消费者模式

5.3 MTRAT介绍

Mtrat软件下载网址是http://www.alphaworks.ibm.com/tech/mtrat

第6章 死锁

6.4减小锁的竞争和粒度

6.5使用MTRAT诊断死锁


第七章 显示锁

7.3 Lock与Condition


第八章原子变量与非阻塞算法


9 Java内存模型
此章几乎没内容

9.1.2 发生前关系(happen-before)

9.2 初始化安全性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值