进程:每个进程都拥有独立的内存空间,进程切换有较大的开销,一个进程包含1-n个线程(是资源分配的最小单位) 线程:同一进程的线程共享内存空间,每个线程有独立的运行栈和程序计数器,线程切换开销小(是CPU调度的最小单位)
同步: 排队执行 , 效率低但是安全. 异步: 同时执行 , 效率高但是数据不安全.
并发: 指两个或多个事件在同一个时间段内发生。 并行: 指两个或多个事件在同一时刻发生(同时发生)。
线程的创建一般有两种方法,一是继承java.lang.Thread类,二是实现java.lang.Runnable接口。(其实还有第三种实现Callable接口)。
(1)只使用一条线程的话,可以通过继承java.lang.Thead类来创建线程,语法如下: 创建MyThread继承Thread类:
/** * 继承Thread */ public class MyThread extends Thread{ /** * run方法是线程要执行的任务方法 * 触发条件时通过Thread对象的start()来启动任务 */ @Override public void run() { //这里的代码是一条新的执行路径 System.out.println("这是一条线程执行的代码"); } }在main方法调用start()方法:
//继承Tread MyThread m = new MyThread(); m.start();(2)需要多个线程同时执行任务的就需要实现java.lang.Runnable接口。语法如下: 创建MyRunnable继承Runnable方法:
public class MyRunnable implements Runnable{ @Override public void run() { //线程的任务 System.out.println("这是一个任务"); } }在main方法创建任务对象,再创建Thread并传入任务,然后启动:
//1. 创建一个任务对象 MyRunnable r = new MyRunnable(); //2. 创建一个线程,并为其分配一个任务 Thread t = new Thread(r); //3. 执行这个线程 t.start();相比于继承Thread,实现Runnable有如下优点
通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况可以避免单继承所带来的局限性任务与线程是分离的,提高了程序的健壮性线程池技术中,接受Runnable类型的任务,不接受Thread类型的线程(3)实现Callable接口 使用语法:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * Callable的使用 */ public class demo2 { public static void main(String[] args) throws ExecutionException, InterruptedException { //1. 编写类实现Callable接口,实现call方法 Callable<Integer> call = new MyCallable(); //2. 创建FutureTask对象,传入第一步的Callable对象 FutureTask<Integer> task = new FutureTask<>(call); //3. 通过Thread类,启动线程 new Thread(task).start(); //调用FutureTask的get()方法会阻塞主线程,先执行Callable再执行主线程 Integer num = task.get(); //task.isDone();//判断子线程是否执行完毕 //task.cancel(true);//取消任务 System.out.println(num); for (int i=0;i<10;i++){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是主线程"+i); } } static class MyCallable implements Callable<Integer>{ @Override public Integer call() throws Exception { for (int i=0;i<10;i++){ Thread.sleep(100); System.out.println("我是Callable线程"+i); } return 100; } } }Callable和Runnable的相同点与不同点
相同点:
都是接口都可以编写多线程程序都采用Thread.start()启动线程不同点:
Runnable没有返回值;Callable可以返回执行结果Callable接口的call()允许抛出异常;Runnable的run()不能抛出Callable获取返回值Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。JDK11API文档说明线程有以下6种状态
初始(NEW) :尚未启动的线程处于此状态。运行(RUNNABLE) :在Java虚拟机中执行的线程处于此状态(包括等待CPU时间片(READY)的线程和运行中(RUNNING)的线程)。阻塞(BLOCKED) :被锁的线程处于此状态。等待(WAITING) :无限期等待另一个线程执行特定操作(唤醒或者中断)的线程处于此状态。超时等待(TIMED_WAITING) :有限时间(参数指定)等待另一个线程执行特定操作的线程处于此状态。(终止)TERMINATED :执行完毕线程处于此状态。线程状态转换图: 来源于http://www.uml-diagrams.org/java-thread-uml-state-machine-diagram-example.html
线程常用方法:
方法作用long getId()返回此Thread的标识符String getName()返回此线程的名称int getPriority()返回此线程的优先级void interrupt()中断此线程(只是中断的信号)static boolean interrupted()测试当前线程是否已被中断void setDaemon(boolean on)参数为true将此线程标记为守护线程static void sleep(long millis)令当前正在执行的线程休眠(暂时停止执行)指定的毫秒数static void sleep(long millis, int nanos)令当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数static void yield()暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。void join()等待这个线程执行完毕void join(long millis)此线程等待 millis毫秒void join(long millis, int nanos)此线程等待 millis毫秒加上 nanos纳秒void start()令此线程开始执行; Java虚拟机调用此线程的run方法void setName(String name)将此线程的名称更改为等于参数 namevoid setPriority(int newPriority)更改此线程的优先级static Thread currentThread()返回对当前正在执行的线程对象的引用1.线程的优先级范围是整数1~10,默认优先级是5。 优先级越高不一定先执行,只是抢到时间片的概率高一点。线程优先级具有继承关系,如果A线程中创建了B线程,则B线程拥有和A线程一样的优先级。 Thread类有以下三个字段描述线程优先级。
变量和类型字段描述static intMAX_PRIORITY线程可以拥有的最大优先级,取值为10static intMIN_PRIORITY线程可以拥有的最低优先级,取值为1static intNORM_PRIORITY分配给线程的默认优先级,取值为52.线程等待:Object类的wait()方法。需要依赖synchronized关键字。会释放锁。 3.线程唤醒:Object类的notify()和notifyAll()方法。需要依赖synchronized关键字。notify()唤醒此对象下的任意一个线程,notifyAll()唤醒当前对象下的所有线程。 3.线程休眠:Thread类的sleep()方法,让线程休眠指定时间,期间不会释放锁,不依赖synchronized关键字 4.线程让步:Thread类的yield()方法,暂停当前线程,让步给同级别或高级别线程。 5.线程加入:Thread类的join()方法,等待该线程执行完毕。
线程分为守护线程和用户线程 用户线程: 当一个进程不包含任何的存活的用户线程时,进行结束 守护线程: 守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。 通过Thread类的setDaemon()方法可以设置当前线程为守护线程。
实现线程同步可以通过锁机制来实现,锁又分为隐式锁和显式锁。
隐式锁是靠synchronized关键字完成的,一共有2种使用方式,分别是同步代码块和同步方法。
下面通过一段代码演示同步代码块的使用:
public class Demo8 { public static void main(String[] args) { //格式:synchronized(锁对象){ // // // } Runnable run = new Ticket(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); } static class Ticket implements Runnable{ //总票数 private int count = 10; private Object o = new Object();//成员变量才是同一把锁 @Override public void run() { //Object o = new Object(); //局部变量则是每人一把锁。这里不是同一把锁,所以锁不住 while (true) { synchronized (o) {//锁住if判断语句 if (count > 0) { //卖票 System.out.println("正在准备卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count); }else { break; } } } } } }同步代码块的使用首先需要创建一个锁对象,需要主要创建的位置,再锁住需要同步的代码部分就可以实现同步效果。
依然通过一段代码演示同步方法的使用
//线程同步synchronized public class Demo9 { public static void main(String[] args) { Runnable run = new Ticket(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); } static class Ticket implements Runnable{ //总票数 private int count = 10; @Override public void run() { while (true) { boolean flag = sale(); if(!flag){ break; } } } public synchronized boolean sale(){ if (count > 0) { //卖票 System.out.println("正在准备卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count); return true; } return false; } } }使用同步方法只需要再方法的权限修饰符后面加上synchronized关键字就可以完成同步效果,需要注意的是非静态同步方法则调用this作为锁对象。静态同步方法调用类.class作为锁对象。
显示锁使用的是Lock类的子类ReentrantLock,lock()方法上锁,unlock方法解锁。依然通过下面代码进行演示。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo10 { public static void main(String[] args) { // 显示锁 Lock 子类 ReentrantLock Runnable run = new Ticket(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); } static class Ticket implements Runnable{ //总票数 private int count = 10; //参数为true表示公平锁 默认是false 不是公平锁 private Lock l = new ReentrantLock(true); @Override public void run() { while (true) { l.lock(); if (count > 0) { //卖票 System.out.println("正在准备卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count); }else { break; } l.unlock(); } } } }关于公平锁和非公平锁,隐式锁和显式锁都默认使用非公平锁。 公平锁:线程按照它们发出请求的顺序获取锁。 非公平锁:可以“插队”,哪个线程抢到就是哪个线程的。
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处:
降低资源消耗。提高响应速度。提高线程的可管理性。特点:无长度限制
执行流程: A:判断线程池是否存在空闲线程 B:存在则使用 C:不存在则创建线程并使用
创建语法:
//创建缓存线程池 ExecutorService service = Executors.newCachedThreadPool(); //指挥线程池执行新的任务 service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } });特点:长度指定
执行流程: A: 判断线程池是否存在空闲线程 B: 存在则使用 C:不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池, 然后使用 D:不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程单线程线程池
创建语法:
ExecutorService service = Executors.newFixedThreadPool(2);//指定线程池长度 //指挥线程池执行新的任务 service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); }特点:长度为1的定长线程池
执行流程: A:判断线程池的那个线程是否空闲 B :空闲则使用 C :不空闲则等待空闲后使用
创建语法:
ExecutorService service = Executors.newSingleThreadExecutor();//创建单线程线程池 //执行任务 service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } });有定时执行任务和周期执行任务两种方式
//创建周期性任务定长 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //定时执行一次 //参数1:定时执行的任务 //参数2:时长数字 //参数3:2的时间单位 Timeunit的常量指定 scheduledExecutorService.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } },5, TimeUnit.SECONDS); //5秒钟后执行 /* 周期性执行任务 参数1:任务 参数2:延迟时长数字(第一次在执行上面时间以后) 参数3:周期时长数字(没隔多久执行一次) 参数4:时长数字的单位 * **/ scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } },5,1,TimeUnit.SECONDS); }