一、多线程
一、概述
1、在程序执行时,即使没有自己创建线程,后台会有主线程、gc线程
2、在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能人为干预。
3、对同一份资源进行操作时,会存在资源抢夺问题,需要加入并发控制。
4、线程会带来额外开销,如CPU调度时间,并发控制开销。
二、线程创建:Thread、Runnable、Callable
Thread:1、继承Thread类 2、重写run方法 3、调用start方法
Runnable接口: 1、实现Runnable接口 2、重写run方法,编写线程执行体 3、调用线程对象,调用start方法启动线程。
三、λ表达式
避免匿名内部类过多,让代码变得很简洁。其实质属于函数式编程的概念
函数式接口:任何接口如果只包含唯一一个抽象方法,那么就是一个函数式接口
对于函数式接口,可以通过λ表达式来创建该接口的对象。
//1 外部类
//2 静态内部类:一个类如果只用一次为了提升性能[避免不使用也编译], 可以写成内部类
//3 局部内部类
//4 匿名内部类:必须借助接口或者父类来写
//5 λ表达式
public class TestLamuda {
//2、静态内部类
static class Like2 implements Ilike{
@Override
public void lamuda() {
System.out.println("静态内部类的实现!");
}
}
public static void main(String[] args) {
Ilike like =new Like();
like.lamuda();
like =new Like2();
like.lamuda();
//3、局部内部类
class Like3 implements Ilike{
@Override
public void lamuda() {
System.out.println("局部内部类的实现!");
}
}
like =new Like3();
like.lamuda();
//4、匿名内部类 没有类的名称,必须借助类的接口或者父类
like =new Ilike() {//接口不能实例化,必须带上实现体
@Override
public void lamuda() {
System.out.println("匿名内部类的实现!");
}
};
like.lamuda();
//5、λ表达式的实现
like =()->{
System.out.println("λ表达式的实现!");
};
like.lamuda();
}
}
//定义一个函数式接口
interface Ilike{
void lamuda();
}
//1、实现类
class Like implements Ilike{
@Override
public void lamuda() {
System.out.println("lamuda实现类的调用!");
}
}
四、线程的5大状态
1、停止线程
1、不推荐使用JDK的方法
2、推荐线程自己停下来
3、建议使用一个标志位进行终止变量,flag=false时终止线程运行
public class TestStop implements Runnable{
private boolean flag=true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("线程正在运行,次数为"+i++);
}
}
public void stop(){//在run外部设置一个stop方法
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop=new TestStop();
new Thread(testStop).start();//创建一个多线程对象
for (int i = 0; i < 1000; i++) {
System.out.println("main方法正在运行"+i);
if(i==900){
testStop.stop();
System.out.println("线程停止了");
}
}
}
}
2、程序休眠
1、模拟网络延时:放大问题的发生性。避免由于处理过快看不到问题的发生
2、sleep存在异常,抛出即可
3、时间达到后进入就绪状态
4、sleep可以模拟网络延时
//模拟倒计时:
public class TestSleep{
public static void main(String[] args) {
try {
tenD();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenD() throws InterruptedException {
int nums=10;
while(true){
System.out.println("倒计时"+nums--+"秒钟");
Thread.sleep(1000);
if(nums<=0){
break;
}
}
}
}
3、线程礼让yield
1、让正在执行的线程暂停,但不阻塞
2、将线程从运行状态转为就绪状态
3、让CPU重新调度,礼让不一定成功
4、线程强制执行Join
合并线程,等待此线程完成后,再执行其他线程,其他线程阻塞
5、线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
Thread.MIN_PRIORITY=1;
Thread.MAX_PRIORITY=10;
Thread.NORM_PRIORITY=5;
优先级设置在start之前。
优先级低只是概率低。
6、守护线程(daemon)
1、线程分为用户线程和守护线程
2、虚拟机必须确保用户线程执行完毕(main函数)
3、虚拟机不必等待守护线程执行完毕(gc)
4、如:后天记录操作日志、监控内存、垃圾回收等待…
五、线程同步
多个线程操作同一个资源,线程同步其实就是一种等待机制,多个需要同时访问次对象的线程进入这个对象的等待池形成队列。
并发:同一个对象被多个线程同时操作
队列和锁:是线程同步的形成条件
锁:为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized)。
存在的问题:
1、一个线程持有锁会导致其他所有需要此锁的线程挂起。
2、加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
3、如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
同步方法
1、针对方法提出一套机制,synchronized关键字,包括synchronized方法和synchronized块。
2、synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞,方法一旦执行就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:如果将一个方法声明为synchronized会大大地影响效率
synchronized或synchronized块要锁增删查改的对象。
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能正常运行,而等待两个或者多个线程都在等待对方释放资源,都停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”时,发生死锁问题
产生死锁的条件
1、互斥条件:一个资源只能被一个资源使用
2、请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
3、不剥夺条件:进程已经获得的资源,在未使用完前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源
Lock锁
synchronized:隐式锁
lock:显示锁
class{
private final ReentrantLock lock=new ReentrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码
}
finally{
lock.unlock();//如果同步代码有异常,unlock写入finally中
}
}
}
六、线程同步
既要实现线程之间的同步,又要实现线程之间的通信
生产者消费者模式
两个线程共享同一个资源,并且二者之间互为条件,相互依赖
Java提供的解决线程之间的通信问题的方法
1、并发协作模型 “生产者/消费者模式”—管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区中取出
public class TestPC {
public static void main(String[] args) {
Container container=new Container();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
Container container;
public Productor(Container container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了第"+i+"只鸡");
container.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
Container container;
public Consumer(Container container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+container.pop().id+"只鸡");
}
}
}
//产品:鸡
class Chicken{
int id;
public Chicken(int id) {//构造方法
this.id = id;
}
}
//缓冲区
class Container{
//需要容器大小
Chicken[] chickens=new Chicken[10];
int cnt=0;//计数器
//push方法,生产者放入产品
public synchronized void push(Chicken chicken){
if(cnt==chickens.length){
//容器满了,通知消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有满,通知生产者生产
chickens[cnt]=chicken;//将鸡丢入
cnt++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
if(cnt==0){
//容器空了,通知消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cnt--;
Chicken chicken=chickens[cnt];
this.notifyAll();
return chicken;//返回吃了哪只鸡
}
}