一.线程与进程
多线程中的名词解释:
1.线程:进程中负责程序执行的执行单元,线程本身依靠程序进行运行,线程是程序中的顺序控制流,只能使用分配给程序的资源和环境。
2.进程:执行中的程序一个进程至少包含一个线程。
3.单线程:程序中只存在一个线程,实际上主方法就是一个主线程。
4.多线程:在一个程序中运行多个任务目的是更好地使用CPU资源。
5.并行:多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时。
6.并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时,并发往往在场景中有公用的资源。
7.线程安全:并发的情况下,该代码经过多线程使用,线程的调度顺序不影响任何结果。反过来,线程不安全就意味着线程的调度顺序会影响最终结果。
8.同步:Java中的同步指的通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。
二.线程的实现
1.继承Thread类
在java.lang包中定义,继承Thread类必须重写run()方法;
线程类:
public class MyThread extends Thread{
public static int num =0;
public MyThread(){
num++;
}
/**
* run()方法用来执行线程定义的任务
*/
@Override
public void run (){
System.out.println("主动创建第"+num);
}
}
线程调用:
public class Test{
public static void main(String[] args) {
MyThread thread =new MyThread();
//通过start()方法去调用线程
thread.start();
}
}
创建好了自己的线程类之后,就可以创建线程对象,然后通过start()方法区启动线程。不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务,等于在main线程中调用run()方法。
区分start()和run()方法:
public class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name=name;
}
/**
* run()方法用来执行线程定义的任务
*/
@Override
public void run (){
//查看线程ID的方法
System.out.println("子线程ID:"+name+"-"+Thread.currentThread().getId());
}
}
public class Test{
public static void main(String[] args) {
System.out.println("主线程的ID:"+Thread.currentThread().getId());
MyThread thread1 =new MyThread("Thread1");
thread1.start();
MyThread thread2 =new MyThread("Thread2");
thread2.run();
}
}
结论:
(1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别。
(2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。
2.实现Runable接口
public class MyThread implements Runnable{
private String name;
public MyThread(String name){
this.name=name;
}
/**
* run()方法用来执行线程定义的任务
*/
@Override
public void run (){
//查看线程ID的方法
System.out.println("子线程ID:"+name+"-"+Thread.currentThread().getId());
}
}
public class Test{
public static void main(String[] args) {
System.out.println("主线程的ID:"+Thread.currentThread().getId());
MyThread thread1 =new MyThread("Thread1");
Thread thread =new Thread(thread1);
thread.start();
MyThread thread2 =new MyThread("Thread2");
Thread thread3 =new Thread(thread2);
thread3.run();
}
}
三.使用ExecutorService、Callable、Future实现有返回结果的多线程
有返回值的任务必须实现Callable接口,无返回值的任务必须Runable接口。执行Callable任务后,可以获取一个Future对象,在该对象上调用get就可以获取Callable任务返回的Object,再结合线程池接口ExecutorService就可以实现由返回结果的多线程。
import java.util.Date;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Object>{
private String taskNum;
public MyCallable(String taskNum){
this.taskNum=taskNum;
}
@Override
public Object call() throws Exception {
System.out.println(">>>"+taskNum+"任务启动");
Date datetemple1 =new Date();
Thread.sleep(2000);
Date datetemple2 =new Date();
long time =datetemple2.getTime()-datetemple1.getTime();
System.out.println(">>>"+taskNum+"任务终止");
return taskNum+"任务返回运行结果,当前任务时间"+time+"毫秒";
}
}
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test{
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("程序开始运行");
Date date1 = new Date();
int taskNum = 5;
//创建一个数组为5的线程池
ExecutorService pool = Executors.newFixedThreadPool(taskNum);
//创建多个有返回值的任务
List<Future>list =new ArrayList<Future>();
for(int i=0;i<taskNum;i++){
Callable c =new MyCallable(i+"");
Future f =pool.submit(c);
list.add(f);
}
pool.shutdown();
for(Future f:list){
System.out.println(">>>"+f.get().toString());
}
Date date2 = new Date();
System.out.println("---程序结束运行---"+(date2.getTime()-date1.getTime()));
}
}
四.线程的状态
1.创建(new)状态:准备好一个多线程的对象
2.就绪(runable)状态:调用start()方法,等待CPU进行调度
3.运行(running)状态:执行run()方法
4.阻塞(blocked)状态:暂时停止执行,可能将资源交给其它线程使用
5.终止(dead)状态:线程销毁
(1)New一个线程对象start()方法——通过JVM分配内存到达Runable状态——通过获取CPU资源到达Running状态——执行run方法——线程销毁(单线程执行步骤)
(2)yield()方法:当前运行线程释放处理器资源,从running状态回到Runable状态。
(3)join()方法:主要作用于同步,使得线程之间的并行执行变为串行执行。join的意思是放弃当前线程的执行,并返回对应的线程。在A线程中调用了B线程的join()方法,表示只有当B线程执行完毕时,A线程才能继续执行。
(4)sleep()方法:
sleep和wait的区别:
*sleep是Thread类的方法,wait是Object类中定义的方法
*Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁
*Thread.sleep和Object.wait都会暂停当前的线程,OS会将执行时间分配给其他线程,区别是,调用wait后,需要别的线程执行notify/notifyAll才能重新获取CPU执行时间
上下文切换:
对于单核CPU来说,CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另一个线程,这个叫做线程上下文切换。
静态方法:currentThread()方法可以返回代码段正在被哪个线程调用的信息。
sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠。
sleep相当于让线程睡眠,让CPU去执行其他任务。
yield()方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,yield方法只能让拥有相同优先级的线程获取CPU执行时间的机会。
public class MyThread extends Thread{
/**
* run()方法用来执行线程定义的任务
*/
@Override
public void run(){
long beginTime=System.currentTimeMillis();
System.out.println(beginTime);
int count=0;
for (int i=0;i<50000000;i++){
count=count+(i+1);
Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println(endTime);
System.out.println("用时:"+(endTime-beginTime)+" 毫秒!");
}
}
getId():getId()的作用是取得线程的唯一标识
join()方法,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程早于子线程之前结束。比如子线程处理一个数,主线程要用子线程的数据,通过join()方法,让主线程给子线程让路。join()的作用是等待线程对象销毁。
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
/**
* run()方法用来执行线程定义的任务
*/
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(getName()+" "+i);
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
new MyThread("new Thread").start();
for(int i=0;i<10;i++){
if(i==5){
MyThread th =new MyThread("Join Thread");
th.start();
th.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
getName和setName 用来得到或者设置线程名称。
getPriority和setPriority 用来获取和设置线程优先级
setDaemon和isDaemon 用来设置线程是否成为守护线程和判断线程是否是守护线程
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也随着消亡。守护线程依赖于main线程,main运行完,守护线程也结束。
停止线程
1.使用退出标志,使线程正常退出,当run方法完成后线程终止
2.使用stop方法强行终止线程,不推荐使用
3.使用interrupt方法中断线程,但不会终止一个正在运行的线程,还需要假如一个判断才可以完成线程的停止。
interrupt()方法
线程的优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也是CPU优先执行优先级较高的线程对象中的任务。
线程优先级特性:
1.继承性
A线程启动B线程,则B线程的优先级与A是一样的
2.规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
3.随机性
优先级高的线程不一定每一次都先执行完