前段时间因为阿里的大数据比赛接触到了几位科研达人的博客,深切感受到技术博客的重要性。现在准备参加网易的移动应用开发竞赛,于是便想把所学的Java 和Android 知识分享出来。就让我的第一篇博客从Java并发编程开始吧。
-------------------------------------------
1.并发(concurrency)编程的两个基本执行单位:进程(process)和线程(thread),在Java中主要涉及线程。
进程:具有自己的执行环境、完整的私有的资源、内存空间。常被看作应用程序的同义语。
线程:轻量(lightweight)进程,存在于进程内。进程至少有一个线程,进程的资源被它的线程共享。应用程序启动的第一个线程为主线程(main thread)。
2.线程定义、启动、暂停和中断
1)定义和启动
Java自带的Thread类定义了很多用于线程管理的方法。每个线程都和Thread类的一个实例相关联,创建Thread实例的程序必须提供线程中的运行代码(实现run方法)。有两种方法来启动新的线程:
i. 提供Runnable对象。Runnable是一个接口,定义了单一方法run。
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");//run方法的实现
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
ii.创建Thread的子类。Thread类本身实现Runnable,但是没有实现run方法。
public class HelloThread extends Thread {
public void run() {
System.out.println("Hello from a thread!");//run方法的实现
}
public static void main(String args[]) {
(new HelloThread()).start();
}
}
两种方法相比,第一种更加通用,因为Runnable对象可以子类化Thread之外的类。2)使用sleep暂停线程
Thread.sleep使当前线程的执行挂起一段指定的时间,以使其他线程能使用处理器。下面例子中SleepMessages使用sleep按4秒间隔输出消息。
public class SleepMessages {
public static void main(String args[])
throws InterruptedException {
String importantInfo[] = {
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"A kid will eat ivy too"
};
for (int i = 0;
i < importantInfo.length;
i++) {
//Pause for 4 seconds
Thread.sleep(4000);
//Print a message
System.out.println(importantInfo[i]);
}
}
}
中断表示线程停止正在进行的工作去进行其他工作。中断一个线程的方法:
i.抛出InterruptedException;
for (int i = 0; i < importantInfo.length; i++) {
// Pause for 4 seconds
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// We've been interrupted: no more messages.
return;
}
// Print a message
System.out.println(importantInfo[i]);
}
ii.用Thread.interrupted()检查是否发生中断
class ATask implements Runnable{
public void run() {
//检查程序是否发生中断
while (!Thread.interrupted()) {
System.out.println("I am running!");
}
System.out.println("ATask.run() interrupted!");
}
}
1)为什么要用同步
同步在这里可以理解成一种工具,它是为了防止线程通信中的两种错误:线程干扰(thread interference)和内存一致性错误(memory consistency error)。
i.线程干扰
两个不同的线程对相同数据进行操作时就会出现干扰。下面的例子中如果线程A和线程B同时引用Counter对象,A调用increment将c加1,B调用decrement将c减1,这种情况下A,B的运行结果可能会互相覆盖,这种交错情况是不可预期的。
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
ii.内存一致性错误这种错误指的是不同的线程看到的同一数据的视图不同。假设定义和初始化一个int字段:int counter = 0;counter在线程A和B之间共享。假设A递增counter:counter++; 然后B输出counter:System.out.print(counter);这时候的输出值可能是0,因为不能保证线程A对counter的改动对线程B 是可见的,除非在这两个语句之间建立发生前(happens-before)关系:一个语句写的内存对另一个语句可见。
2)实现同步的机制
同步是建立在一种被称为内在锁(inner lock)的内部实体上。内在锁在同步的两个方面起作用:强制对对象的独占访问;建立发生前关系。每个对象都有一个内在锁,需要对对象进行独占的线程在访问之前必须获得这个对象的内在锁,完成访问后释放内在锁,在获得到释放的时间内被称为拥有内在锁。只要线程拥有内在锁,其他线程就不能获得同一个锁。当线程释放内在锁时,该线程的操作会和该锁的任何后续调用都建立发生前关系,这样就确保该线程对对象状态的改动对所有后续线程都是可见的。
i.同步方法:把synchronized关键字添加到方法的申明中。
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
如果counter是SynchronizedCounter的一个对象。当一个线程执行counter的一个同步方法时,它获得counter的内在锁,调用counter的同步方法的其他线程都会被阻碍,直到第一个线程对counter的操作完成为止。
同步方法的不足之处:它所拥有的锁就是该方法所属的类的对象锁,换句话说,也就是this对象,而且锁的作用域也是整个方法,这可能导致其锁的作用域可能太大,也有可能引起死锁(后面会介绍),同时因为可能包含了不需要进行同步的代码块在内,也会降低程序的运行效率。
和同步方法不同,同步语句必须制定内在锁的对象。它的语法:
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
上例中,lock1和lock2对c1和c2这两个字段的更新是同步的。同步块可以更加精确的控制对象锁的作用域,但是如果在使用同步块机制时,如果使用过多的锁也会容易引起死锁问题,同时获取和释放所也有代价。