最近在github上写了几个关于多线程的练习, 项目地址:https://github.com/jndf/multithreading-pratice
需要的朋友可以看看,代码如有错误,请多提出指正意见。
下面说一下最近的一些心得。
这两种方法都是最基础的实现线程的方法,声明线程对象后,通过调用对象的start()方法,来执行线程内部的run()方法。
其中Runnable是由Thread实现的,因此在执行Runnable的任务时,一般将这个对象作为Thread的一个tag来使用。
使用Thread进行实现多线程:
使用Runnable进行实现多线程:
生产者消费者模型是一个较为经典的多线程应用场景,在该场景中,引用到了(wait/notify)等待/通知机制
wait/notify是对象的方法,而不是线程的。体现在资源上,同一时刻,对象的资源只能被同一个线程使用,线程运行完毕后,需要释放资源,并进入wait状态,对象再启动notify方法,唤醒其他线程运行,之后这个线程再次wait,一直循环到线程运行结束。
举个例子:有一群人在一扇门外等着,每个人都想进到门里去,但同时最多只能进去一个人,在这个例子里,门就是对象,人们就是线程;一个人进门去,相当于占用对象资源,其他人在门外等着(wait),当这个人从屋里出来,门开着(notify),就又有人可以进去,而门外的人再次进入wait状态,一直循环到事情结束。
这里提一下notify,和notifyAll,一个是门开着然后只有一个人看到了,另一个是所有人都看到了门开着。
也就是说notify只会随机唤醒一个等待(也可以叫做阻塞)线程,而notify All唤醒了所有等待线程,前者会导致线程效率不高,后者会引起线程线程竞争,这时要做的就是确保线程安全;
当然还有另一种办法,Thread.join,是让线程依次执行的一个方法具体表现为当一个线程执行完毕,才能执行下一个线程。
/**
*写一个生产者消费者模型
* (发生了死锁,情况如下):
* 1x已无产品可消费,等待生产
* 4x已无产品可消费,等待生产
* 2x已无产品可消费,等待生产
* 3x已无产品可消费,等待生产
*
*/
public class Demo3 implements Runnable{
private String name;
private List list = new ArrayList();
private final int size = 10;
public void produce(int num) throws Exception {
while (true) {
synchronized (list) {
while (list.size() + num > size) {
System.out.println(Thread.currentThread().getName()+"生产过剩,等待消费");
list.wait();
}
System.out.println(Thread.currentThread().getName()+"正在生产");
for (int i = 0; i < num; i++) {
list.add("hello, world");
}
list.notifyAll();
}
Thread.sleep(1000);
}
}
public void consume() throws Exception {
while (true) {
synchronized (list) {
while (list.size() == 0) {
System.out.println(Thread.currentThread().getName()+"已无产品可消费,等待生产");
list.wait();
}
System.out.println(Thread.currentThread().getName()+"正在消费");
list.remove(0);
list.notifyAll();
}
Thread.sleep(1000);
}
}
public void setName(String name) {
this.name = name;
}
public void run() {
try {
while("producer".equals(name)){
produce(1);
}
while("consumer".equals(name)){
consume();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Demo3 myThread = new Demo3();
myThread.setName("producer");
Thread t1 = new Thread(myThread,"1x");
Thread t4 = new Thread(myThread,"4x");
t1.start();
t4.start();
Thread.sleep(1);
myThread.setName("consumer");
Thread t2 = new Thread(myThread,"2x");
Thread t3 = new Thread(myThread,"3x");
t2.start();
t3.start();
}
catch (Exception e) {
}
}
}
这个框架主要是可以创建一个线程池,也不需要写原生的线程对象来进行操作,比较方便。
使用方式见代码注释
/**
* 写一个Executor 多线程框架
*
*/
public class Demo4{
/**
* Executor包括四种创建线程池的对象
* Java通过Executors提供四种线程池,分别为:
* newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
* newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
* newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
* newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个定长的线程池 例子是10个线程的
ExecutorService executorService = Executors.newFixedThreadPool(10);
//1·框架执行execute方法,来执行一个线程类,但是没有返回值
//executorService.execute(new Demo2.Runner1());
//2·使用submit执行线程,可以得到返回值
//Future submit = executorService.submit(new Call1());
//System.out.println(submit.get());
//3·CompletionService 可以将已完成任务与未完成的任务分离出来 ExecutorCompletionService此类将安排那些完成时提交的任务,把它们放置在可使用 take 访问的队列上
CompletionService completionService = new ExecutorCompletionService(executorService);
completionService.submit(new Call1());
Future future =completionService.take();
System.out.println(future.get());
//4·submit一个runnable和一个callable的区别
//主要是靠futureTask区别,具体在下面
//可以使用lambda表达式进行代码优化
}
static class Call1 implements Callable {
public String call() {
return "返回callable线程";
}
}
static class Run1 implements Runnable {
public void run() {
//return "返回callable线程";
}
}
}
它实现了Runnable和Future,可以用来执行Runnable和Callable,都能获得返回值:
Callable当然能返回,这里说一下void泛型的Runnable,具体返回方法,我把源码放出来:
上面是Callable下面是Runnable,而result是作为参数输入的,这里我们看到调用了
Executors.callable(runnable, result)
于是明白了,Runnable返回值被执行并有返回值是因为,返回值是一个预期结果,我们去执行这个线程并输入预期结果,执行成功后,这个result会被返回回来。
FutureTask还适用于执行多任务计算的使用场景,在高并发环境下确保任务只执行一次,以及通过cancel方法来取消掉线程等功能。