非常详细的JAVA并发基本线程机制
编程问题中相当大的一部分都可以通过使用顺序编程来解决。然而对于某些问题,若果能够并发地执行程序中的多个部分,则会变得非常方便甚至非常必要,因为这些部分要么看起来在并发执行,要么在多处理器环境下可以同时执行。
JAVA是一种多线程语言,并提出了并发问题。实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包容程序。多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程。每个任务都作为进程在其自己的地址空间中执行,因此它们是完全独立的;更重要的事,对进程来说,它们之间没有任何彼此通信的需要,因为它们是完全独立的。
JAVA在并发上采取了比较传统的方式,在顺序语言的基础上提供对线程的支持。线程机制是在由执行程序表示的单一进程中创建任务。
在但CPU机器上使用多任务的程序在任意时刻仍旧值在执行一项工作,因此从理论上讲,肯定可以不用任何很污二编写出相同的程序。但是毛病发提供了一个重要的组织结构上的好处:你的程序设计可以极大地简化。而例如仿真,没有并发数的支持是很难解决的。
JAVA的线程机制是抢占式的,这表示调度机制会周期性地中断线程将上下文切换到另一个线程,从而为每个线程都提供时间片,是的每个线程都会分配到数量合理的时间去驱动它的任务。
并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(子任务)中的每一个都将有执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有其自己的CPU一样,其底层机制是切分CPU时间(有需要了解这部分知识的朋友可以在下方留言,可以出一篇详细的博客)。
CPU轮流给每个任务分配其占用时。每个任务都觉得自己再一直在占用CPU,大三事实上CPU时间是划分成片段分配给了所有的任务(例外是程序确实运行在多个CPU之上)。线程的一大好处是可以使你从这个层次抽身出来,即代码不必知道它是运行在具有一个还是多个CPU的机器上。所以,使用线程机制是一种建立透明的、可扩展的程序的方法。
JAVA多线程机制的代码介绍篇章(实用篇)
1. 定义任务
package Kim_1;
/*定义任务,需要一种描述任务的方式,可由Runnable接口提供。
* 想要定义任务,只需要实现Runnable接口并编写run()方法,使得该任务可以执行你的命令
* */
public class LiftOff implements Runnable{
protected int countDown = 10;// Default
private static int taskcount = 0;// 记任务数
private final int id = taskcount++;
public LiftOff(){}
public LiftOff(int countDown) {
this.countDown = countDown;
}
//
public String status() {
return "#"+id+"("+(countDown > 0 ? countDown:"LiftOff!")+").";
}
public void run() {
while(countDown-->0) {
System.out.println(status());
// Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程;
Thread.yield();
}
}
}
MAIN类:
package Kim_1;
public class MainThread {
public static void main(String[] args) {
LiftOff launch = new LiftOff();
launch.run();
}
}
猜猜运行结果是什么勒?
分析对了吗?没对没关系,跟我继续往下,你就能渐渐理解了。
从结果我们可以得知,整个过程我们只有一个过程产生了ListOff对象,即目前只有0号一个LiftOff任务被执行;后面括号中的倒数进行发射为描述的任务。
不过就纳闷了说好的多线程呢?
的确,如果这里我们需要实现多线程的话,还少了一些步骤;因为虽然我们实现了Runnable接口,并重写了run()函数,但是这个方法并无特殊之处——它不会产生任何内在的线程能力。要实现线程行为,你必须显示地讲一个任务附着到线程上。
通过上面的程序我们可以再来理一遍上面的程序-任务-进程图
(理解这个模式,之后就能很简单的区分继承自Thread和实现Runnable接口的区别了)
2. Thread类
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器。
下面的例子展示了如何使用Thread来驱动LiftOff对象:
package Kim_1;
public class BasicThreads {
public static void main(String[] args) {
Thread t = new Thread(new LiftOff());
t.start();
System.out.println("Waiting for LiftOff");
}
}
运行结果如下:
Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必要的初始化操作,然后调用Runnble的run()方法,以便在这个新线程中启动该任务。初学者看到这样的结果可能会觉得很诧异,比较平时习惯了顺序执行的方式;但这也就是多线程的魅力所在,实际上t.start()产生的是对LiftOff.run()的方法调用,并且这个方法还没有完成,但是因为LiftOff.run()是由不同的线程执行的,因此你人就可以执行main()线程中的其他操作。
到这里不可避免还是一头雾水,我们暂时先了解有这么个东西,能运行出这样奇怪的结果,具体细节咱们后面慢慢道来。
有了上面的基础,你可以像方一样很容易地添加更多的线程去驱动更多的任务,结果不方便截图,各位小伙伴可以在自己的PC上运行一下试试,可以显然的看到运行的结果杂乱无章,并且每次运行的结果几乎都不一样,这说明不同任务的执行在线程中被换进换出时混在了一起,这种交换是由线程条肚脐自动控制的,且线程调度机制是非确定性的。
(注意:本例子中,单一线程(main()在创建所以的LiftOff线程,所以id为0-4。但是如果多个线程在创建LiftOff线程,那么就要可能会有多个LiftOff拥有相同的id))
package Kim_1;
public class MoreBasicThreads {
public static void main(String[] args) {
for(int i=0;i<5;i++)
new Thread(new LiftOff()).start();
System.out.println("Waiting for LiftOff");
}
}
当main()创建Thread对象时,它并可以捕获任何对这些对象的引用。而每个Thread都“注册”了它自己,因此有一个对它的引用,而且在它的任务结束退出其run()并死亡之前,垃圾回收期无法清除它。
3. 使用Executor(执行器)
Executor可以为我们管理Thread对象,从而简化并发编程;它在客户端和任务之间提供了一个间接层;它允许我们管理一部任务的执行,而无需显示地管理线程的声明周期。
我们可以使用Excutor来代替MoreBasicThread.java中显示地创建Thread对象。LiftOff对象知道如何运行具体的任务。ExecutorService知道如何构建恰当的上下文来执行Runnable对象。
package Kim_1;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();// ExecutorService是具有服务生命周期的Executor
for (int i = 0; i < 5; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();// 可以防止新任务被提交给这个Executor
}
}
这一次我们可以发现运行的结果齐整多了。
4. 从任务中产生返回值
package Kim_1;
/*从人物中产生返回值,
* 通过实现Callable接口,Callable是一种具有类型参数的泛型,它的类型参数表示从方法call()中返回的值的类型
* 并且必须使用ExcecutorService.submit()方法调用它*/
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id) {
this.id = id;
}
public String call() {
return "result of TaskWithResult" + id;
}
}
public class CallableDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> result = new ArrayList<Future<String>>();
for (int i = 0; i < 10; i++) {
result.add(exec.submit(new TaskWithResult(i)));
}
for (Future<String> fs : result) {
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
// TODO: handle exception
System.out.println(e);
return;
}catch (ExecutionException e) {
// TODO: handle exception
System.out.println(e);
}finally {
exec.shutdown();
}
}
}
}
5. 休眠
package Kim_1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SleepingTask extends LiftOff{
public void run() {
try {
while (countDown-->0) {
System.out.print(status());
TimeUnit.MILLISECONDS.sleep(1000);
}
} catch (Exception e) {
// TODO: handle exception
System.err.println("Interrupted");
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new SleepingTask());
}
exec.shutdown();
}
}
6. 优先级
线程的优先级将该线程的重要性传给了调度器。调度器会倾向于让优先级最高的线程限执行;但这并不意味着优先级低的线程得不到执行,优先级不会造成死锁现象。
三个常用的优先级级别常量:MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY
package Kim_1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* */
public class SimplePriorities implements Runnable{
private int countDown = 5;
private volatile double d;
private int priority;
public SimplePriorities(int priority){
this.priority = priority;
}
//toString()方法被覆盖,用来打印线程的名字
//这里为自动生成的名称(你也可以在构造器里自己设置这个名称)
public String toString(){
return Thread.currentThread()+":"+countDown;
}
public void run() {
Thread.currentThread().setPriority(priority);
while(true) {
for (int i = 0; i < 100000; i++) {
d+=(Math.PI + Math.E)/(double)i;
if (i%1000==0) {
Thread.yield();
}
System.out.println(this);
if (--countDown==0) {
return;
}
}
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++)
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
exec.shutdown();
}
}
7.让步
yield()方法。当已经知道run()方法的循环的一次迭代过程中所需要的工作,就可以将线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程来使用CPU了。这个暗示将通过调用yield()方法来做出。当调用yield()时,你也是在建议具有相同优先级瑟其他线程可以运行。
注意:虽然yield()可以对线程的产生分布给予了良好的处理机制,但是对于任何重要的控制或在调整应用时,都不能依赖于yield();
8.后台线程
后台线程是指在程序运行时在后台提供的一种通用服务线程,并且这种线程并不属于程序中不可或缺的部分,即当所有的非后台程序结束时,程序也就终止了,同时会杀死进程中的所以后台线程。比如:执行main()的就是一个非后台线程。
必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。实例中因为main()线程别设定为短暂睡吗,所以可以观察到所以后台线程启动后的结果。
//: concurrency/SimpleDaemons.java
// Daemon threads don’t prevent the program from ending.
package Kim_1;
import java.util.concurrent.TimeUnit;
public class SimpleDaemons implements Runnable {
public void run() {
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.print(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
System.out.print("sleep() interrupted");
}
}
public static void main(String[] args) throws Exception {
for(int i = 0; i < 10; i++) {
Thread daemon = new Thread(new SimpleDaemons());
// 将daemon设为后台线程
daemon.setDaemon(true); // Must call before start()
daemon.start();
}
System.out.print("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
} /* Output: (Sample)
All daemons started
Thread[Thread-0,5,main] SimpleDaemons@530daa
Thread[Thread-1,5,main] SimpleDaemons@a62fc3
Thread[Thread-2,5,main] SimpleDaemons@89ae9e
Thread[Thread-3,5,main] SimpleDaemons@1270b73
Thread[Thread-4,5,main] SimpleDaemons@60aeb0
Thread[Thread-5,5,main] SimpleDaemons@16caf43
Thread[Thread-6,5,main] SimpleDaemons@66848c
Thread[Thread-7,5,main] SimpleDaemons@8813f2
Thread[Thread-8,5,main] SimpleDaemons@1d58aae
Thread[Thread-9,5,main] SimpleDaemons@83cc67
...
*///:~
9.加入一个线程
一个线程可以在其他线程智商调用join()方法,其效果十等待一段时间知道第二个线程结束才能继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(t.isAlive()为false)。join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法(追interrupt需要处理异常,用到try-catch语句块)
从下方运行结果我们可以看出,Sleeper被中断或者是正常结束,Joiner将和Sleeper一同结束。
//: concurrency/Joining.java
// Understanding join().
package Kim_1;
class Sleeper extends Thread {
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch(InterruptedException e) {
System.out.println(getName() + " was interrupted. " +
"isInterrupted(): " + isInterrupted());
return;
}
System.out.println(getName() + " has awakened");
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println(getName() + " join completed");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumpy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
} /* Output:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*///:~