并发解决的问题
1.速度:避免了单线程阻塞
2.完整的仿真涉及大量的任务,需要使用协作多线程
并发存在的问题
共享内存和I/O资源,编写多线程要协调多线程对资源的使用,以使得资源不会被多个任务同时使用。
线程驱动任务
线程可以驱动任务,所以需要一种描述任务的方式,这可以由runnable接口来提供。
- thread调用方式 new Thread(new Runnable())
- 线程池调用方式:executorService .execute(Runnable runnable)
从任务中产生返回值
如果你希望在任务完成时能够返回一个值,那需要使用callable而不是runnable,返回类型是callable的泛型参数类型。
- thread调用方式:new FutureTask<T>(new Callable()) new Thread(FutureTask futureTask);
- 线程池调用的方式:executorService.submit(Callable callable)
thread.sleep方法和thread.yield()方法区别
Sleep方法,当前线程进入阻塞状态,但是不会释放锁。Yield方法,是进入到就绪状态,也不会释放锁,调用yield方法也只是建议相同优先级或更高的线程可以运行,因为调用yield方法的本线程处于就绪状态,低于本线程优先级的线程不会得到执行。
生产者消费者模式通用模板
* 1.锁{
* 2. while 条件不满足
* 释放锁 , 等待条件改变
* end while
* 3. 生产或者消费
* 4. 唤醒其他生产者或消费者线程
* 5. 释放锁
* }
线程的加入 Thread.join()方法
如某个线程执行了 t.join(),那么该线程会挂起,直到线程t执行结束才恢复。
此方法可以实现经典的3线程顺序输出问题。
对join方法的调用可以中断,做法是在调用线程上调用interrupt()方法。
线程池 Executor可以异常处理 UncaughtExceptionHandler
synchronized和reentrantLock的区别
区别点 | syschronized | reentrantlock |
锁的类型 | 非公平、可重入 | 可公平、可重入 |
锁的获取 | 获取不到一直等待 | 可以尝试获取 |
锁的释放 | 结束自动释放 | 必须在finally里面释放锁 |
锁的状态 | 无法判断 | 可判断 |
volatile的作用
保证了数据的可见性,每个线程都有自己的工作缓存,如果用volatile修饰,那么一旦数据发生改变,就会通知各个线程的工作缓存失效。
ThreadLocal使用
线程的本地存储,每一个线程都有一份变量的复制,也就避免了资源共享的问题,本质是空间换时间。代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadLocalHolder {
private static Logger LOGGER = LoggerFactory.getLogger(ThreadLocalHolder.class);
private static ThreadLocal<Integer> data = new ThreadLocal<Integer>();
public static void add() {
if (data.get() == null) {
data.set(0);
} else {
data.set(data.get() + 1);
}
LOGGER.info("Thread" + Thread.currentThread() + ":data=" + get());
}
public static Integer get() {
return data.get();
}
}
public class TaskRunnable implements Runnable {
private int count = 0;
public void run() {
while (count < 10) {
ThreadLocalHolder.add();
count++;
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new TaskRunnable());
executorService.execute(new TaskRunnable());
executorService.shutdown();
}
}
执行的结果如下:
INFO [pool-1-thread-1] - ThreadThread[pool-1-thread-1,5,main]:data=0
INFO [pool-1-thread-1] - ThreadThread[pool-1-thread-1,5,main]:data=1
INFO [pool-1-thread-2] - ThreadThread[pool-1-thread-2,5,main]:data=0
INFO [pool-1-thread-1] - ThreadThread[pool-1-thread-1,5,main]:data=2
INFO [pool-1-thread-2] - ThreadThread[pool-1-thread-2,5,main]:data=1
INFO [pool-1-thread-2] - ThreadThread[pool-1-thread-2,5,main]:data=2
线程的状态
如何中断线程
也许你想过使用标志位 iscanceled,通过控制这个字段,使得线程退出,但是线程退出还需要清理一些必要得资源,这就会引导你使用异常(这是滑向异常得不恰当用法,控制程序执行流程---出自《Thing in java》)
Thread.interrupt()提供了中断线程,这个方法将设置线程的中断状态,而Thread.interrupted()方法将中断状态复位。
Thread.interrupted()方法提供了离开run()方法而不抛出异常的第二种方法。
线程池中断线程的方法:Executor.shutdownNow(),那么它将发送一个interrupt()调用给它启动的每一个线程。那线程池如何中断某一个线程呢?可以通过Executor.submit()方法来启动线程,这将返回一个Future对象,调用Future.cancel()方法将中断单一线程。
可中断的操作:sleep()
不可中断的操作:synchronized锁(ReentrantLock支持中断)和I/O阻塞(nio类支持I/O中断)
线程之间的协作
任务协作的关键是握手,实现握手的基础特性是:互斥,在这种情况下,只有一个任务可以响应某个信号。Object.wait()和notify(),以及await()和signal()可以实现握手。