线程
1 线程概念
进程:进程指正在内存中运行的程序。进程具有一定的独立性。
线程:线程是进程中的一个执行单元。负责当前进程中程序的执行。一个进程中至少有一个线程。如果一个进程中有多个线程,称之为多线程程序。
java中的线程采用的是抢占式调度,如果线程的优先级相同,那么会随机选择一个线程执行。理论上,优先级高的线程抢到CPU的概率更大。
CPU使用抢占式调度在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程。CPU在多个线程间切换的速度相对我们的感受要快得多,看上去就是在同时执行。
多线程程序并不能提高程序的运行速度,但是能够提高程序的运行效率,让CPU的使用率更高。
为什么要使用多线程?
提高用户体验
提高CPU的利用率
线程在执行的过程中会和计算机硬件进行交互,线程和硬件交互的时候会暂时空置CPU,从而多线程可以提高CPU利用率
2 线程的创建
2.1 线程的创建方式一
定义一个类继承Thread
重写run方法
创建子类对象
调用start方法,开启线程并让线程执行
public class MyThread extends Thread{ @Overridepublic void run() {// 把希望其他线程执行的代码放在run方法中for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "正在执行了。。。");}} }
2.2 线程的创建方式二[推荐]
定义类实现Runnable接口
实现接口中的run方法
创建Thread类的对象
将Runnable接口的实现类对象作为参数传递给Thread类的构造方法
调用Thread类的start方法开启线程
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "正在执行");}} } public static void main(String[] args) { // MyRunnable myRunnable = new MyRunnable(); // Thread thread = new Thread(myRunnable,"子线程"); // thread.start();// 匿名内部类 // new Thread(new Runnable() { // @Override // public void run() { // for (int i = 0; i < 100; i++) { // System.out.println(Thread.currentThread().getName() + "执行了"); // } // } // }).start();// lambda表达式new Thread(()-> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "执行了");}},"小线程").start(); for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName());}}
课堂练习:售票案例:模拟电影院卖票,实现3个窗口同时卖"哪吒2"的100张票。
方式一:
public class Ticket implements Runnable{int ticket = 100;@Overridepublic void run() {while (true){if (ticket <= 0) {break;} System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);}} } public static void main(String[] args) {Ticket ticket = new Ticket();// 创建线程Thread thread1 = new Thread(ticket,"窗口1");Thread thread2 = new Thread(ticket,"窗口2");Thread thread3 = new Thread(ticket,"窗口3");thread1.start();thread2.start();thread3.start();}
方式二:
public class TicketDemo { static int ticket = 100; public static void saleTicket(){while (true){if (ticket <= 0) {break;}System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);}} } public static void main(String[] args) { // new Thread(()->{ // TicketDemo.saleTicket(); // },"窗口1").start();new Thread(TicketDemo::saleTicket,"窗口1").start();new Thread(TicketDemo::saleTicket,"窗口2").start();new Thread(TicketDemo::saleTicket,"窗口3").start();}
经过多次运行会发现以上的内容可能会出现以下结果:
有重复的票
有0的票
有-1的票
线程安全隐患:如果有多个线程在同时运行,而这些线程可能会同时运行某段代码。程序每次运行的结果和单线程运行的结果一样,而且其他的变量值也和预期的一样,就是线程安全。否则就是有线程安全隐患。
线程安全隐患出现的条件:
多线程
有共享资源
有修改操作
以上三个条件破坏一个就可以避免线程安全隐患
3 线程安全隐患解决方案
3.1 同步代码块
synchronized(锁对象){可能会产生线程安全问题的代码; }
在同一个时刻,同步代码块中只能有一个线程执行,执行完毕之后会释放锁,所有线程再去抢锁对象,抢到之后的线程可以执行同步代码块中的代码,其他线程等待同步代码块执行完毕后释放锁。
同步代码块中的锁对象可以是任意的对象,但是多个线程时,要使用同一个锁对象才能够保证线程安全。
3.2 同步方法
在方法上添加synchronized
public synchronized 返回值类型 方法名(参数){}
在同一个时刻,同步方法中只能有一个线程执行,执行完毕之后会释放锁,所有线程再去抢锁对象,抢到之后的线程可以执行同步方法中的代码,其他线程等待同步方法执行完毕后释放锁。
// 静态同步方法// 锁对象是 类名.classprivate synchronized void method2(){} // 同步方法 // 锁对象是thisprivate synchronized boolean method() {if (ticket <= 0) {return true;} System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);return false;}
3.3 Lock
// 创建锁Lock lock = new ReentrantLock(); @Overridepublic void run() {while (true) {// 线程休眠 单位:毫秒try {Thread.sleep(1L);} catch (InterruptedException e) {throw new RuntimeException(e);}// 加锁lock.lock();try {if (ticket <= 0) {break;}System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);} finally {// 释放锁lock.unlock();} }}
同步:一段代码只允许一个线程执行
异步:一段代码允许多个线程执行
同步一定是线程安全的
线程安全不一定同步
异步不一定线程不安全
线程不安全一定是异步
3.4 ThreadLocal
从程序上游向下游传递数据
static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {String str = "你好";threadLocal.set(str);a();}public static void a(){b();}private static void b() {c();}private static void c() {d();}private static void d() {String s = threadLocal.get();System.out.println(s);}
可以解决线程安全隐患
public static ThreadLocal<Printer> tl = new ThreadLocal<>() {// 匿名内部类// 重写初始化方法,返回一个打印机@Overrideprotected Printer initialValue() {return new Printer();}};public static void main(String[] args) {new Thread(new GFS()).start();new Thread(new BFM()).start();}}class Printer {public void print(String str) {System.out.println("打印机在打印" + str);} }class BFM implements Runnable {@Overridepublic void run() {Printer printer = TestDemo4.tl.get();printer.print(printer + "我是白富美");printer.print(printer + "我很美");printer.print(printer + "很有钱");} }class GFS implements Runnable {@Overridepublic void run() {Printer printer = TestDemo4.tl.get();printer.print(printer + "我是高富帅");printer.print(printer + "我很高");printer.print(printer + "也很帅");} }
4 死锁
死锁:由于锁的嵌套导致锁之间相互锁死的现象
public class TestDemo5 {static Print p = new Print();static Scan s = new Scan();public static void main(String[] args) {new Thread(()->{synchronized (p){p.print();try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (s){s.scan();}}}).start();new Thread(()->{synchronized (s){s.scan();synchronized (p){p.print();}}}).start();} }class Print{public void print(){System.out.println("打印机在打印");} }class Scan{public void scan(){System.out.println("扫描仪在扫描");} }
5 等待唤醒机制
package cn.javasm.demo;public class TestDemo6 {public static void main(String[] args) {Student student = new Student();new Thread(new Ask(student)).start();new Thread(new Change(student)).start();} }class Change implements Runnable{private Student student;public Change(Student student) {this.student = student;}@Overridepublic void run() {while (true){synchronized (student){if (student.flag){try {// 让当前线程陷入等待student.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}if ("周杰伦".equals(student.getName())){student.setName("林志玲");student.setGender("女生");}else {student.setName("周杰伦");student.setGender("男生");}student.flag = true;// 随机唤醒一个正在等待的线程student.notify();}}} }class Ask implements Runnable{private Student student;public Ask(Student student) {this.student = student;}@Overridepublic void run() {while (true){synchronized (student) {if (!student.flag){try {// 让当前线程陷入等待student.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("老师,我是" + student.getName() + ",我是" + student.getGender() + ",我要问问题");student.flag = false;// 唤醒一个线程student.notify();}}} }class Student{private String name;private String gender;// true让Ask执行// false让Change执行public boolean flag;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;} }
作业:完成生产者消费者模式
一个线程作为消费者,一个线程作为生产者。生产者每生产一次,消费者就消费一次。生产者每次生产的商品数量以及消费者消费的数量使用随机数产生即可。每一次的生产的商品数量和上一次剩余的商品数量之和不能超过1000。