多线程
相关概念
程序:指令和数据的有序集合
进程:执行程序的一次执行过程,是系统资源分配的单位
线程:一个进程可以包含多个线程,至少包含一个线程,线程是cpu调度和执行的单位
线程的三种创建方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Tread类
自定义线程类继承Thread类,重写run()方法编写线程执行体,创建线程对象,调用start()方法启动。
启动线程:子类对象.start()
实现Runnable接口
自定义线程类实现Runnable接口,重写run()方法编写线程执行体,创建线程对象,调用start()方法启动线程,可以避免单继承的局限性,推荐使用
启动线程:传入目标对象+Thread对象.start()
Lamda表达式
(params)->{ statements }
new Thread(()->System.out.println("多线程")).start()
线程状态
Thread t=new Thread();//线程进入创建状态 t.start();//线程进入就绪状态 t.run();//线程进入运行状态,获取到cpu资源也进入到运行状态 t.sleep();//线程进入阻塞状态,调用wait()或同步锁定时也进入阻塞状态,阻塞解除后重新进入就绪状态,等待cpu调度执行 //线程中断或者执行结束就会进入死亡状态,不能再次启动
线程方法
线程休眠
- sleep(指定毫秒数):设置阻塞时间
- sleep()存在异常InterruptedException
- sleep()时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时
- sleep不会释放锁
线程礼让
- yield():让当前线程暂定,进入就绪状态
- 让cpu重新调度,即当前线程有可能继续运行
Join
等待线程执行完毕后其他线程才能执行,期间其他线程阻塞
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
线程的优先级用数字表示1-10
Thread.MIN_PRIORITY=1
Thread.MAX_PRIORITY=10
使用getPriority(),setPriority()来获取和设置优先级
优先级的设置要找start()方法之前
守护线程
线程分别为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
守护线程如后台记录操作日志,监控内存,垃圾回收
线程同步
并发:同一个对象被多个线程同时操作
为了保证数据在被访问时的正确性,加入了锁机制synchronized,当一个线程获得对象的排它锁,其他线程就必须等待,使用后释放锁即可
同步的方法是synchronized关键字,包括两种用法
- synchronized方法:public synchronized void method(){} synchronized方法控制对对象的访问,每个对象都加了一把锁
- synchronized块:synchronized(obj){} obj是同步监视器,就是该对象本身
同步监视器执行过程:
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现没有被锁,将其锁定并访问
死锁
两个或多个进程互相竞争资源就是死锁
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已经获得的资源,在未使用前,不能强行剥夺
- 循环等待条件,若干进行之间形成头尾相接的循环等待资源关系
只要破坏其中任意一个或多个条件就能避免死锁
Lock
显示定义同步锁来实现同步
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
ReentrantLock类实现了Lock,可以显示的加锁和释放锁
Synchronized与Lock对比
- Lock是显式的加锁(需要手动加锁和释放锁),synchronized是隐式锁,出了作用域自动释放锁
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- Lock锁的性能更好
- 优先顺序Lock>同步方法块>同步方法
线程通信
Java提供几个方法来解决线程之间的通信问题
- wait():线程一直等待,直到其他线程通知,会释放锁
- wait(long timeout):直到等待毫秒数
- notify():唤醒一个处于等待状态的线程
- notifyAll():唤醒一个对象上所有调用wait()方法的线程,优先级别高的先调用
这些方法只能在同步方法或者同步代码块中使用,否则会抛出异常
线程池
避免频繁的创建和销毁线程,实现重复利用
线程池接口ExecutorService和工厂类Executors