重点:
- java中多线程运行原理
- 掌握两种线程创建方式
- 两种创建线程方式好处和弊端
- 掌握使用Thread类 中获取和设置线程名称的方法
- 使用匿名内部类创建多线程
- 描述java中线程池的运行原理
- 线程安全问题出现的原因
- 使用同步代码块\同步方法解决线程安全问题
- 出现死锁的原因
- wait方法
- notify方法
- 线程的五个状态
一、概述
- 进程: 进程是指正在运行的程序。 确切的来说,当一个程序进入内存开始运行,即变成一个进程。 进程是处于运行状态的程序,并具有一定独立功能。
- 线程: 线程是进程的一个执行单元。 负责当前进程中程序的执行。一个进程中至少有一个线程,一个进程可以是多线程的。
- 多线程程序与单线程程序:
- 单线程程序 : 若有多个任务只能一次执行。
- 多线程程序 : 若有多个任务,可以同时执行。
二、多线程运行原理
- 分时调度 : 所有线程轮流使用CPU 的使用权 , 平均分配每个线程的占用CPU时间。
- 抢占式调度 : 优先让优先值高的线程使用Cpu , 如果线程的优先级相同 , 则随机选择一个 (**线程的随机性**)。
- java 使用的是抢占式调度。
三、抢占式调度详解
- 实际上 , CPU使用抢占式调度模式是在多个线程之间进行着高速的切换。 对于CPU的一个核而言 , 在某一时刻只能执行一个进程 。 而CPU在多个线程之间的切换速度相对于我们而言相当的块 , 看上去是多个线程同时执行。
- 多线程不能提高程序的执行速度 , 但是能提高CPU的使用率。
四、主线程
- JVM启动后 , 必然有一个执行路径(线程)从mian方法开始 , 一直执行到main方法执行结束 , 这个线程在java中 称之为主线程。
- 示例:
package com.tj.ThreadTest; public class ThreadTest_01 { public static void main(String[] args) { class_01 c1 = new class_01("催化"); class_01 c2 = new class_01("淑芬"); c1.show(); c2.show(); } } class class_01{ private String name; class_01(String name){ this.name = name; } public void show(){ for(int i= 0;i<1000;i++){ System.out.println(this.name+","+i); } } }
五、Thread类
- 构造方法:
- Thread() : 创建一个新线程。
- Thread() : 创建一个名为name的线程。
- 常用函数:
- start() : 使线程开始执行 , 由JVM调用Thread‘中的run方法。
- run() : 该线程要之心的操作
- sleep() : 在指定的毫秒数之内让当前正在执行的线程休眠(暂停执行)。
- ’创建线程的两种方法:
- 将该类声明为Thread类的子类。 该类必须重写Therad类的run方法 。 创建对象 , 开启线程。 run方法相当于主线程的main方法
- 该类实现Runnable接口: 实现run方法 , 创建Runnable子类对象并传入到某个线程的构造方法中 , 开启线程。
六、创建线程方式 --- 继承Thread类
- 第一步: 定义一个类 , 继承Thread类
- 第二步: 重写run方法
- 第三步:创建子类 对象
- 第四步: 调用start方法 , 开启线程并执行 , 此时JVM会自动调用run方法。
- 示例:
package com.tj.ThreadTest; public class ThreadTest_02 { public static void main(String[] args) { //创建线程对象 ThreadClass_01 ti = new ThreadClass_01("线程1"); //开启线程 ti.start(); } } //使用继承Thread类的方式创建一个线程类 class ThreadClass_01 extends Thread{ private String name; public ThreadClass_01(String name) { this.name = name; } //重写run方法 @Override public void run() { for(int i =0;i<1;i++){ System.out.println(name +","+i); //获取当前对象线程 System.out.println(Thread.currentThread()); System.out.println(Thread.currentThread().getName()); } } }
- 思考: run方法与start方法的区别?
- 线程对象调用run方法不能开启线程 。 仅仅是对象调用方法 。
- 线程对象调用start方法 ,开启线程 , 并让JVM调用run方法在开启的线程中执行。
- 继承Therad类原理
- Thread类用于描述线程 , 具备线程应有的功能 。
- 创建线程的目的是为了建立程序单独执行的路径, 让多部分代码同时执行 。 也就是说线程的创建并执行 需要给定线程 执行的任务。
- 程序中的主线程 , 任务定义在main中 。 自定义线程需要执行的任务定义在run中
- Thread类中本身的run方法并不是我们所期望的 , 所以要重写run方法 , 重新制定线程任务。
- 多线程图解
- 多线程执行在栈内存中 , 其实每一个执行线程都有一片属于自己的栈内存空件 , 进行方法的弹栈和压栈。
- 当线程任务执行完成之后 , 自动释放栈内存 , 当所有的线程执行完毕之后, 进程结束。
- 获取线程的名称
- currentThread() : 返回当前正在执行的线程的实例
- getName() : 获取当前实例的名称
- 主线程的名称 : main
- 自定义线程的名称: Thread -0 ,如果有多个线程时 , 数字顺延。 。。。 Thread - 1 。。。。
- 示例:
//使用继承Thread类的方式创建一个线程类 class ThreadClass_01 extends Thread{ private String name; public ThreadClass_01(String name) { this.name = name; } //重写run方法 @Override public void run() { for(int i =0;i<1;i++){ System.out.println(name +","+i); //获取当前对象线程 System.out.println(Thread.currentThread()); System.out.println(Thread.currentThread().getName()); } } }
七、创建线程方式 --- 实现Runnable接口
- 第一步 :定义一个类 , 实现Runnable接口
- 第二步 :实现run方法
- 第三步 :将该类的实例对象传入到线程的构造函数中。
- 第四步 :开启线程
- 示例:
package com.tj.ThreadTest; public class ThreadTest_03 { public static void main(String[] args) { //创建实现Runnable接口的类对象 ThreadClass_03 tc = new ThreadClass_03(); //将实现实例一参数形式传入到Thread类的构造函数中 Thread t1 = new Thread(tc); Thread t2 = new Thread(tc,"线程2");//可以指定该线程的名字 //启动线程 t1.start(); t2.start(); } } //创建实现Runnable接口的类 class ThreadClass_03 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程被执行"); } }
- 实现Runnable原理 , 两种方式的区别:
- 实现Runnable接口 , 避免了单继承的局限性 , 覆盖Runnable接口中run方法 , 将线程任务定义在run方法中。
- 只有创建Thread类对象时才能创建新的线程 , 线程任务已经被封装在RUnnable接口中 。 所以可以将实现Runnable接口的实例对象以参数的形式传到Thread构造函数中。 这样线程创建时就可以明确指定要运行的线程任务。
- 实现Runnable接口的好处
- 避免了单继承的局限性
- 更加符合面向对象 。 线程分为两部分 : 线程对象 , 线程任务 ; 而继承Thread之后 , 线程对象和线程任务耦合在一起
- 实现Runnable接口 , 将任务单独分离出类 封装成对象 , 是线程任务解耦。
- 线程匿名内部类的使用
- 方式一:创建线程对象 ,直接重写run方法。
- 方式二:使用匿名内部类实现RUnnable接口后传入THread构造函数。
- 示例 :
package com.tj.ThreadTest; //线程匿名内部类的使用 public class ThreadTest_04 { public static void main(String[] args) { //方式一 new Thread(){ @Override public void run() { System.out.println("线程1开启"); } }.start(); //方式二 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"开启"); } },"线程2").start(); } }
八、线程池
- 概述:
- 可以容纳多个线程的容器 , 其中的线程可以反复使用 。 省去了频繁创建线程对象的操作 。 节省资源。
- 为什么要使用线程池?
- 在java中如果每个请求到达都开启一个线程 , 开销是相当大的 , 在实际开发中 , 创建和销毁线程花费的时间和消耗掉的资源都相当大 , 甚至超过实际处理任务请求的时间和资源。
- 如果JVM里创建了太多的线程 , 可能会使系统由于过度消耗内存或“”切换过度“导致系统资源不足 , 为了防止系统资源不足的情况 , 需要采取一些办法 来限定任何给定时间处理的请求 数目。
- 宗上述两点 , 所以需要尽可能的减少线程的创建的销毁次数 , 并尽量使用已有的对象进行服务。
- 线程池主要用于解决线程声明周期开销和系统资源不足的问题。由于请求到来的时候 , 线程已经存在 , 所以消除了创建线程带来的延时, 立即为请求服务 。 使程序响应更快。
- ”使用线程池的方式 ---- 实现Runnable接口
- 通常线程池都是通过线程工厂创建 , 在调用线程池中的方法和数据 , 通过线程去执行任务 。
- 创建线程池
- Executors线程池创建工厂类
- ExecutorService 线程池类
- Furture接口 用来记录线程任务执行完毕后产生的结果
- 使用线程池的步骤:
- 创建线程池对象
- 创建Runnable子类对象
- 提交Runnabel子类对象
- 关闭线程池
- 示例:
package com.tj.ThreadTest; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 使用线程池方式 --- Runnable * */ public class ThreadTest_05 { public static void main(String[] args) { //创建线程池对象, 指定包含几个线程。 ExecutorService es = Executors.newFixedThreadPool(3); //创建实现Runnable接口的子类对象 ThreadClass_05 t5 = new ThreadClass_05(); //提交线程任务 es.submit(t5,"周教练"); es.submit(t5,"吴教练"); es.submit(t5,"正教练"); es.submit(t5,"赵教练"); es.submit(t5,"王教练"); /*注意: submit 方法调用结束后,程序并不终止,因为线程池控制了线程的关闭 *将使用完的线程有归还到线程池中 * */ //关闭线程池 es.shutdown(); } } class ThreadClass_05 implements Runnable{ @Override public void run() { System.out.println("我需要一个教练"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("教练来了"+Thread.currentThread().getName()); System.out.println("叫我游泳之后教练回到了游泳池!"); } }
- 使用线程池方式 --- Callable接口
- Callable 接口 : 与Runnable接口功能相似 。 用类指定线程任务 , 其中call方法 用来返回线程任务执行后返回的结果 , call方法可以抛出异常。
- ExecutorService : 线程池类 , 获取一个线程池对象 , 并执行线程的call方法
- Future接口 : 用来记录线程对象执行完毕之后产生的结果 。
- 使用线程池对象的步骤:
- 创建线程池对线
- 创建Callable子类对象
- 提交Callable子类对象
- 关闭线程池
- 示例:
package com.tj.ThreadTest; 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; /** * 使用线程池方式 -- callable * */ public class ThreadTest_07 { public static void main(String[] args) throws InterruptedException, ExecutionException { //创建线程池 ExecutorService es = Executors.newFixedThreadPool(2); //创建县线程任务 ThreadDemo_07 t1 = new ThreadDemo_07(); //提交线程任务 Future<String> submit = es.submit(t1); System.out.println(submit.get()); //关闭线程池 es.shutdown(); } } class ThreadDemo_07 implements Callable<String> { @Override public String call() throws Exception { return "绝对符合国家"; } }
- 线程池练习: 返回两个数相加的结果
package com.tj.ThreadTest; 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 ThreadTest_08 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService es = Executors.newFixedThreadPool(2); ThreadDemo_08 t1 = new ThreadDemo_08(1, 1); ThreadDemo_08 t2 = new ThreadDemo_08(6, 1); ThreadDemo_08 t3 = new ThreadDemo_08(3, 1); ThreadDemo_08 t4 = new ThreadDemo_08(2, 1); Future<Integer> s1 = es.submit(t1); Future<Integer> s2 = es.submit(t2); Future<Integer> s3 = es.submit(t3); Future<Integer> s4 = es.submit(t4); System.out.println(s1.get()); System.out.println(s2.get()); System.out.println(s3.get()); System.out.println(s4.get()); es.shutdown(); } } class ThreadDemo_08 implements Callable<Integer>{ int a = 0,b = 0; public ThreadDemo_08(int a, int b) { this.a = a; this.b = b; } @Override public Integer call() throws Exception { return a+b; } }
- 线程同步
- 同步代码块
- 同步代码块 中的锁对象可以是任意对象 , 但是要使多个线程同步时,需要使用同一个锁对象 , 才能保证线程安全。
- 同步方法
- 在方法声明上添加 synchronized
- 同步方法的锁对象就是this
- 静态同步方法的锁对象是: 类名.class
- 线程不安全示例:
package com.tj.ThreadTest; /** * 电影院卖票 * 本场共100张票 * 多个窗口同时卖票 * */ public class ThreadTest_09 { public static void main(String[] args) { ThreadDemo_09 t = new ThreadDemo_09(); new Thread(t,"窗口1").start();; new Thread(t,"窗口2").start();; new Thread(t,"窗口3").start();; new Thread(t,"窗口4").start();; } } class ThreadDemo_09 implements Runnable{ int ticket = 100; @Override public void run() { //模拟卖票 while(ticket>=0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖票,余票:"+ --ticket); } } /** * 出现了错误的情况 , 线程不安全 * 窗口4正在卖票,余票:1 窗口3正在卖票,余票:-1 窗口1正在卖票,余票:0 窗口2正在卖票,余票:-2 * */ }
- 同步代码块示例:
package com.tj.ThreadTest; /** * 同步代码块解决 线程安全问题 * */ public class ThreadTest_10 { public static void main(String[] args) { ThreadDemo_10 t = new ThreadDemo_10(); new Thread(t,"窗口一").start(); new Thread(t,"窗口二").start(); new Thread(t,"窗口三").start(); } } class ThreadDemo_10 implements Runnable { int ticket = 10; //创建锁对象 Object lock =new Object(); @Override public void run() { synchronized (lock) { while(ticket>0){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在售票,余票"+ --ticket); } } } }
- 同步方法示例:
package com.tj.ThreadTest; /** * 同步方法解决线程安全问题 * * */ public class ThreadTest_11 { public static void main(String[] args) { ThreadDemo_11 t = new ThreadDemo_11(); new Thread(t,"窗口一").start(); new Thread(t,"窗口二").start(); new Thread(t,"窗口三").start(); } } class ThreadDemo_11 implements Runnable { int ticket = 10; //同步方法 //同步方法的锁对象就是this // 静态同步方法的锁对象的 : 类名.class @Override public synchronized void run() { while(ticket>0){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在售票,余票"+ --ticket); } } }
- 同步代码块
- 死锁
- 同步锁使用的弊端 : 当线程中出现 多个同步(多个锁)时 , 如果同时嵌套了其他的同步 , 容易 引起 一种现象: 程序 无限等待 , 这种现象叫死锁。
- 示例:
package com.tj.ThreadTest; import java.util.Random; /** * 线程任务中出现多个同步时 * 发生死锁的情况 * */ public class ThreadTest_12 { public static void main(String[] args) { ThreadDemo_12 t = new ThreadDemo_12(); new Thread(t).start(); new Thread(t).start(); } } //定义锁对象 class MyLock_12{ public static final Object lockA = new Object(); public static final Object lockB = new Object(); } class ThreadDemo_12 implements Runnable{ @Override public void run() { int x = new Random().nextInt(1); // 0~1 if(x%2 == 0){ synchronized (MyLock_12.lockA) { System.out.println("if-lockA"); synchronized (MyLock_12.lockB) { System.out.println("if - lockB"); System.out.println("if 大口吃肉"); } } }else{ synchronized (MyLock_12.lockB) { System.out.println("if-lockB"); synchronized (MyLock_12.lockA) { System.out.println("if - lockA"); System.out.println("else 大口吃肉"); } } } x++; } }
- Lock接口
- 常用方法 :
- Lock() : 获取锁对象 。
- unlock() : 释放锁
- 示例:
package com.tj.ThreadTest; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用Lock接口 * 改进卖票程序 * */ public class ThreadTest_13 { public static void main(String[] args) { MyTicket_13 mt = new MyTicket_13(); new Thread(mt , "窗口一").start(); new Thread(mt , "窗口二").start(); // new Thread(mt , "窗口三").start(); // new Thread(mt , "窗口四").start(); // new Thread(mt , "窗口五").start(); // new Thread(mt , "窗口六").start(); } } class MyTicket_13 implements Runnable{ int ticket = 10; //创建锁对象 Lock l = new ReentrantLock(); @Override public void run() { //获取锁 l.lock(); while(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖票,余票:" + --ticket); } //释放锁 l.unlock(); } }
- 常用方法 :
- 等待唤醒机制
- 线程之间的通讯 : 多个线程在处理同一个资源 , 但处理的动作不同。
- 等待唤醒机制 : 通过 一定的手段使各个线程能有效的利用资源 。
- 常用方法:
- wait() : 等待 , 将正在执行的线程释放其执行资格和执行权 ,并 存储在线程池中 。
- notify() : 唤醒 , 唤醒线程池中被wait的线程 , 一次唤醒一个 , 而且是任意的 。
- notifyAll() : 唤醒所有 , 将线程池中所有被wait的线程唤醒。
- 这些方法都定义在Object 中 , 因为:使用 时必须明确指定所属的锁 , 而锁又可以是任意对象 , 所以能被任意对象调用的方法一定定义在Object中 。
- 示例:
package com.tj.ThreadTest; /** * 线程中等待唤醒机制实例 * 等待唤醒机制,使多个线程之间有效利用资源。 * 输入线程想Resource 中输入name ,sex ,输出线程从资源中输出 * 1. 档input发现resource中没有数据时 ,开始输入,输入完成后叫outPut输出,如果已有数据 则wait * 2. 档output发现Resource中没有数据时 ,就wait ,有数据时唤醒输出后叫input输入 * */ public class ThreadTest_14 { public static void main(String[] args) { //资源对象 Resours_14 r = new Resours_14(); //任务对象 Input in = new Input(r); Output out = new Output(r); //开启线程 new Thread(in).start(); new Thread(out).start(); } } //模拟资源类 class Resours_14{ private String name ; private String sex ; private boolean flag = false; public synchronized void in(String name ,String sex ){ //如果有值 ,则wait状态 ,等待输出 if(flag) try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } //设置成员变量 this.name = name; this.sex = sex; //设置之后 Resource 中有值 将flag置为true flag = true; //唤醒output this.notify(); } public synchronized void out(){ //如果没有值 ,则进入等待状态 ,登台输入 if(!flag) try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } //将数据输出 System.out.println("姓名:" + name +"性别: "+sex); //改变标记 flag = false; //唤醒input ,进行数据输入 this.notify(); } } //输入线程任务类 class Input implements Runnable{ private Resours_14 r ; public Input(Resours_14 r) { this.r = r; } @Override public void run() { int count = 0; while (true){ //降低CPU压力 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //交替设置资源 if(count == 0){ r.in("小明", "男"); }else{ r.in("小花", "女"); } //两个数据之间相互切换 count = (count +1) %2; } } } //输出线程任务类 class Output implements Runnable{ private Resours_14 r ; public Output(Resours_14 r) { super(); this.r = r; } @Override public void run() { while(true){ //降低CPU压力 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //输出资源内容 r.out(); } } }
- 补充: wait() 与 sleep() 的区别 :
- wait() : 释放锁对象 , 释放CPU使用权 , 在休眠时间内能被唤醒 。
- sleep() : 不释放锁对象 , 释放CPU使用权 , 在休眠时间内不能被唤醒
九、线程安全
-
如果有多个线程同时运行同一段代码,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量值也与预期是一样的,那么就是线程安全的。
-
线程的安全问题都是由全局变量和静态变量引起的,每个线程对全局变量和局部变量只有读操作而没有写操作,一般来说这个线程就是安全的;若多个线程同时执行写操作,一般都是需要考虑线程同步,否则的话可能影响线程安全。
-
示例:
/** * 线程同步的两种方式 1. 同步代码块 2. 同步方法 示例: 电影院买票 共有100张票 ,三个窗口同时买票 * */ public class ThreadDemo_09 { public static void main(String[] args) { new Thread(new ThreadTest_09_01(),"窗口一").start(); new Thread(new ThreadTest_09_01(),"窗口二").start(); new Thread(new ThreadTest_09_01(),"窗口三").start(); } } class ThreadTest_09_01 implements Runnable { private static int ticket = 100; @Override public void run() { while (1 == 1) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "出票,余票" + --ticket); } } } } 结果: 窗口一出票,余票99 窗口三出票,余票98 窗口二出票,余票99//与窗口一重复 ,线程不安全 窗口二出票,余票95 窗口三出票,余票96 窗口三出票,余票93 窗口三出票,余票92 窗口一出票,余票97 窗口二出票,余票94 窗口一出票,余票91 窗口二出票,余票90 。。。。
十、线程同步
- java中处理线程同步 的两种方式:
- 同步代码块
- 同步方法
- 同步代码块: 在代码块声明上加上synchronized 且要使用同一锁对象才能保证线程同步。.
- 同步方法 : 在方法属性上加synchronized 。
十一、守护线程
-
守护其他线程的执行。
-
当被守护的线程结束时 , 无论守护线程是否执行完毕则随之结束 。
-
只要代码中出现了线程要么是守护线程 , 要么是被守护的线程 。
-
如果出现率多个被守护的线程 , 则以最后一个被守护的线程结束为标志而结束 。
-
gc(垃圾回收机制)本质上就是一盒
-
//设置为守护线程 t.setDaemon(true);
十二、 线程的优先级
- 线程有1~10 10个优先级 。
- 数字越大 , 优先级越高 , 理论上优先级越高的线程越容易抢到cpu执行权 , 但是相邻的两个优先级之间差别不大, 至少相差五个优先级才能看出效果 。
- 如果不设置优先级 , 则默认的优先级为5 。
- java中处理线程同步 的两种方式: