Java多线程JAVA

多线程

2019-12-24  本文已影响0人  leap_

Java开启子线程的方式

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

MyThread thread = new MyThread();
thread.start();
public class MyThread implements Runnable{
    public void run() {
        
        System.out.println(Thread.currentThread().getName()+"Thread方法实现多线程");
    }
}
Thread thread = new Thread(new MyThread());
thread.start();
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方法获取执行结果,该方法会阻塞直到任务返回结果。

FutureTask:
实现了RunableFuture接口(RunableFuture继承了Runable,Future接口),这个类既可以作为Runable任务被线程类Thread执行,又可以当作Future拿到Callable任务的返回值;

线程的终止

协作式关闭线程

生命周期&线程方法

start()&run():
 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();
private Runnable target;

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

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

线程共享与协作

synchronized关键字:

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

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

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

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

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()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作;

标准范式:
  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接口的方法
显示锁的范式

必须在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();
        }


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

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

上一篇下一篇

猜你喜欢

热点阅读