多线程(重点 :创建线程、同步机制)
- Lambda表达式
函数式接口:只包含一个抽象方法的接口,就可以用Lambda表达式
线程的四种实现方法
- 1、继承Thread类,不是函数式接口,用不了Lambda表达式,重写run()方法,创建继承类对象,调用start():1.方法开启线程,2.调用当前线程的run()(不推荐)
//注意:开启线程后,线程不一定立即执行,由cpu执行调度 public class MyThread extends Thread{ /*共享资源要加static,因为这是extends方式,多线程会创建多个MyThread对象, 如果不加static,那么每个线程都会有一个num*/ private static int num; //重写run() @Override public void run() { //执行体 for (int i = 0; i < 100; i++) { //Thread.currentThread().getName() 得到当前线程名 System.out.println(Thread.currentThread().getName()+"第"+i+"次"); } } public static void main(String[] args) { //开启线程,一个线程只能启动一次 //start()的作用:1.开启线程 2.调用run() new MyThread().start(); new MyThread().start(); } } }
- 2、实现Runnable接口(函数式接口),实现run(),创建线程对象,调用start()(推荐)
public class MyThread implements Runnable{ //当多个对象使用同一Runnable接口是,num为共享资源,不用加static,继承方式就要加static private int num; //重写run() @Override public void run() { //执行体 for (int i = 0; i < 100; i++) { //Thread.currentThread().getName() 得到当前线程名 System.out.println(Thread.currentThread().getName()+"==>第"+i+"次"); } } public static void main(String[] args) { MyThread t1 = new MyThread(); //开启线程,同一个对象t,可以被多个线程同时使用,一个线程只能启动一次 //设置线程名 //start()方法开启线程,调用当前线程的run() new Thread(t1,"线程一").start(); new Thread(t1,"线程二").start(); } }
//方式二:使用Lambda表达式 public class MyThread{ public static void main(String[] args) { Thread t1 = new Thread(//使用Lambda表达式 ()->{ for (int i = 0; i < 100; i++) { //Thread.currentThread().getName() 得到当前线程名 System.out.println(Thread.currentThread().getName()+"==>第"+i+"次"); } } ,"线程1"); //一个线程只能启动一次 t1.start(); } }
继承Thread和实现Runnable区别
"继承Thread" : 会有OOP单继承的局限性 "实现Runnable" : 避免了OOP单继承的局限性,方便同一个对象被多个线程使用
- 3、实现Callable接口(函数式接口)4、结合线程池
//1.实现Callable接口,需要设置返回值类型 public class MyThread implements Callable<Integer> { //2.重写call方法,需要抛出异常 @Override public Integer call() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"==>第"+i+"次"); } return 1; } public static void main(String[] args) throws ExecutionException, InterruptedException { //3.创建目标对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); //4.创建执行服务,创建线程池,并指定线程池容量 ExecutorService service = Executors.newFixedThreadPool(3); //5.传入指定目标线程对象,提交执行 Future<Integer> result1 = service.submit(t1); //service.execute(Runnable run);没有返回值,适用于Runnable //service.submit(Callable call);有返回值,适用于Callable Future<Integer> result2 = service.submit(t2); Future<Integer> result3 = service.submit(t3); //获取各线程结果 Integer integer1 = result1.get(); Integer integer2 = result2.get(); Integer integer3 = result3.get(); //关闭服务 service.shutdownNow(); } }
- 使用线程池的好处
(1)提高响应速度(减少了创建新线程的时间) (2)降低资源消耗(复用线程) (3)利于管理: corePoolSize : 核心池的大小 maximumPoolSize : 最大线程数 keepAliveTime : 线程空闲多长时间后会终止
线程的五大状态
- 线程礼让(了解)
//描述:给别的线程再一次竞争cpu的机会,不一定会成功,成功与否看cpu让那条线程执行 //用法,Thread的静态方法yield() Thread.yield();
join插队行为(了解)
public class MyThread implements Runnable{ //重写run() @Override public void run() { //执行体 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"==> 第"+i+"次"); } } public static void main(String[] args) throws InterruptedException { MyThread t1 = new MyThread(); Thread vip = new Thread(t1, "VIP线程"); vip.start(); //一个线程只能启动一次 for (int i = 0; i < 100; i++) { if(i==50){ //当main线程执行50次后,VIP线程强行插队啦 //main线程被阻塞直到Vip线程执行完毕 vip.join(); } System.out.println("main ==> 第"+i+"次"); } } }
模拟延时:放大问题的发生性
获取当前系统时间
//获取当前系统时间 public class Mytest { public static void main(String[] args) throws InterruptedException { //获取当前系统时间毫秒值 Date times = new Date(System.currentTimeMillis()); //每过一秒打印一次 while(true){ System.out.println(new SimpleDateFormat("YYYY年 MM月 dd日 HH:mm:ss").format(times)); //打印一次更新一次 Thread.sleep(1000); times = new Date(System.currentTimeMillis()); } }
}
- set 、 get name/pripority 设置、获取线程名、优先级 - Thread.currentThread().setName() 设置主线程名字 ```java 线程对象.setDaemon(true); 用户进程结束了,守护线程也会结束
解决线程安全的三种方法(重点)
"出现线程安全的前提:多个线程操作共享数据" 1.同步代码块(推荐) 2.同步方法(不推荐) 3.Lock锁(超级推荐) 注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象
1、同步代码块,synchronized 可以修饰方法和类
synchronized(锁对象){ //操作共享资源的代码 } (1) 锁对象: extends Thread :MyThread.class 或 static修饰的任意对象 implements Runnable :this (2) 只有implements Runnable方法才可以用this,因为只new一个Runnable实现类, 而extends Thread方***new多个Thread对象,此时this锁不唯一 (3) 多个线程必须共用同一把锁 注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象
- 同步代码块局限性
其他线程必须挂起等待进入同步代码块的线程 引起性能问题 引起优先级倒置
2、同步方法
public synchronized void run(){ //同步监视器:this,适合implement Runnable的线程 //同步代码 注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象 } public static synchronized void run(){ //static修饰 //同步监视器:当前所属类.class,适合extends Thread的线程 //同步代码 注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象 }
3、Lock 锁
在实现类Runnable里面定义ReentrantLock锁(最好用static修饰),finally里面解锁
线程通信 synchronized
- Object的三个线程通信方法 :
只能出现在synchronized中,并且调用三个方法的必须是同步监视器同步监视器.wait() :一旦执行此方法,当前线程进入阻塞状态,并释放同步锁 同步监视器.notify() :一旦执行此方法,就会唤醒一个被wait的高优先级线程 同步监视器.notifyAll() :一旦执行此方法,就会唤醒所有被wait的线程
面试题
1、synchronized 和 Lock 的异同
相同 :都可以解决线程安全问题 不同 :synchronized会自动释放锁对象 Lock需要手动上锁lock() 和解锁 unlock() 使用优先级 :Lock => 同步代码块 => 同步方法
2、sleep() 和 wait() 的异同
相同 :都可以使当前的线程进入阻塞状态 不同 : (1) 声明的位置不同:Thread类声明了sleep();Object类声明了wait()。 (2) 调用的要求不同:sleep()可以在任何场景下调用。 wait()必须在synchronized的同步监视器调用。 (3) sleep()不会释放同步监视器,wait()会释放同步监视器。
3、如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
(1) call()可以有返回值 (2) call()可以抛出异常,被外面的操作捕获,获取异常信息 (3) Callable支持泛型
4、手写线程安全的懒汉式/写一个线程安全的单例模式
package com.gwq.pojo; public class BankTest{ } //单例模式:只能有一个实例化的对象,构造方法私有 //懒汉式 class Bank{ //构造方法私有 private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //外加if(instance == null)是为了提高效率,避免每一次都进入锁内判断 if(instance == null){ //线程安全 synchronized(Bank.class){ if(instance == null){ instance = new Bank(); } } } return instance; } }#java多线程##学习路径#