多线程(重点 :创建线程、同步机制)
- 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() 设置主线程名字
线程对象.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;
}
}