转载请注明源出处:http://www.cnblogs.com/lighten/p/5967853.html
1.概念
老调重弹,学习线程的时候总会牵扯到进程的概念,会对二者做一个区分。网上有较多的解释,这里引入一个感觉很专业的解释(摘自百度经验):
1)进程是具有独立功能的程序对于某个数据集合的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
2)线程是进程的一个实体,是CPU调度和分派的一个基本单位,它是比进程更小的能独立运行的基本单位。线程基本自己不拥有系统资源,只拥有一点在运行中必不可少的资源(比如程序计数器,一组寄存器和栈),但是它可以与同属同一个进程的其他的线程共享进程所拥有的全部资源。
进程与线程的区别在于以下几个方面:
1)地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程中的线程对于其他进程而言是不可见的。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要同步和互斥的手段辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换快的多。
4)在多线程OS中,进程不是一个可执行的实体。
以上的概念有些繁琐不好理解,当然还是很有必要的,这里我个人想到个例子方便理解:
程序执行无非就是要使用CPU等硬件资源,以单核CPU为例,就好比是一个篮球场。多个进程就好比有多个篮球俱乐部,操作系统就是篮球场管理人员,他来协调哪个俱乐部什么时候使用篮球场。俱乐部之间都有属于自己内部的秘密,与其他俱乐部交流的手段有限,但内部的秘密自己人可以很方便的获得信息。一个俱乐部有多个球队,一队,二队等,这就是多个线程了。一队上场锻炼的时候,二队就歇着呗,一队打累了,打完了或者被二队抢场子了,就二队使用篮球场了。JVM(java虚拟机)俱乐部的领导不干了,一队是主力啊,要更多的训练时间啊,但又不好明着伤二队的心,反正是争抢场子,就掷色子吧,不过1-4的时候一队上,5、6就二队上(线程优先级的概念,可能举的例子不恰当),但是领导说了不算啊,这里是操作系统,篮球管理员的地盘,我的地盘我做主,要看管理员认不认可这个规则了(注意,线程优先级要看具体的操作系统,不是100%有效)。那咋办呢,领导不想各队混乱的抢占资源,就派了一个催眠师,让一些队睡觉(wait或sleep 二者有区别,这里不介绍),需要他们的时候再叫醒他们(notify),叫醒所有被催眠的队伍就(notifyAll),万幸的是这种方法被管理员认可了(笑)。
以上就是一些基本的理解了,对于更详细的细节差异,比如wait和sleep,同步的作用等等,之后介绍到的话会继续使用这个例子介绍。
2.Java创建线程的方法
Java最原始的创建线程的方法只有两种,一种是继承Thread类,另一种是实现Runnable接口。网上可能会说有三种,多出来的一种是通过Excutors创建线程池pool,然后提交一个实现了Callable接口的类给pool,让pool执行,返回一个Future对象。但是实际上这种方式并不能说是一种新的方法,只能说是一种新的思路罢了,因为其内部还是使用的第二种方式,具体代码如下:
// Excutors使用演示 MyCallable是我写的一个实现了Callable接口类
ExecutorService pool = Executors.newFixedThreadPool(1);
Callable c = new MyCallable("name为" + i);
Future f = pool.submit(c); // Future中保存了Callable接口需实现的方法的返回值 // 这里具体看看pool.submit(c)中做了什么
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
} // 上面可以看出Callable的涵义就是一个执行的任务
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
} // newTaskFor(Callable)返回的RunnableFuture接口很明显的看出是使用的Runnable方法
// newTaskFor方法实际上是创建了一个FutureTask对象,其实现了RunnableFuture接口,下面是其run方法的实现
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
} //execute(ftask)的实现
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
所以说JAVA创建一个线程最原始的方法就是那两个,其它的都是扩展而来的。实际上执行还是Thread类,接口又不能执行,多出一个Runnable接口也就是因为Java的语法限制,单继承和多实现(针对于类,接口可以多继承)。如果都继承Thread类,那就意味着这个类不能继承其它的类了,这显然是不好的。
1)继承Thread类,覆写run方法。
public class A extends Thread{ @Override
public void run() {
System.out.println("线程启动了");
} public static void main(String[] args) {
new A().start();
}
}
2)实现Runnable方法,实现run方法。
public class B implements Runnable { @Override
public void run() {
System.out.println("runnable is start");
} public static void main(String[] args) {
// 这里可以看出还是使用的Thread类来运行的
new Thread(new B()).start();
}
}
3 小结
Java对多线程开发提供了强大的原生类,都在java.util.concurrent下。多线程的开发确实有些难点,比如思考难以全面,潜在问题难以检测,出现的问题难以重现等等。但是还是有方法去处理的,这章主要就是对线程的基础概念和Java线程的创建做一个简答的介绍,万丈高楼平地起,基础扎实了,其它问题处理起来就会得心应手了。