1概念
1.1进程
进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
任务管理器中:
1.2线程
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
1.3多线程
就是一个程序中有多个线程在同时执行。
单线程程序:多个任务只能依次执行。只有一个线程, 例:main方法中调用多个方法,效率较低
多线程程序:多个任务可以同时执行。
1.4程序运行原理
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
1.5主线程
程序从上往下执行的过程:
javac编译----JVM运行Demo01----找到main方法,运行----找到操作系统OS,开启线程
对于CPU就有了一条执行路径,运行方法main的这条路径就叫main,即主线程。
例:
public class Demo01 { //主线程 public static void main(String[] args) { method01(); System.out.println("-9的绝对值是:"+Math.abs(-9)); } public static void method01(){ for(int i=0;i<10;i++){ System.out.println(i); } } }
加一条错误语句:
可以看到异常发生在main线程中
2 Thread类
Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
常用方法:
创建新执行线程有两种方法:
2.1方法1
定义Thread子类,继承Thread,重写run方法
在测试类中创建Thread子类对象
子类对象调用start方法,并且只能调用一次
例:
public class ThreadDemo extends Thread{ public void run() { for(int i=0;i<10;i++){ System.out.println("Thread:"+i); } } }
public class Test01 { public static void main(String[] args) { ThreadDemo td=new ThreadDemo(); td.start(); for(int i=0;i<10;i++){ System.out.println("main:"+i); } } }
start()方法的两个任务:
1)让线程执行
2)让JVM调用线程中的run方法
内存图:
图说明:
Main进栈
看到New Thread,start(),会开线程,会再开一个新的栈,run方法进入新栈
两个栈就是两个线程
每new一个Thread,就多一个栈
注意:要执行start方法,不是run方法,
这就不是多线程了,是正常调用方法。
2.2获取线程名称
例:
public class ThreadDemo extends Thread{ public void run() { System.out.println("线程名为:"+getName()); } }
public class Test02 { public static void main(String[] args) { ThreadDemo td=new ThreadDemo(); td.run(); Thread td2=Thread.currentThread(); System.out.println("主线程名为:"+td2.getName()); } }
注意:主线程的名字只能用Thread.currentThread()这种方式获取,如果这样:
这里因为main方法是static,而getName()不是static,静态不能调用非静态,所以即使Test03继承了Thread也不能直接用getName()。
2.3修改线程名称(了解即可,尽量不要修改,没有必要)
1)setName()方法
public class ThreadDemo extends Thread{ public void run() { System.out.println(getName()); } }
public class Test03 extends Thread{ public static void main(String[] args) { ThreadDemo td=new ThreadDemo(); td.setName("改名1"); td.run(); } }
2)构造方法
public class ThreadDemo extends Thread{ public ThreadDemo(){ } public ThreadDemo(String name){ super(name); } public void run() { System.out.println(getName()); } }
public class Test03 extends Thread{ public static void main(String[] args) { ThreadDemo td=new ThreadDemo("改名2"); td.run(); } }
Tips:主线程无法改名,就叫main
2.4 sleep()方法
public class ThreadDemo extends Thread{ public void run() { for(int i=0;i<5;i++){ try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(i); } } }
public class Test03 extends Thread{ public static void main(String[] args) { ThreadDemo td=new ThreadDemo(); td.run(); } }
3创建线程方式2—实现Runnable接口
3.1步骤
- 创建实现类,实现Runnable接口,重写run方法
- 在测试类中创建实现类对象,创建线程对象
- 将实现类对象传入线程对象的构造方法
- 用线程对象开启线程
例:
public class MyRunnable implements Runnable{ public void run() { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
public class Demo01 { public static void main(String[] args) { //创建线程任务对象 MyRunnable mr=new MyRunnable(); //创建线程对象1 Thread t1=new Thread(mr); //开启线程1 t1.start(); //创建线程对象2 Thread t2=new Thread(mr); //开启线程2 t2.start(); } }
3.2好处
实现Runnable接口,避免了继承Thread类的单继承局限性,较为常用
实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
总结:把线程任务的定义,和线程的创建,分开了
高内聚,低耦合
3.3线程的匿名内部类使用
public class Demo02 { public static void main(String[] args) { Runnable r=new Runnable(){ public void run() { System.out.println("重写后的run方法"); } }; //创建线程 Thread t=new Thread(r); //开启线程 t.start(); } }
简写:
public class Demo2 { public static void main(String[] args) { new Thread(new Runnable(){ public void run() { System.out.println("重写后的run方法"); } }).start(); } }
4线程的状态
在Thread类中有个内部类:
线程状态。线程可以处于下列状态之一:
NEW
至今尚未启动的线程处于这种状态。
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED
已退出的线程处于这种状态。
图示:
5线程池
5.1概念
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
5.2使用线程池方式--Runnable接口
通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
1)Executors:线程池创建工厂类
public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象
2)ExecutorService:线程池类
Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
3)Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
创建线程池对象
创建Runnable接口子类对象
提交Runnable接口子类对象
关闭线程池
例:
public class MyRunnable implements Runnable{ public void run() { for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo03 { public static void main(String[] args) { //从线程池工厂获取线程池对象 ExecutorService es=Executors.newFixedThreadPool(2); //2条线程的线程池 //线程池中抽取一个有空的线程执行线程任务 es.submit(new MyRunnable()); es.submit(new MyRunnable()); //销毁线程池 es.shutdown(); } }
5.3使用线程池方式—Callable接口
1)Callable接口:
与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
2)ExecutorService:线程池类
<T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法
3)Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
创建线程池对象
创建Callable接口子类对象
提交Callable接口子类对象
关闭线程池
例:
public class MyCallable implements Callable<String>{ public String call() throws Exception { return "abc"; } }
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Demo01 { public static void main(String[] args) throws InterruptedException, ExecutionException { //从线程池工厂获取线程池对象 ExecutorService es=Executors.newFixedThreadPool(2); //获取call方法执行后的Future对象 Future<String> str=es.submit(new MyCallable()); //从Future对象中获取返回值 String s=str.get(); System.out.println(s); //销毁线程池 es.shutdown(); } }
5.4练习:从1到100的和,从1到200的和
import java.util.concurrent.Callable; public class CallSum implements Callable<Integer>{ //定义成员变量 private int num; //构造方法 public CallSum(){ }; public CallSum(int num){ this.num=num; }; //线程任务 public Integer call() throws Exception { int sum=0; for(int i=0;i<=num;i++){ sum+=i; } return sum; } }
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 { ExecutorService es=Executors.newFixedThreadPool(2); Future<Integer> f=es.submit(new CallSum(100)); System.out.println(f.get()); Future<Integer> f2=es.submit(new CallSum(200)); System.out.println(f2.get()); es.shutdown(); } }