在什么情况下要用到多线程
在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。我也有段时间纠结于这里,无论如何我必须得给你明确一点:在某一个时间点,一个CPU(单)只会运行某一个进程里的单个线程,所以我们经常称之为并发,说道同步机制,其实多线程并未真正实现微观意义上的同步,进程是一个运行单元,线程则是更小的运行单元,简而言之,就是进程细分成多个线程,譬如:一个进程A运行需要1s,它就会切换到进程B,但是实现多线程机制后,进程A细化成10个线程,每个线程只需运行0.1s,当然B线程也一样,这就出现,线程之间的切换时间更短,从宏观上看就出现同步幻象了。所以学习多线程你得真正理解所谓的同步并发,并不是真正的“同步”。当你理解这些的时候,你就初略的感觉什么时候该使用多线程机制,其实你的电脑每个程序都至少有一个主线程,那个管理器中的每一个进程,其实内部包含若干线程,每个时间点都是某个程序进程中的某个线程在运行。
什么是java多线程详解
线程对象是可以产生线程的对象。比如在Java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线程的优势有:
(1)进程之间不能共享数据,线程可以;
(2)系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小;
(3)Java语言内置了多线程功能支持,简化了java多线程编程。
一、创建线程和启动
(1)继承Thread类创建线程类
通过继承Thread类创建线程类的具体步骤和具体代码如下:
• 定义一个继承Thread类的子类,并重写该类的run()方法;
• 创建Thread子类的实例,即创建了线程对象;
• 调用该线程对象的start()方法启动线程。
复制代码
class SomeThead extends Thraad {
public void run() {
//do something here
}
}
public static void main(String args){
SomeThread oneThread = new SomeThread();
步骤3:启动线程:
oneThread.start();
}
复制代码
(2)实现Runnable接口创建线程类
通过实现Runnable接口创建线程类的具体步骤和具体代码如下:
• 定义Runnable接口的实现类,并重写该接口的run()方法;
• 创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。
复制代码
class SomeRunnable implements Runnable {
public void run() {
//do something here
}
}
Runnable oneRunnable = new SomeRunnable();
Thread oneThread = new Thread(oneRunnable);
oneThread.start();
复制代码
(3)通过Callable和Future创建线程
通过Callable和Future创建线程的具体步骤和具体代码如下:
• 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
• 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
• 使用FutureTask对象作为Thread对象的target创建并启动新线程。
• 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值其中,Callable接口(也只有一个方法)定义如下:
复制代码
public interface Callable {
V call() throws Exception;
}
步骤1:创建实现Callable接口的类SomeCallable(略);
步骤2:创建一个类对象:
Callable oneCallable = new SomeCallable();
步骤3:由Callable创建一个FutureTask对象:
FutureTask oneTask = new FutureTask(oneCallable);
注释: FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了 Future和Runnable接口。
步骤4:由FutureTask创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
步骤5:启动线程:
oneThread.start();
如何用Java编写多线程
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。
对于直接继承Thread的类来说,代码大致框架是:
?
123456789101112 class 类名 extends Thread{ 方法1; 方法2; … public void run(){ // other code… } 属性1; 属性2; … }
先看一个简单的例子:
?
12345678910111213141516171819202122232425262728 /** * @author Rollen-Holt 继承Thread类,直接调用run方法 * */class hello extends Thread { public hello() { } public hello(String name) { this.name = name; } public void run() { for (int i = 0; i 《 5; i++) { System.out.println(name + “运行 “ + i); } } public static void main(String args) { hello h1=new hello(“A“); hello h2=new hello(“B“); h1.run(); h2.run(); } private String name; }
【运行结果】:
A运行 0
A运行 1
A运行 2
A运行 3
A运行 4
B运行 0
B运行 1
B运行 2
B运行 3
B运行 4
我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。
当我们把上面的主函数修改为如下所示的时候:
?
123456 public static void main(String args) { hello h1=new hello(“A“); hello h2=new hello(“B“); h1.start(); h2.start(); }
然后运行程序,输出的可能的结果如下:
A运行 0
B运行 0
B运行 1
B运行 2
B运行 3
B运行 4
A运行 1
A运行 2
A运行 3
A运行 4
因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的,呵呵。
注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。
那么:为什么我们不能直接调用run()方法呢?
我的理解是:线程的运行需要本地操作系统的支持。
如果你查看start的源代码的时候,会发现:
?
1234567891011121314151617 public synchronized void start() { /** * This method is not invoked for the main method thread or “system“ * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state “NEW“. */ if (threadStatus != 0 || this != me) throw new IllegalThreadStateException(); group.add(this); start0(); if (stopBeforeStart) { stop0(throwableFromStop); } } private native void start0();
注意我用红色加粗的那一条语句,说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。
但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。
通过实现Runnable接口:
大致框架是:
?
123456789101112 class 类名 implements Runnable{ 方法1; 方法2; … public void run(){ // other code… } 属性1; 属性2; … }
来先看一个小例子吧:
?
123456789101112131415161718192021222324252627282930 /** * @author Rollen-Holt 实现Runnable接口 * */class hello implements Runnable { public hello() { } public hello(String name) { this.name = name; } public void run() { for (int i = 0; i 《 5; i++) { System.out.println(name + “运行 “ + i); } } public static void main(String args) { hello h1=new hello(“线程A“); Thread demo= new Thread(h1); hello h2=new hello(“线程B“); Thread demo1=new Thread(h2); demo.start(); demo1.start(); } private String name; }
【可能的运行结果】:
线程A运行 0
线程B运行 0
线程B运行 1
线程B运行 2
线程B运行 3
线程B运行 4
线程A运行 1
线程A运行 2
线程A运行 3
线程A运行 4
关于选择继承Thread还是实现Runnable接口?
其实Thread也是实现Runnable接口的:
?
12345678 class Thread implements Runnable { //… public void run() { if (target != null) { target.run(); } } }
其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。关于代理模式,我曾经写过一个小例子呵呵,大家有兴趣的话可以看一下:
?
-多线程