一、什么叫做线程与进程
进程:进程是操作系统进行资源分配的最小单位。例如:IO资源、内存资源
线程:线程是操作系统进行资源调度的单位。
线程存在于进程中,一个进程可以对应多个线程;进程与线程之间相互隔离;一个进程的崩溃不会影响其他进程的崩溃,但是一个线程的崩溃可能引起其他线程的崩溃,因此,进程的健壮性要比线程好
内存资源占比:创建进程意味着开辟内存空间、同一个进程下的线程共享部分空间,开辟一个进程相比线程资源占用更多
二、创建线程的四种方式
(1)通过创建Runnable接口创建线程
实现步骤:
step1:实现Runnable接口,并且实现Runnable接口中的run方法
step2:实例化Runnable接口的类
step3:创建Thread对象,并且将Runnable实例作为参数来实例化Thread对象
step4:调用Thread对象的start方法,意味着子线程可以使用
实例演示:
public class ImplementRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现run接口");
}
}
public class CreadThread {
public static void main(String[] args) {
ImplementRunnable runable=new ImplementRunnable();
Thread thread = new Thread(runable);
thread.start();}
}
(2)通过继承Thread类来创建线程
实现步骤:
step1:创建类并继承Thread类,重写run方法
step2:实例化该类的对象
step3:对象调用start方法,启用子线程
实例演示:
public class ExtThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("继承Thread");
}
}
public class CreadThread {
public static void main(String[] args) {
ExtThread extThread = new ExtThread();
extThread.start();}
}
(3)通过实现Callable接口创建多线程
实现步骤:
step1:实现Callable接口,并且实现call方法
step2:调用Executors工具类实现线程池(说明Callable是Executors下的工具类)
step3:将实例化的Callable对象提交到线程池(即使用submit方法)
实例演示:
public class ImplementCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("实现Callable接口");
return 666;
}
}public class CreadThread {
public static void main(String[] args) {
ImplementCallable implementCallable = new ImplementCallable();
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> submit = executorService.submit(implementCallable);
try {
Integer integer = submit.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}}
(4)通过匿名内部类创建
实例演示:
public class CreadThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类创建线程");
}
}).start();
}}
Runable接口和Thread类的区别:
1)线程类继承自Thread则不能继承其他类;而Runable接口可以
2)线程类继承自Thread相对于Runable来说,使用线程的方法更方便一些
3)实现Runnable接口的线程类的多个线程、可以更方便的访问同一个变量,而Thread类需要内部类来进行替换
Callable 和 Runnable接口的区别
1)Callable中的方法是call(),而Runnable中的方法是run();
2)Callable的任务执行后可返回值,而Runnable的任务是没有返回值的;
3)call()方法可抛出异常,而run()方法不能抛出异常;
三、线程中常用的方法
1)start()
启动一个新线程。start需要首先调用,且start不能被重复调用;如果在start之前调用了某一个方法,只是普通方法的调用,和多线程没有关系。
2)run()
和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程。
3)yield()
定义在Thread.java中,是Thread的静态方法。作用是让步,它能让当前正在执行的线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行。
4)sleep()
定义在Thread.java中,是Thread的静态方法;sleep方法的作用是让线程休眠指定时间,在时间到达时自动恢复线程的执行;sleep方法不会释放线程锁;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
5)join()
定义在Thread.java中。join() 的作用是让“主线程”等待“子线程”结束之后才能继续运行。
使用方法:在Father线程中调用:sonThread.join();则father线程会阻塞到son线程完成。join的原理是用wait做的,因此其行为类似wait。
6)interrupt()
它的作用是中断当前线程。
7)isDaemon() setDaemon()
Java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。
需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。
8)setPriority() getPriority()
线程优先级、来指导JVM层面优先来执行那个成程序,但最终的执行顺序需要操作系统来指定。
java 中的线程优先级的范围是1~10,最小值是1、默认的优先级是5、最大值是10。“高优先级线程”会优先于“低优先级线程”执行。
四、线程状态及状态转换
线程总共有5种状态,分别是:
1)新建状态(New)
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
2)就绪状态(Runnable)
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
3)运行状态(Running)
这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
4)阻塞状态(Blocked)
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态可以分为以下3种:
1、位于对象等待池中的阻塞状态
当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
2、位于对象锁池中的阻塞状态
当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
3、其他阻塞状态:
当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
5)死亡状态(Dead)
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。
五、线程调度与线程调度算法
线程调度
1)调整现场优先级:Java线程有优先级,优先级高的线程获得较多的运行机会(运行时间);
static int Max_priority 线程可以具有的最高优先级,值为10;
static int MIN_PRIORIYT 线程可以具有的最低优先级,值为1;
static int NORM_PRIORITY 分配给线程的默认优先级,值为5;
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级;
2)线程睡眠:Thread.sleep(long millins)使线程转到阻塞状态;
3)线程等待:Object.wait()方法,释放线程锁,使线程进入等待状态,直到被其他线程唤醒(notify()唤醒的是其中一个线程和notifyAll()唤醒的是所以的线程);
4)线程让步:Thread.yeild()方法,暂停当前正在执行的线程,使其进入等待执行状态, 把执行机会让给相同优先级或更高优先级的线程,如果没有较高优先级或相同优先级的线程, 该线程会继续执行;
5)线程加入:join()方法,在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,知道另一个进程运行结束,当前线程再有阻塞状态转为就绪状态;
线程调度算法
1)实时系统:
FIFO(First Input First Output,先进先出算法)
SJF(Shortest Job First,最短作业优先算法)
SRTF(Shortest Remaining Time First,最短剩余时间优先算法)。
2)互式系统:
RR(Round Robin,时间片轮转算法)
HPF(Highest Priority First,最高优先级算法)。
六、实际解决问题
1、main方法运行属于进程还是线程?
main方法属于进程,会创建JVM实例,分配内存空间;也是一个线程,是一个主线程。
2、有三个线程T1,T2,T3,怎样确保他们按顺序执行?
有多个线程中有多种方法让线程按特定顺序执行,可以用线程类的join()方法,在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序应该先启动最后一个(即T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。