线程和进程的概念:
其实在电脑上打开一个应用程序的时候就开启了一个进程,比如果我们在打开播放器在线观看电视剧的时候,此时就开启了一个进程,可以一边播放一边缓存下载,播放是一个线程完成的,缓存是另外一个线程完成的,那么我们可以认为进程是线程的载体,一个进程由多个线程组成,线程是进程最小单位。
在Java中我们第一次输出Hello world的使用在一个类中定义主方法实现的,这个主方法的运行就是启动一个线程,这个线程叫做主线程,这个主线程的入口就是方法名“main(String[] args)”,如果方法名称不是main()则无法启动这个主线程。
可以在主线程中继续启动新的线程,这样的线程相对于主线程来说他们叫做子线程。在Java中有三个种方式可以实现线程的创建。
继承Thread类:只要继承了这个类的子类都可以创建线程(不常用,因为有单继承局限,而且不方便实现数据共享)
实现Runnable接口:实现了Runnable接口的子类也可以创建线程(最常用的方法,因为没有单继承局限还可以方便实现数据的共享)
实现Callable接口:这个接口是在jdk1.5版本增加(基本上不使用)
Thread类实现线程的创建:
继承了Thread类的子类就是一个线程主体类。我们知道主线程入口是main()方法,那么其他也一样需要有自己的入口方法才能启动,其他线程的入口方法是start()和run()。
创建一个线程主体类:
package com.sun.thread;
public class MyThread extends Thread {
private String threadName;
public MyThread() {
}
public MyThread(String threadName) {
this.threadName=threadName;
}
@Override
public void run() {
System.out.println(this.threadName+"启动了");
}
}
以上就定义了一个线程主体类,之后需要使用该类创建线程对象启动。
创建线程对象:
package com.sun.thread;
public class ThreadTest {
public static void main(String[] args) {
//创建线程对象
Thread th1=new MyThread("线程A");
Thread th2=new MyThread("线程B");
Thread th3=new MyThread("线程C");
Thread th4=new MyThread("线程D");
Thread th5=new MyThread("线程E");
}
}
上面在主方法中创建了线程对象,那么一个线程对象需要启动才有意义,如何启动一个线程,我们暂时使用run()方法启动
使用run()方法作为入口启动线程:
public class Test {
public static void main(String[] args){
//创建线程对象 并且启动线程
Thread thread1=new MyThread("线程A");
Thread thread2=new MyThread("线程B");
Thread thread3=new MyThread("线程C");
Thread thread4=new MyThread("线程D");
//调用线程对象的run()方法启动
thread1.run();
thread2.run();
thread3.run();
thread4.run();
}
}
发现了以上启动线程的顺序就是代码的顺序,这样不符合多线程的并发性,随机性,正常来说线程的启动是随机的,不会有固定顺序的,而是每次都不一样,出现DEMO中的根本原因没有启动线程,使用run()仅仅是调用了Java类中的一个普通的方法,所以线程没有启动。如果要真正的启动线程需要使用start()方法
使用start()方法启动线程:
public class Test {
public static void main(String[] args){
//创建线程对象 并且启动线程
Thread threadA=new MyThread("线程A");
Thread threadB=new MyThread("线程B");
Thread threadC=new MyThread("线程C");
Thread threadD=new MyThread("线程D");
//调用线程对象的run()方法启动
threadA.start();
threadB.start();
threadC.start();
threadD.start();
}
}
当使用start()方法启动线程的时候会进行cpu资源的调度,为每个线程分配cpu资源,或者更确切的说是每个线程抢占cpu资源,谁先抢到资源就先执行谁。那么为什么会分配cpu资源呢?这个资源分配的任务是谁完成的呢?是本地系统的一些C函数完成的。也就是说当使用start方法之后jvm会调用本地系统的c函数。观察原码
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)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
从原码中可以看到start方法调用了start0方法,这个方法的定义是
“private native void start0();”该方法没有方法体,而且使用native修饰的,是一个本地方法,为什么叫做本地方法呢?因为该方法的实现是本地系统的函数去完成的(C语言写的函数)。调用这个方法的作用是进行cpu资源的调度。当分配到资源之后再回去调用run()方法。
总结:
1、线程和进程的关系:进程是线程的载体,线程是进程的组成单位
2、线程的实现方法是有三种(Thread类、Runnable接口、Callable接口)
3、启动线程要使用start()方法才能真正的实现cpu资源的分配
使用Runnable接口实现线程:
在上面使用继承Thread类的方式实现了线程对象的创建以及启动,但是存在单继承的局限,还可以使用实现Runnable接口的方式创建和启动线程,而且避开了单继承的局限性。
使用Runnable接口
package com.sun.thread;
public class RunThread implements Runnable {
private String threadName;
public MyThread() {
}
public MyThread(String threadName) {
this.threadName=threadName;
}
@Override
public void run() {
System.out.println(this.threadName+"启动了");
}
}
此时使用Runnable接口创建线程主体类,但是如何使用这个类去创建线程对象呢?
创建线程对象
package com.sun.test;
class RunThread implements Runnable{
private String threadName;
public RunThread() {
}
public RunThread(String threadName) {
this.threadName=threadName;
}
@Override
public void run() {
System.out.println(this.threadName+" 启动了!");
}
}
public class Test {
public static void main(String[] args){
//创建线程对象 并且启动线程
Runnable threadA=new RunThread("线程A");
Runnable threadB=new RunThread("线程B");
Runnable threadC=new RunThread("线程C");
//调用线程对象的run()方法启动
}
}
很遗憾的是Runnable接口中没有start方法启动线程,此时还得借助于Thread类辅助完成。Thread提供了一个构造方法可以接收Runnable类型的对象。
public Thread(Runnable target)
之后可以将Runnable对象传递给Thread的构造方法然后辅助启动线程。
借助Thread类的start()启动线程
public class Test {
public static void main(String[] args){
//创建线程对象 并且启动线程
Thread thA=new Thread(new RunThread("线程A"));
Thread thB=new Thread(new RunThread("线程B"));
Thread thC=new Thread(new RunThread("线程C"));
//调用线程对象的run()方法启动
thA.start();
thB.start();
thC.start();
}
}
面试题:
start()方法和run()方法的区别?
run(): 只是Java类的一个普通方法,不能实现cpu资源的调度,不能真正的启动一个线程
start(): 会启动一个线程,会在该方法中调用一个叫做start0()的方法进行cpu资源的调度,之后再去调用run()方法。