参考教程:B站狂神说Java
通过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();
使用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();
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");
}
}
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();
}
}
Synchronized为java的关键字;Lock为一个java类
Synchronized会自动加锁解锁;Lock需要手动进行加锁解锁,如果解锁后没有解锁可能产生死锁
获得Synchronized锁的线程如果发生阻塞,其他等待这个锁的线程也会进入阻塞一直等待直到能获取锁;
Lock可以通过tryLock方法尝试获取锁,无论是否能获取都会退出方法继续执行,而非等待阻塞
Synchronized锁为不公平锁;Lock锁默认为不公平锁,但可以设置
方法上Synchronized锁的对象为当前对象的引用,普通方法为调用这个方法的对象,静态方法为该类的Class对象;锁代码块的Synchronized锁的为括号中的对象
Lock锁锁的是Lock本身这个对象的引用
Synchronized使用Object类的wait和notify进行阻塞唤醒;Lock使用Condition类的await和signal进行阻塞唤醒
假设现有一个类,定义一个int型的number,初始值为0,以及有一个生产方法使number在等于0时会加1,一个消费方法使number大于0时会-1。
现有若干生产者及消费者同时进行生产和消费(多线程),需要保证生产消费有序进行,即线程每次执行number的结果010101以此类推。
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();
}
}
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();
}
}