多线程

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 {
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 myCallable = new MyCallable(); // 创建MyCallable对象
// FutureTask是RunableFuture的子类
FutureTask ft = new FutureTask(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 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():通知这个对象上的所有线程
标准范式:
等待方:
获取对象锁
循环中判断是否满足条件,不满足调用wait方法(调用wait会释放锁),满足就执行具体业务代码
等待方:
获取对象锁
修改判断条件,通知等待方(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 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();
    }


}

}
先执行等待线程

先执行通知线程
效果和上面的是一样的,分析参照上面的分析;

转:https://www.jianshu.com/p/1d7ca61d7ccf

posted @ 2021-03-31 10:20  AronJudge  阅读(61)  评论(0编辑  收藏  举报