Java 多线程(一)

参考教程:B站狂神说Java

线程创建方式

1. 实现重写Thread类的run方法或实现Runnable接口的run方法

通过Thread类的源码可知,Thread类实现了Runnable接口,因此本质都是Runnable的run方法。而Thread实现的run方法所调用的为成员变量Runnable target的run方法,该对象可以通过Thread的构造方法传递一个已实现的Runnable接口,如果不传递则默认为null。

public class Thread implements Runnable {
    private Runnable target;
    public void run() {
        if (this.target != null) {
            this.target.run();
        }
    }
}

线程开启代码

new Thread(()->{
    System.out.println(Thread.currentThread().getName() + "启动");
}, "线程A").start();

2. 实现Callable接口

使用Callable接口也可以实现线程创建,但是线程启动需要调用Thread的start方法,因此Callable需要现与Thread产生关联,而FutureTask正好满足了这一点。FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口,因此FutureTask既是Callable的实现类,也是Runnable的实现类,我们只需要把实现好的Callable放进FutureTask中,然后把FutureTask传递给Thread来执行就好。而Callable的底层实际为在Runnable接口的run方法中调用了call方法

class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "启动");
        return Thread.currentThread().getName() + "执行完毕";
    }
}

与Runnable的run方法不同,Callable的call方法可以抛出异常,且有一个返回值,可以通过futureTask.get()获取这个返回值,不过根据call方法的执行时间,futureTask.get()方法为获取线程的返回值可能产生阻塞。

MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask, "线程B").start();
System.out.println(futureTask.get());

需要注意的是,java本身是不能开启线程的,在Thread的start方法中,通过调用本地方法start0方法开启线程,即通过JVM调用底层C/C++的代码开启线程

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

        try {
            this.start0();
            var1 = true;
        } finally {
            try {
                if (!var1) {
                    this.group.threadStartFailed(this);
                }
            } catch (Throwable var8) {}
        }
    }
}
private native void start0();

1. Synchronized锁

Synchronized为一个java关键字,能对一个对象进行加锁解锁,使多线程下代码有同步的效果,作用在方法以及代码块上

public synchronized void test1() {
    System.out.println(Thread.currentThread().getName() + "=========>hello");
}
public void test2() {
    synchronized (this){
        System.out.println(Thread.currentThread().getName() + "=========>hello");
    }
}

2. Lock锁

Lock为java.util.concurrent即JUC包下的一个类,可以通过lock和unlock方法进行加锁以及解锁

Lock lock = new ReentrantLock();
public void test3(){
    lock.lock();
    try {
        System.out.println(Thread.currentThread().getName() + "=========>hello");
    } catch (Exception e){
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

3. Synchronized锁与Lock锁的区别

  • Synchronized为java的关键字;Lock为一个java类

  • Synchronized会自动加锁解锁;Lock需要手动进行加锁解锁,如果解锁后没有解锁可能产生死锁

  • 获得Synchronized锁的线程如果发生阻塞,其他等待这个锁的线程也会进入阻塞一直等待直到能获取锁;

    Lock可以通过tryLock方法尝试获取锁,无论是否能获取都会退出方法继续执行,而非等待阻塞

  • Synchronized锁为不公平锁;Lock锁默认为不公平锁,但可以设置

  • 方法上Synchronized锁的对象为当前对象的引用,普通方法为调用这个方法的对象,静态方法为该类的Class对象;锁代码块的Synchronized锁的为括号中的对象

    Lock锁锁的是Lock本身这个对象的引用

  • Synchronized使用Object类的wait和notify进行阻塞唤醒;Lock使用Condition类的await和signal进行阻塞唤醒

sleep和wait的区别

  • sleep为Thread的方法;wait为Object类的方法
  • sleep可以在任意代码用执行;wait只能带同步代码块中执行,对应的notify也同样
  • sleep必须指定线程睡眠的时间;wait可以指定也可以不指定,同时可以使用notify通知唤醒
  • sleep睡眠过程中,线程不会释放已获得的锁;wait等待阻塞过程中,会释放已获得的锁

生产者消费者问题

假设现有一个类,定义一个int型的number,初始值为0,以及有一个生产方法使number在等于0时会加1,一个消费方法使number大于0时会-1。

现有若干生产者及消费者同时进行生产和消费(多线程),需要保证生产消费有序进行,即线程每次执行number的结果010101以此类推。

1. Synchronized版

public class Test {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者A").start();
    }
}
class Resource{
    private int number = 0;
    public synchronized void produce() throws InterruptedException {
        if (number != 0)
            wait();
        number++;
        System.out.println(Thread.currentThread().getName() + "完成生产,number为:" + number);
        notifyAll();
    }
    public synchronized void consume() throws InterruptedException {
        if (number == 0)
            wait();
        number--;
        System.out.println(Thread.currentThread().getName() + "完成消费,number为:" + number);
        notifyAll();
    }
}
//结果
//生产者A完成生产,number为:1
//消费者A完成消费,number为:0
//生产者A完成生产,number为:1
//消费者A完成消费,number为:0
//生产者A完成生产,number为:1
//消费者A完成消费,number为:0

需要注意,这种实现并不安全,当线程增加时可能会出现虚假唤醒,导致结果无序,比如以下情况

Resource resource = new Resource();
new Thread(()->{
    for (int i = 0; i < 10; i++) {
        try {
            resource.produce();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "生产者A").start();
new Thread(()->{
    for (int i = 0; i < 10; i++) {
        try {
            resource.produce();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "生产者B").start();
new Thread(()->{
    for (int i = 0; i < 10; i++) {
        try {
            resource.consume();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "消费者A").start();
new Thread(()->{
    for (int i = 0; i < 10; i++) {
        try {
            resource.consume();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, "消费者B").start();
运行结果的一部分:
生产者B完成生产,number为:1
生产者A完成生产,number为:2
生产者B完成生产,number为:3
消费者B完成消费,number为:2
消费者B完成消费,number为:1
消费者B完成消费,number为:0
消费者A完成消费,number为:-1
消费者A完成消费,number为:-2
消费者A完成消费,number为:-3

虚假唤醒:在上面有提到,在wait等待阻塞时,线程会释放已获得的锁,因此会有多个线程进入方法,并都处于等待阻塞状态。此时消费者完成消费,并通知唤醒所有线程,此时生产者由于使用if检查number的值,因此所有生产者都被唤醒后,会直接进行生产使number+1,导致生产消费无序。

改进:只需把if判断改为while即可,这样只要有一个生产者进行生产,其他生产者会继续判断需不需要进行生产,若number不为0则重新进入阻塞状态

class Resource{
    private int number = 0;
    public synchronized void produce() throws InterruptedException {
        while (number != 0)
            wait();
        number++;
        System.out.println(Thread.currentThread().getName() + "完成生产,number为:" + number);
        notifyAll();
    }
    public synchronized void consume() throws InterruptedException {
        while (number == 0)
            wait();
        number--;
        System.out.println(Thread.currentThread().getName() + "完成消费,number为:" + number);
        notifyAll();
    }
}

2. Lock版

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void produce() {
    lock.lock();
    try {
        while (number != 0)
            condition.await();
        number++;
        System.out.println(Thread.currentThread().getName() + "完成生产,number为:" + number);
        condition.signalAll();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}
public void consume() {
    lock.lock();
    try {
        while (number == 0)
            condition.await();
        number--;
        System.out.println(Thread.currentThread().getName() + "完成消费,number为:" + number);
        condition.signalAll();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

你可能感兴趣的:(java)