基础做起,首先围绕Java多线程进行研究,利用博客总结自己学习的内容。
1、线程基本概念
进程:指内存中运行的应用程序,进程在执行过程中拥有独立的内存单元。当我们启动一个应用程序时,就会生成一个新的进程。进程是资源分配的最小单位。
线程:指进程中的一个执行流程,一个进程中可以包含多个线程。线程是CPU调度的最小单位。引进线程的主要目的就是为了提高资源的合理利用率。线程之间切换比进程之间切换节约系统开销。
多进程:操作系统能并行式运行多个应用程序,比如你可以同时打开word软件和Excel软件。
多线程:一个应用程序中存在多个顺序流同时运行,比如QQ软件能够同时视频聊天,打字。
2、多线程的实现方式及区别
(1)继承式(Thread)
继承Thread类,重写类Thread中的run()方法,示例如下:
package wygu.multiThread.study;
/** * @author guweiyu * */
public class MultiThreadExtends extends Thread{
String threadName = "";
public MultiThreadExtends(String threadName){
this.threadName = threadName;
}
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(threadName+":"+i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String []argv){
MultiThreadExtends threadA = new MultiThreadExtends("A");
MultiThreadExtends threadB = new MultiThreadExtends("B");
threadA.start();
threadB.start();
}
}
第一次运行结果如下:
A:0
A:1
B:0
B:1
A:2
B:2
A:3
B:3
A:4
B:4
第二次运行结果如下:
A:0
A:1
A:2
B:0
B:1
B:2
B:3
B:4
A:3
A:4
如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MultiThreadExtends,通过线程对象调用start()方法,使得该线程处于就绪状态,执行run()方法。注意此时线程并不一定能够立刻执行,这取决于CPU的调度。
(2)实现接口式(Runnable)
实现接口Runnable,重写该接口的run()方法,示例代码如下:
package wygu.multiThread.study;
/** * @author guweiyu * */
public class MultiThreadImplem implements Runnable{
String threadName = "";
public MultiThreadImplem(String threadName){
this.threadName = threadName;
}
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(threadName+":"+i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread threadC = new Thread(new MultiThreadImplem("C"));
Thread threadD = new Thread(new MultiThreadImplem("D"));
threadC.start();
threadD.start();
}
}
第一次运行结果:
C:0
D:0
D:1
C:1
C:2
D:2
C:3
D:3
D:4
C:4
第二次运行结果:
C:0
C:1
C:2
C:3
D:0
D:1
C:4
D:2
D:3
D:4
如上所示,实现接口Runnable,通过重写run()方法定义了一个新的Runnable类MultiThreadImplem,线程对象调用start()方法,使得该线程处于可运行的状态。我们发现上述两种方式都能够实现多线程的并发执行,其实实际上类Thread类本身也是实现了Runnable接口,run()方法最先是在Runnable接口中定义的方法。
(3)实现接口式(Callable)
实现接口Callable,重写该接口call()方法
package wygu.multiThread.study;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MultiThreadCallable implements Callable<String>{
private String threadName;
public MultiThreadCallable(String threadName) {
this.threadName = threadName;
}
@Override
public String call() throws Exception {
for(int i=0;i<5;i++){
System.out.println(threadName+":"+i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return threadName;
}
public static void main(String []argv){
MultiThreadCallable mCallableE = new MultiThreadCallable("E");
MultiThreadCallable mCallableF = new MultiThreadCallable("F");
FutureTask<String> futureTaskE = new FutureTask<String>(mCallableE);
FutureTask<String> futureTaskF = new FutureTask<String>(mCallableF);
new Thread(futureTaskE,"带返回值的线程E").start();
new Thread(futureTaskF,"带返回值的线程F").start();
try {
System.out.println("线程E返回值为:"+futureTaskE.get());
System.out.println("线程F返回值为:"+futureTaskF.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第一次运行结果:
E:0
F:0
F:1
F:2
E:1
F:3
E:2
F:4
E:3
E:4
线程E返回值为:E
线程F返回值为:F
第二次运行结果:
F:0
E:0
F:1
E:1
F:2
E:2
F:3
E:3
E:4
F:4
线程E返回值为:E
线程F返回值为:F
如上所述,实现接口Callable,通过重写call()方法定义了一个新的Callable类MultiThreadCallable,相对于Runnable,Callable可以带返回值。关于Callable深层次的研究放在后面的博客中。
(4)实现接口Runnable或Callable和继承Thread的区别
1):适合多个功能相同的线程去处理同一个资源;
2):避免java中的单继承的限制,可以利用接口实现多继承特性;
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
下面举例说明利用接口Runnable实现资源的共享功能:
package wygu.multiThread.study;
public class MultiThreadShare implements Runnable{
private volatile int breakFast=10;
@Override
public void run() {
for(int i=0;breakFast>0;i++){
System.out.println(Thread.currentThread().getName()+"---->"+breakFast--);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MultiThreadShare mThreadShare = new MultiThreadShare();
Thread threadG = new Thread(mThreadShare, "KFC窗口1");
Thread threadH = new Thread(mThreadShare, "KFC窗口2");
Thread threadI = new Thread(mThreadShare, "KFC窗口3");
threadG.start();
threadH.start();
threadI.start();
}
}
运行结果:
KFC窗口1—->10
KFC窗口3—->9
KFC窗口1—->8
KFC窗口3—->7
KFC窗口1—->6
KFC窗口3—->5
KFC窗口2—->4
KFC窗口2—->3
KFC窗口2—->2
KFC窗口1—->1
通过上述实例可以发现利用Runnable实现不同线程之间的资源共享。
3、线程的生命周期及状态间的转换
图片来源:http://www.cnblogs.com/lwbqqyumidi/p/3804883.html
Java线程的生命周期状态大致可以分为以下5中状态:
1)创建状态(New):通过New关键字创建了Thread类(或其子类)的实例。
2)就绪状态(Runnable):线程除了CPU资源外,其它资源都已准备充分,正等待CPU的调度
Thread类的实例调用start()方法,等待时间片轮转(CPU资源调度),以便获得CPU资源;
线程在处于Running状态时,但是时间片使用完后,回到Runnable状态;
处于Blocked状态的线程结束了当前的Blocked状态之后重新回到Runnable状态。
3)运行状态(Running):线程获得CPU的资源,正在执行run(),Running状态是所有线程 都希望获得的状态。注意,就绪状态是进入到运行状态的唯一入口。
4)阻塞状态(Blocked):处于Running状态线程,由于某种原因,比如运行时执行到wait(),获取synchronized同步锁失败,发出I/O请求。
5)死亡状态(Dead):处于Running状态的线程执行完run()方法,或者出现异常,线程就处在Dead状态,即该线程的生命周期执行结束。
Thread类的实例调用start()方法,等待时间片轮转(CPU资源调度),以便获得CPU资源;
4、线程的常见几种方法比较
Thread.sleep(long millis):
正在运行的线程调用该方法,该线程进入阻塞,释放CPU资源,但不释放对象锁,millis后线程自动苏醒进入Runnable状态。
Thread.yield():
正在运行的线程调用该方法,该线程释放CPU资源,重新由Running状态转变为Runnable状。其主要作用实现让相同优先级的线程轮流执行,但并不保证一定会轮流执行。但在实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
wait():
wait()方法是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
thread.join()/thread.join(long millis):
join()方法实现是通过wait()方法进行线程控制的。 当main线程调用thread.join时候,main线程会获得线程实例thread的锁,调用该对象的wait(long millis),直到该对象唤醒main线程,比如退出后,线程实例thread退出后,会在native方法中调用线程对象的notifyAll()方法,然后执行main线程的后续部分代码。
package wygu.multiThread.study;
public class MultiThreadJoin implements Runnable{
@Override
public void run() {
System.out.println("开始执行线程--->"+Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("结束执行线程--->"+Thread.currentThread().getName());
}
public static void main(String[] args) {
System.out.println("主线程开始执行");
MultiThreadJoin mThreadJoin = new MultiThreadJoin();
Thread thread = new Thread(mThreadJoin, "子线程");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程结束执行");
}
}
运行结果:
主线程开始执行
开始执行线程—>子线程
结束执行线程—>子线程
主线程结束执行
可以看出主线程是在子线程执行结束后,才真正的结束。
notify():
notify()是Object类里的方法,唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
5、线程的中常见的名词
在Java中有两类线程:用户线程(User Thread),守护线程(Daemon Thread)。在JVM中,只要还存在一个运行的用户线程,所有的守护线程均没有结束;只有当所有的用户线程全部结束,守护线程会随JVM的退出而消亡。换句话说,JVM中任何一个守护线程都是所有用户线程的保姆。比如,JVM中负责垃圾回收的线程(GC 垃圾回收器)就是一个守护线程。
在JAVA中,将一个线程设置成守护线程非常简单,只需要:创建一个线程实例,然后设置thread.setDaemon(true)即可将该线程设置为守护线程。
以下几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则程序会抛出IllegalThreadStateException异常。因为不能把一个正在运行的线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。
package com.wygu.thread.study;
public class MultiThreadDaemon implements Runnable{
@Override
public void run() {
System.out.println("线程开始运行:"+Thread.currentThread().getName());
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束运行:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
System.out.println("线程开始运行:"+Thread.currentThread().getName());
MultiThreadDaemon mThreadDaemon = new MultiThreadDaemon();
Thread thread = new Thread(mThreadDaemon,"Dameon Thread");
thread.setDaemon(true);
thread.start();
System.out.println("线程结束运行:"+Thread.currentThread().getName());
}
}
程序运行结果:
线程开始运行:main
线程结束运行:main
线程开始运行:Dameon Thread
我们发现线程thread还没有运行结束就随着主线程的结束而结束了,如果把thread.setDaemon(true);注释掉,会发现程序运行结果为:
线程开始运行:main
线程结束运行:main
线程开始运行:Dameon Thread
线程结束运行:Dameon Thread
守护线程除了被用于垃圾回收等方面,还可以被用在网络socket通信中,维持长连接或者收发信息,比如举个简单的例子:
package com.wygu.thread.study;
public class MultiThreadDaemon implements Runnable{
@Override
public void run() {
int times = 0;
while(true){
System.out.println("发送消息:"+times);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("接受消息:"+times);
times++;
}
}
public static void main(String[] args) {
System.out.println("线程开始运行:"+Thread.currentThread().getName());
MultiThreadDaemon mThreadDaemon = new MultiThreadDaemon();
Thread thread = new Thread(mThreadDaemon,"Dameon Thread");
thread.setDaemon(true);
thread.start();
while(true) {
try {
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
System.out.println("Main thread sleep error!");
System.exit(0);
}
}
}
}
程序运行结果:
线程开始运行:main
发送消息:0
接受消息:0
发送消息:1
接受消息:1
发送消息:2
接受消息:2
发送消息:3
接受消息:3