多线程复习——B站


程序:指令的集合,是静态的
进程:程序执行后的动态行式,是系统分配资源的基本单元
线程:一个进程至少有一个线程,是CPU调度执行的基本单位

多线程:真多线程是指多个CPU同时服务(并行),伪多线程是指一个CPU的多次切换模拟出的效果(并发)

线程创建

三种方法:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

其实:Thread类相当于Runnable接口的静态代理,将Runnable接口的实现类传入,然后生成代理对象,去做一些事情,事情怎么做就在start函数中。

继承Thread类

步骤:继承Thread–重写run–调用start方法
该方式过于简单,懒得写过程了
注意:线程执行顺序,无法确定!

实现Runnable接口

步骤:实现Runable–new Thread(实现类)–调用new出来的对象的start方法
该方式过于简单,懒得写过程了
注意:推荐使用该方法,可以避免单继承,同时方便同一个对象创建多个线程
由此产生一个问题:线程不安全。
线程不安全是因为多个线程操作同一个资源导致的。

线程之间的资源共享
静态变量:由于是类所有的,存在于方法区,所有该类实例都能看到该变量,都可以修改,所以线程不安全
实例变量:如果多线程共享一个实例则等同于静态变量的情况,线程不安全。如果每个线程都有一个类的实例,则实例变量互相隔离,线程安全
局部变量:局部变量是线程安全

实现Callable接口

步骤:★★★

  1. 实现Callable接口,带上返回值
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 开启服务
  5. 提交执行
  6. 获取结果
  7. 关闭服务

参考demo

public class Demo01 implements Callable<Integer> {
    private int num;

    @Override
    public Integer call() throws Exception {
        int x;
        while(6 != (x=(int)(Math.random()*10))){
            num++;
//            System.out.println(Thread.currentThread().getId()+":running,x="+x);
        }
        return num+1;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建实例
        Demo01 d1 = new Demo01();
        Demo01 d2 = new Demo01();
        Demo01 d3 = new Demo01();
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Future<Integer> f1 = executorService.submit(d1);
        Future<Integer> f2 = executorService.submit(d2);
        Future<Integer> f3 = executorService.submit(d3);
        // 获取值
        Integer r1 = f1.get();
        Integer r2 = f2.get();
        Integer r3 = f3.get();
        //关闭线程池
        executorService.shutdown();

        System.out.println("r1="+r1+",r2="+r2+",r3="+r3);
    }
}

Lambda表达式

避免匿名内部类定义过多,属于函数式编程

函数式接口

定义:任何接口,如果只包含一个方法且为抽象方法,那他就是函数式接口(Functional Interface,Java8)

public interface Abc(){
	public abstract void go()
}

对于函数式接口,可以用lambda创建该接口对象

Abc abc = ()->{//实现};
abc.go();

注意:如果带参数:单参数可以不写参数括号。实现语句如果是一句,可以不写花括号。入参类型可以省略。

线程状态

在这里插入图片描述
注意点:就绪和运行状态是可以互相转化的取决于【CPU资源是否释放和获得】,线程一旦结束不能再被启动!
线程常用方法:
在这里插入图片描述

线程停止

注意:不推荐JDK提供的线程停止方法:stop、destory,推荐使用自定义标志位来作为停止的标识
在这里插入图片描述

线程休眠

sleep:

  1. 参数是毫秒数
  2. 会抛出InterruptedException
  3. 结束后恢复就绪状态
  4. 不释放锁!!

线程礼让

让线程暂停,直接回到就绪状态,不会释放锁!!

Join插队

thread.join的含义是当前线程需要等待previousThread线程终止之后才从thread.join返回。简单来说,就是线程没有执行完之前,会一直阻塞在join方法处。
在这里插入图片描述

线程状态

Thread.State

  1. NEW状态:没有启动的线程
  2. RUNNABLE:运行态
  3. BLOCKED:阻塞态
  4. WATING:等待另一个线程执行工作
  5. TIMED_WATING:等待另一个线程执行工作到指定时间
  6. TERMINATED:退出的线程

线程优先级

Java提供给线程调度器来使用,优先调用优先级高的,但是否一定会调用是不确定的。主线程优先级是默认的5
优先级范围:1-10
其中:
Thread.MIN_PRIORITY=1
Thread.MAX_PRIORITY=10
Thread.NORM_PRIORITY=5

使用方法:getPriority() setPriority(int xx)
注意:优先级超过1-10范围会报错,优先级设置不能超过当前线程所属组的最大线程优先级
如果对于性能要求比较高则可以设置线程优先级防止出现性能倒置。

守护线程Daemon

我们创建的线程都是用户线程,除非设置了为守护线程
thread.setDaemon(true)必须要在start前设置

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

线程同步

队列+锁:

  • 一个线程持有锁,会导致其他想要该锁的线程挂起
  • 多线程下,加锁和释放锁会产生性能开销
  • 高优先级线程等待低优先级线程释放锁会导致性能倒置

同步方法:在方法上加sychronized关键字,同时只能有一个线程在该方法内执行:pubic synchronized void a(){}
同步代码块:在代码块上加锁:synchronized(obj){}

一般而言,只在需要修改的部分加锁,防止性能浪费

同步方法

在方法上加锁,锁的是调用该方法的对象,this

同步块

同步块锁定的是一个对象,该对象称为同步监视器,推荐使用共享资源对象作为锁,比如要修改的那个对象。

JUC

java util concurrent 并发包,专门用来做并发编程的
CopyOnWriteArrayList 线程安全的ArrayList,使用volatile和transient来修饰,限制了内存可见性和指令重排

死锁

多个线程各子占有一部分资源(锁)又等待去占有其他资源(锁)时,容易出现,通俗点讲就是,想同时占有多个锁
解决方法:获取对方线程可能占有的锁时,释放掉自己的锁
在这里插入图片描述

Lock锁

JDK5开始,显示定义同步锁对象,使用Lock对象充当,JUC下的包,用于控制多个线程对共享资源进行访问

Lock是一个接口,ReentrantLock是其一个实现类,可重入锁
使用示例:

package com.xiaopi3;

import java.util.concurrent.locks.ReentrantLock;

public class Demo01 {
    public static void main(String[] args) {
        Test t = new Test();
        new Thread(t).start();
        new Thread(t).start();
    }
}

class Test implements Runnable{

    private int num=10;
    /**
     * static 保证new多个对象时,锁只有一份
     * final 保证该锁不会被重新赋值
     */
    private static final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {

        while(true){
        	// 一定要在判断前加锁,要不然会出现同时判断成功的错误 
            lock.lock();
            if(num>=0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getId()+" : "+num--);
            }else{
            	lock.unlock();
                break;
            }
            lock.unlock();
        }
    }
}

注意:一般而言,lock.unlock()在try catch中需要写在finally中,也可以直接try finally中写unlock!(推荐)

Lock和synchronized区别

  1. 前者显示锁后者隐式锁
  2. 前者只能锁代码块,后者还可以锁方法
  3. 前者调度线程消耗少于后者,扩展性高
  4. 建议:Lock>同步代码块>同步方法

线程通信

明确一个模型:生产者和消费者模型,两条线程进行通信
解决方式一:管程法
生产者:产出产品到缓冲区,缓冲区满则停产,不满则生产
消费者:从缓冲区消费,缓冲区空则停止,不空则消费
线程通信的方法:wait和notify
示例:

package com.xiaopi3;

public class Demo {
    public static void main(String[] args) {
        MyPool myPool = new MyPool();
        new Producer(myPool).start();
        new Consumer(myPool).start();
    }
}
class MyPool{
    private int[] pool = new int[10];
    private int num=0;
    public synchronized void push(int x){
        System.out.println("生产");
        if(num==pool.length){
            try {
                System.out.println("仓库满了,等待消费!");
                wait();// 等待并释放MyPool的push上的锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            num++;
            System.out.println("存入,当前存量为:"+num);
            notifyAll();// 通知所有在MyPool的push上等待锁的线程
        }
    }
    public synchronized void pop(){
        System.out.println("消费");
        if(num==0){
            try {
                System.out.println("仓库空了,等待生产!");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            num--;
            System.out.println("取出,当前存量:"+num);
            notifyAll();
        }
    }
}
class Producer extends Thread{
    private MyPool myPool;
    public Producer(MyPool o){
        this.myPool=o;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            myPool.push(i);
        }
    }
}
class Consumer extends Thread{
    private MyPool myPool;
    public Consumer(MyPool o){
        this.myPool=o;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            myPool.pop();
        }
    }
}

解决方式二:信号灯法
生产者和消费者通过标志位来判断是否生产或者消费,原理是(借助第三方中介来作为判断人),信号灯法适用于生产者产出1个消费者消费1个的情况。

这里讲一下思路:第三方负责生产和消费的具体实现,生产者负责调用第三方生产方法,消费者负责调用第三方消费方法,第三方设置了标志位,生产还是消费取决于标志位。

线程池

JDK5提供了线程池相关接口
在这里插入图片描述
Executors:线程池工具类

public class Demo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Test test = new Test();
        executorService.execute(test);
        executorService.execute(test);
        executorService.execute(test);
        executorService.shutdown();
    }
}
class Test implements Runnable{
    @Override
    public void run() {
        System.out.println("123");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值