一、线程的基本概念
线程是一个程序内部的顺序控制流
线程和进程的区别:
- 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。
- 线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的空间小。
- 多进程:在操作系统中能同时运行多个任务(程序)。
- 多线程:在同一应用程序中有多个顺序流同时执行。
进程是一个静态的概念,平时说的进程的执行指的是进程中main方法开始执行了。在机器中实际上运行的都是线程。
java中的线程:
- java的线程是通过java.lang.Thread类来实现的
- jvm启动时会有一个由public static void main(){ } 所定义的线程。
- 可以通过创建Thread的实例来创建新的线程。
- 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。
- 通过调用Thread类的 start()方法来启动一个线程。
二、线程的创建和启动
1、第一种: 定义线程类实现Runnable接口
-
Thread myThread = new Thread(Target)// Target 为Runnable 接口类型。
(在这也有多态的存在,父类的引用指向子类的对象。) -
Runnable 中只有一个方法
- public void run();用以定义线程运行体。 -
使用Runnable 接口可以为多个线程提供共享的数据
-
在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法。
-public static Thraed currentThread()获取当前线程的引用。
2、第二种:
可以定义一个Thread的子类并重写其run()方法。
class myThread extends Thread{
public void run {
}
}
- 然后生成该类的对象
- myThread mythread = new myThread (…)
Thread类本身实现了Runnable接口。
代码示例:
public class TestThread1 {
public static void main(String args[]) {
Runner1 r = new Runner1();
//在此main方法产生一个分支执行,执行Runner1 中的run方法。而main方法继续执行
Thread r1 = new Thread(r);
Thread r2 = new Thread(r);
//r1.run(); 这相当于方法调用,调用Runner1中的run方法。
//线程启动,线程启动必须调用Thread类的的start方法。
r1.start();
//用第二种方法时不用创建Thread对象,因为本身就是Thread的子类了,所以直接调用start方法。
//r2.start();
for(int i=9;i>=0;i--) {
System.out.println("Main thread"+i+" ");
}
}
}
//方法2、定义Thread类的子类。class Runner1 extends Thread {
//方法1、实现Runnable接口
class Runner1 implements Runnable {
public void run (){
for(int i=0;i<10;i++) {
System.out.println("The Runner1 "+i+" ");
}
}
}
运行结果:
Main thread9
The Runner1 0
Main thread8
The Runner1 1
The Runner1 2
The Runner1 3
Main thread7
Main thread6
The Runner1 4
The Runner1 5
The Runner1 6
The Runner1 7
Main thread5
The Runner1 8
Main thread4
The Runner1 9
Main thread3
Main thread2
Main thread1
Main thread0
因为cpu分配的时间片不一样,所以是交换运行的。
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。
由于继承比较死板,无法再从其他类继承,所以建议使用第一种方法(实现Runnable接口)来创建线程。
实现Runnable接口更好的理由
解耦角度:Runnable接口只定义了一个抽象方法run,语义非常明确,就是线程需要执行的任务。而Thread类除了线程需要执行的任务,还需要维护线程的生命周期、状态转换等
资源角度:继承Thread类的方式,如果想要执行一个任务,必须新建一个线程,执行完成后还要销毁,开销非常大;而实现Runnable接口只需要新建任务,可以做到同一个线程执行多个任务,大大减小了线程创建、销毁的资源浪费
扩展角度:Java不支持多继承,一个类如果继承了Thread类就不能再继承别的类,不利于未来的扩展
三、线程状态转换
在这,没有操作系统里的复杂,简单了解一下即可。
创建之后得启动表示准备好进入cpu了,但由于cpu正在执行任务或拥挤,所以准备好之后还得排队,cpu给时间片可以进入运行状态开始运行,若运行期间时间片到头或没有资源则又会进入就绪状态排队等待…
线程控制的基本方法:
isAlive():判断线程是否终止。
getPriority():获得线程的优先级数值。
setPriority():设置线程的优先级数值。
Thread.sleep():将当前线程睡眠 指定时间毫秒数
join():调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。
yield():让出cpu,使当前线程进入就绪状态等待调度。
wait(): 当前线程进入对象的 wait pool
notify()/ notifyAll():唤醒对象wait pool 中的一个 / 所有 等待的线程。
sleep()、
sleep方法
public static void sleep (long millis,int nanos) throws InterruptedException
注* 静态方法可以由类名直接调用
导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。 线程不会丢失任何显示器的所有权。
参数
millis - 以毫秒为单位的睡眠时间长度
nanos - 0-999999额外的纳秒睡眠
public class TestThread2 {
public static void main(String ages[]) {
Runner2 r = new Runner2();
Thread r1 = new Thread(r);
r1.start();
}
}
class Runner2 implements Runnable {
public void run() {
for(int i=0;i<30;i++) {
if(i%10==0 && i!=0) {
try {
Thread.sleep(10000);//睡眠10秒
System.out.println();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("No.:"+i);
}
}
}
每个10打印10个数。
运行结果(部分):
No.:0
No.:1
No.:2
No.:3
No.:4
No.:5
No.:6
No.:7
No.:8
No.:9
No.:10
No.:11
No.:12
No.:13
No.:14
No.:15
No.:16
No.:17
No.:18
No.:19
线程的优先级别
java提供一个线程调度器来监控程序中启动后将入就绪状态的所有线程。
线程调度器按照线程的优先级决定应该调度哪一个线程来执行。
线程的优先级用数字来表示,范围从1~10,一个线程的缺省优先级为5.
优先级高的得到的cpu执行的时间片就会越多。
举例说明:
public class TestPriority {
public static void main(String[] args) {
Thread t1 = new Thread(new T1());
Thread t2 = new Thread(new T2());
//不设置优先级的话,运行结果t1、t2执行的时间差不多,交换执行
t1.setPriority(Thread.NORM_PRIORITY + 3);//提高t1的优先级,t1的时间片多与t2
t1.start();
t2.start();
}
}
class T1 implements Runnable {
public void run() {
for(int i=0; i<1000; i++) {
System.out.println("T1: " + i);
}
}
}
class T2 implements Runnable {
public void run() {
for(int i=0; i<1000; i++) {
System.out.println("------T2: " + i);
}
}
}
设置优先级之后、部分运行结果截图:
可以看出t1的执行时间多于t2。
四、线程同步
未完待续…