创建线程的5种方法

1、继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
public class createthread {
static class Mythread extends Thread{
@Override
public void run() {
System.out.println("线程创建的第一种方法");
}
}

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

2、实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11

public class createthread implements Runnable{
@Override
public void run() {
System.out.println("线程创建的第二种方法");
}

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

3、使用lambda表达式(匿名线程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class createthread{
public static void main(String[] args) {
new Thread(()->{
System.out.println("线程创建的第三种方法");
}).start();
}
}

public class createthread{
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我的线程执行了5");
}
}).start();
}
}

4、通过Callable接口和Future接口创建线程

​ 通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程的执行体,那么,是否可以直接把任意方法都包装成线程的执行体呢?从JAVA5开始,JAVA提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法的功能的强大体现在:

  • a.call()方法可以有返回值;
  • b.call()方法可以声明抛出异常;

从这里可以看出,完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是call()方法。但问题是:Callable接口是JAVA新增的接口,而且它不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。还有一个原因就是:call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的,所以这里涉及获取call()方法返回值的问题。

于是,JAVA5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。

通过Callable和Future接口创建并启动线程步骤如下:

  • a.创建Callable接口实现类,并实现call()方法,该方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例;
  • b.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
  • c.使用FutureTask对象作为Thread对象的target创建并启动新线程;
  • d.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class createthread{
static class MyCall implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("线程创建的第四种方法");
return "线程创建的第四种方法";
}
}
public static void main(String[] args) {
Thread thread = new Thread(new FutureTask<String>(new MyCall()));
thread.start();
}
}

5、使用线程池

通过Executor 的工具类可以创建三种类型的普通线程池

a.Executors.newFixedThreadPool(5);固定大小的线程池

  • 使用于为了满足资源管理需求而需要限制当前线程数量的场合。使用于负载比较重的服务器。

b.Executors.newSingleThreadExecutor();单线程池

  • 需要保证顺序执行各个任务的场景

c.Executors.newCachedThreadPool();缓存线程池

  • 当提交任务速度高于线程池中任务处理速度时,缓存线程池会不断的创建线程。适用于提交短期的异步小程序,以及负载较轻的服务器
1
2
3
4
5
6
7
8
9
public class createthread{
private static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
executorService.execute(()->{
System.out.println("使用线程池创建线程");
});
executorService.shutdown();
}
}

Java线程间通信

在Java的Object类中提供了waitnotifynotifyAll等方法,这些方法可以实现线程间的通信,因为Object类是所有类的基类,因此所有的对象都具有线程间通信的方法在这里插入图片描述

void wait():调用一个对象的wait方法,会导致当前持有该对象的锁的线程等待,直到该对象的另一个持有锁的线程调用notify或者notifyAll唤醒。

void wait(long timeout):除了和wait相似,还具有超过定时的超时时间,时间到后或自动唤醒。

void wait(long timeout,int nanou):与 void wait(long timeout) 相同,不过提供了纳秒级别的更精确的超时控制。

void notify():调用一个对象的notify方法,会导致当前持有该锁的所有线程中的随机某一个线程被唤醒

void notifyAll():调用一个对象的notifyAll方法,会导致当前持有该锁的所有线程被唤醒

线程间通信案例

​ 通信是在不同线程间的通信,一个线程处于wait状态阻塞等待被唤醒,另一个线程通过notify或者notifyAll唤醒,当前的唤醒操作必须是作用与同一个对象,注意在进行唤醒和阻塞时必须要加锁的,加锁需要使用synchronized关键字。

WaitDemo类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WaitDemo extends Thread{
private Object obj;

public WaitDemo(Object obj) {
this.obj = obj;
}

@Override
public void run() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "WaitDemo执行开始~~~");
try {
obj.wait(); //调用wait方法阻塞线程执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "WaitDemo执行结束~~~");
}
}
}

NotifyDemo类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NotifyDemo extends Thread{
private Object obj;

public NotifyDemo(Object obj) {
this.obj = obj;
}

@Override
public void run() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "NotifyDemo执行开始~~~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
obj.notify(); //调用notify方法唤醒阻塞线程
System.out.println(Thread.currentThread().getName() + "NotifyDemo执行结束~~~");
}
}
}

TestWaitAndNotify测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestWaitAndNotify {
public static void main(String[] args) {
Object object = new Object();
WaitDemo waitDemo = new WaitDemo(object);
NotifyDemo notifyDemo = new NotifyDemo(object);

waitDemo.setName("WaitDemo线程");
notifyDemo.setName("NotifyDemo线程");

waitDemo.start();
notifyDemo.start();
}
}

执行结果:

1
2
3
4
WaitDemo线程WaitDemo执行开始~~~
NotifyDemo线程NotifyDemo执行开始~~~
NotifyDemo线程NotifyDemo执行结束~~~
WaitDemo线程WaitDemo执行结束~~~

使用注意点:

  • 调用notifywait方法必须是作用于同一个对象,如果不是同一个对象则无法完成通信。
  • 对于waitnotifynotifyAll的调用,必须在该对象的同步方法或者代码块中,锁作用的对象和wait等方法必须是作用于同一个对象。
  • wait方法在调用后进入阻塞之前会释放锁,而sleepjoin是不会释放锁的。
  • 线程状态转换时,当wait被唤醒或者超时时,线程并不是直接进入就绪状态,而是先进入阻塞状态,抢锁成功后才能进入到可运行状态。

生产者消费者模型

​ 生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个进程共享一个公共的固定大小的缓冲区。 ​ 其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。

​ 问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费者从缓冲区中取走了一个或者多个数据后再去唤醒它。

​ 同样地,当缓冲区已经空了,而消费者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤醒它。

再具体一点:

a.生产者生产数据到缓冲区中,消费者从缓冲区中取数据。

  1. 如果缓冲区已经满了,则生产者线程阻塞。

  2. 如果缓冲区为空,那么消费者线程阻塞。

​ 上述过程的描述应该已经体现出生产者和消费者之间的线程通信的流程,生产者一旦将队列生成满了之后就要控制线程停止生产,直到消费者将队列中消费一个之后就可以通知生产者继续生产新的元素,当消费者线程将队列中的元素全部取出之后消费者线程就需要停止消费元素,直到生产者线程向队列中添加一个元素之后可以通知消费者线程继续消费元素。

要求:编写一个生产者、消费者模型,给定要求:一个生产者、一个消费者、三个仓库

生产者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Producer extends Thread{
private LinkedList<Integer> cap;//共享仓库
private Random random = new Random();

public Producer(LinkedList<Integer> cap) {
this.cap = cap;
}

@Override
public void run() {
while (true) {
synchronized (cap){
if (cap.size() == 3) {//缓冲区满 生产者进行阻塞
try {
cap.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

//生产产品
int i = random.nextInt(1000);
System.out.println("生产者生产了" + i);
cap.add(i);

//通知消费者消费产品
cap.notify();
}
}
}
}

消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Consumer extends Thread{
private LinkedList<Integer> cap;

public Consumer(LinkedList<Integer> cap) {
this.cap = cap;
}

@Override
public void run() {
while (true) {
synchronized (cap) {
if (cap.size() == 0) { //如果缓冲区为0,消费者阻塞
try {
cap.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

//消费者消费产品
Integer i = cap.remove();
System.out.println("消费者消费了" + i);

//通知生产者生产
cap.notify();
}
}
}
}

测试类

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
LinkedList<Integer> cap = new LinkedList<>();
Producer producer = new Producer(cap);
Consumer consumer = new Consumer(cap);

producer.start();
consumer.start();
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
生产者生产了458
生产者生产了923
生产者生产了373
消费者消费了458
消费者消费了923
消费者消费了373
生产者生产了850
生产者生产了284
生产者生产了37
消费者消费了850
消费者消费了284
消费者消费了37