多线程

作者: leap_ | 来源:发表于2019-12-24 18:55 被阅读0次

Java开启子线程的方式

  • 继承Thread类
public class MyThread extends Thread{
    public void run() {
        super.run();
        System.out.println(Thread.currentThread().getName()+"Thread方法实现多线程");
    }

MyThread thread = new MyThread();
thread.start();
  • 实现Runable接口(避免单继承的局限性)
public class MyThread implements Runnable{
    public void run() {
        
        System.out.println(Thread.currentThread().getName()+"Thread方法实现多线程");
    }
}
Thread thread = new Thread(new MyThread());
thread.start();
  • 实现Callable
public class MyCallable implements Callable<String> {
    private int count = 20;
    @Override
    public String call() throws Exception {
        for (int i = count; i > 0; i--) {
            System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
        }
        return "sale out";
    } 
Callable<String> myCallable = new MyCallable();    // 创建MyCallable对象
// FutureTask是RunableFuture的子类
FutureTask<Integer> ft = new FutureTask<String>(myCallable); //使用FutureTask来包装MyCallable对象
Thread thread = new Thread(ft);
thread.start();
String s = ft.get();  // 获取call的返回值
注意:Thread才是真正的线程类,Runable和Callable是对任务task的封装
Callable:

同Runable接口,这个接口描述的任务有返回值

public interface Callable<V> {
    V call() throws Exception;    //  类似Runable的run方法(run方法没有返回值)
}
Future:

是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

  • get():等待线程执行完毕,返回线程的结果
  • cancel():停止线程
  • isDown():判断线程是否结束
  • isCancel():判断线程是否取消
FutureTask:
实现了RunableFuture接口(RunableFuture继承了Runable,Future接口),这个类既可以作为Runable任务被线程类Thread执行,又可以当作Future拿到Callable任务的返回值;

线程的终止

  • suspend():暂停
  • resume():继续
  • stop():终止
    上面三个方法都已经过时了,这些方法的调用线程不会释放持有的资源,容易引发死锁
协作式关闭线程
  • interrupt():调用这个方法会将线程的中断标志设置为true,表示告诉这个线程你该中断了,但是当前线程并一定会理会这个中断操作
  • isInterrupted():用于判断当前的线程是否执行过interrupt(),即判断中断标志位是否为true;
  • interrupted():这是一个static方法,也用来判断中断标志位,不过会多一步操作,将标志位设置为false;

生命周期&线程方法

start()&run():
  • start():
 public synchronized void start() {
        if (this.threadStatus != 0) {
            throw new IllegalThreadStateException();
        } else {
            this.group.add(this);
            boolean started = false;

            try {
                this.start0();          //  调用native方法开启子线程
                started = true;
            } finally {
                try {
                    if (!started) {
                        this.group.threadStartFailed(this);
                    }
                } catch (Throwable var8) {
                }
            }
        }
    }

private native void start0();
  • run():
private Runnable target;

public void run() {
        if (this.target != null) {
            this.target.run();        //  调用Runable(任务)的run方法(线程的逻辑代码)
        }
    }

run是线程类的逻辑方法,start是开启线程的一个native方法

  • sleep():进入阻塞状态

  • yield():让出当前线程的cpu调度,使cpu重新调度(可能还会调度到当前线程)

  • join():插队,让插入的线程执行完再继续执行当前线程(可以迭代插入,B插入A,C可以插入B,D可以插入C)

  • wait()/notify()/notifyAll() :下文重点讲解

线程共享与协作

synchronized关键字:

加锁,确保多个线程在同一时刻只能由一个线程处于这个方法或代码块中;

对象锁:用于对象实例方法或者对象实例上
  • synchronized方法
// 共享资源
static int ticketNum = 0;

private synchronized void getTicketSync() {
        ticketNum--;
        System.out.println(Thread.currentThread().getName() + "   余票:  " + ticketNum);
    }

这个方法加了synchronized关键字后,对ticketNum的--操作同一时刻只能由一个线程执行;

  • synchronized代码块
private void getTicketSync() {
        synchronized (this) {
            ticketNum--;
            System.out.println(Thread.currentThread().getName() + "   余票:  " + ticketNum);
        }
    }
类锁:用于static方法(锁:类的class对象)
private static synchronized void getTicketSync() {
            ticketNum--;
            System.out.println(Thread.currentThread().getName() + "   余票:  " + ticketNum);
    }

等待通知机制:

是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作;

  • wait():调用该方法的进程进入阻塞状态,并且该进程释放持有的锁
  • wait(long):经过一段时间后如果没有唤醒就超时返回
  • notify():通知一个在该对象上等待的线程,执行wait()下面的代码,要求是该线程必须获得锁;如果没有获得锁就继续阻塞;
  • notifyAll():通知这个对象上的所有线程
标准范式:
  • 等待方:
  1. 获取对象锁
  2. 循环中判断是否满足条件,不满足调用wait方法(调用wait会释放锁),满足就执行具体业务代码
  • 等待方:
  1. 获取对象锁
  2. 修改判断条件,通知等待方(notify/notifyAll)

创建一个快递类

public class Express {

    private int km;

    public Express(int km) {
        this.km = km;
        
    }
    
    public synchronized void changeKm(){
        // 获取锁
        System.out.println("通知线程:----获取对象锁");

        // 改变条件,通知
        System.out.println("通知线程:----改变距离超过100,通知等待线程");
        this.km = 101;
        notifyAll();

        // 释放锁
        System.out.println("通知线程:----释放对象锁");
    }
    
    public synchronized void waitKm(){
        // 获取锁
        System.out.println("等待线程:----获取对象锁");

        // 循环,不满足条件调用wait方法
        while (this.km<100){
            try {
                System.out.println("等待线程:----此时距离小于100,当前线程调用wait等待,释放对象锁");
                wait();
                System.out.println("等待线程:----当前线程从wait返回并且获得锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 满足条件,执行具体业务逻辑
        System.out.println("等待线程:----距离大于100(等待方等待到通知信号后),执行具体的业务逻辑");

        // 释放锁
        System.out.println("等待线程:----释放对象锁");
    }
}

创建两个线程,wait线程,notify线程

public class Main {

    static Express express = new Express(0);

    static class WaitThread extends Thread{
        @Override
        public void run() {
            express.waitKm();
        }
    }
    static class NotifyThread extends Thread{
        @Override
        public void run() {
            express.changeKm();
        }
    }
    
    public static void main(String[] args) {
        new WaitThread().start();
        new NotifyThread().start();
    }
}

先执行wait线程,在执行notiify线程,运行结果:



首先等待线程先获得锁,循环判断当前的距离是否小于100,如果小于100就wait,释放锁,然后通知线程获取对象锁,改变距离超过100,notify通知,然后释放对象锁,等待线程收到notify通知并且获得对象锁从wait方法返回,执行具体的业务逻辑,最后释放锁;

如果是先执行notify线程,再执行wait线程:
public static void main(String[] args) {
        new NotifyThread().start();
        new WaitThread().start();
    }

通知线程获得锁,改变距离超过100,释放锁;等待线程获得锁,判断直接满足条件距离大于100,不需要wait操作,直接执行业务代码;

ThreadLocal:

ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

ThreadLocal往往用来实现变量在线程之间的隔离

创建一个ThreadLocal对象,指定存储的数据为integer,初始值为100

static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(){
        @Override
        protected Integer initialValue() {
            return 100;
        }
    };

创建一个add线程(加法)和sub线程(减法)

static class ADDThread extends Thread{
        @Override
        public void run() {
            Integer integer = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"----初始值----"+integer);

            integer +=100;
            System.out.println(Thread.currentThread().getName()+"----线程 操作int+100 ----");

            threadLocal.set(integer);
            System.out.println(Thread.currentThread().getName()+"----调用set存放新的integer到threadlocal,保存修改");

            System.out.println(Thread.currentThread().getName()+"----当前线程的副本值"+threadLocal.get());

            new SubThread().start();
        }
    }

    static class SubThread extends Thread{
        @Override
        public void run() {
            Integer integer = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"----初始值----"+integer);
        }
    }

启动addThread

 public static void main(String[] args) {
        new ADDThread().start();
    }

我们在add线程中操作了这个int,将他加了100,在add线程中打印出来时200,启动sub线程打印threadlocal中存放的这个值发现还是100;

ThreadLocal原理:

ThreadLocal内部有一个内部类ThreadLocalMap;ThreadLocalMap的内部有一个Entry内部类,ThreadLocalMap内部类维护了一个Entry数组,Entry(类似Map.Entry)key是ThreadLocal,value是Object;每一个线程都有一个ThreadLocalMap的实例,这个ThreadLocalMap内部又有一个Entry数组,将threadLocal作为key获取每个线程中独立的副本,因为threadLocal可以有多个,所以Entry以数组的形式存放;

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

显式锁 Lock

Lock接口:
lock接口的方法
  • lock(): 获取锁
  • lockInterruptibly():可中断的获取锁
  • unlock(): 释放锁
  • tryLock(): 尝试获取锁
  • newCondition(): 用于lock的通知等待机制,下面会详细介绍
显示锁的范式
  • 先获取锁lock.lock();
  • 处理业务后,必须在finally中lock.unlock();

必须在finally中释放锁,因为如果在业务代码中抛出异常,这个锁就永远无法释放了

Lock lock = new ReentrantLock(); // Lock接口的实现类(可重入锁)
    
    public void lockTest(){
        lock.lock(); // 获取锁
        try {
            // 业务代码
            System.out.println("拿到锁后,执行相应的业务代码");
        }finally {
            lock.unlock(); // 释放锁,必须在finally中释放锁
        }
    }
可重入锁:

ReentrantLock是一个可重入锁,执行线程在调用lock.lock()获取了这个锁以后,如果这个方法是一个递归方法,会继续调用lock.lock(),这时可重入锁就可以再次被这个线程获得,即同一线程可以多次获得这个锁,在synchronized内部jdk也加入了可重入机制

公平/非公平锁:
  • 公平锁:等待时间越长的线程先获得锁
  • 非公平锁: 后创建的线程先获得锁(等待时间短的先获得锁)
public ReentrantLock(boolean fair) {
        this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
    }

ReentrantLock提供了一个构造方法,创建的锁是否公平;

非公平锁为什么比公平锁效率高?

因为线程在切换的时候会设计到操作系统的上下文切换,这是一个消耗资源的操作,现在有AB两个线程,A获取了锁,B正在等待(处于阻塞状态),如果这时A释放了锁,创建了一个新线程C,根据非公平锁的概念,这个锁应该给C线程,C线程省去了上下文切换的步骤,所以非公平锁的效率比公平锁高;

读写锁:
ReadWriteLock接口

当读写锁被读线程持有时,其他的读线程可以共享这个资源,写线程不可以;当这个锁被写线程持有时,其他所有的读线程,写线程都不可以共享;

读写锁的使用
ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // ReadWriteLock的实现类,可重入读写锁
    Lock readLock = readWriteLock.readLock();
    Lock writeLock = readWriteLock.writeLock();

    public void read(){
        readLock.lock();
        try {
            // 业务代码
        }finally {
            readLock.unlock();
        }
    }

    public void write(){
        writeLock.lock();
        try {
            // 业务代码
        }finally {
            writeLock.unlock();
        }
    }
Condition实现显示锁Lock的等待通知机制:

将上面等待通知的例子做如下改写,其他代码都不变

public class Express {

    private int km;

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public Express(int km) {
        this.km = km;

    }

    public  void changeKm(){
        // 获取锁
        System.out.println("通知线程:----获取对象锁");
        lock.lock();

        // 改变条件,通知
        try {
            System.out.println("通知线程:----改变距离超过100,通知等待线程");
            this.km = 101;
            condition.signalAll();
        }finally {
            // 释放锁
            System.out.println("通知线程:----释放对象锁");
            lock.unlock();
        }

    }

    public  void waitKm(){
        // 获取锁
        lock.lock();
        System.out.println("等待线程:----获取对象锁");

        // 循环,不满足条件调用wait方法
        try {
            while (this.km<100){
                try {
                    System.out.println("等待线程:----此时距离小于100,当前线程调用wait等待,释放对象锁");
                    condition.await();
                    System.out.println("等待线程:----当前线程从wait返回并且获得锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 满足条件,执行具体业务逻辑
            System.out.println("等待线程:----距离大于100(等待方等待到通知信号后),执行具体的业务逻辑");
        }finally {
            // 释放锁
            System.out.println("等待线程:----释放对象锁");
            lock.unlock();
        }


    }
}
先执行等待线程
先执行通知线程

效果和上面的是一样的,分析参照上面的分析;

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

    本文标题:多线程

    本文链接:https://www.haomeiwen.com/subject/jlxfoctx.html