Java多线程
线程的状态
新建(New)、可运行(Runnable)、阻塞(Blocking)、无限期等待(Waiting)、有限期等待(Timed-Waiting)、死亡(Terminated)
使用线程
- 实现Callable接口
- 实现Runnable接口
- 继承Thread类
线程机制
用户线程与守护(Daemon)线程
守护线程时提供通用服务的线程(例如垃圾回收线程),优先级很低
通过Thread对象的setDaemon(boolean)方法设置守护线程与用户线程,注意此方法必须在线程对象的start方法之前调用,否则会抛出java.lang.IllegalThreadStateException异常
守护线程创建的线程默认是守护线程,用户线程同理。
守护线程是为了服务用户线程而存在的,所有用户线程结束之后,jvm就退出了,守护线程自然也结束,所以守护线程的结束时间不确定
睡眠
线程的sleep方法(静态方法),休眠当前线程,当前线程进入timed-waiting状态
yield
线程对象的yield方法表明该线程已经完成了最重要的工作,尝试让出cpu占用,把cpu使用权让给同优先级的其他线程
中断
interrupt()
通过调用线程对象的interrput方法中断该线程,如果该线程处于等待/阻塞状态,会抛出java.lang.InterruptedException异常。
该方法不能中断处于I/O阻塞或synchronized阻塞的线程
线程对象的该方法被调用后,会设置阻断标志为true,通过interrupted()方法检查是否有人调用了本线程的interrupt方法
interrupted()
检查是否有人调用了本线程的interrupt方法
中断的应用
线程池的shutdownNow方法就是通过调用所有工作线程的interrupt方法来中断工作线程
线程池
https://www.cnblogs.com/darknessplus/p/10359256.html
线程安全的实现方法
不可变
互斥同步
synchronized关键字与ReentrantLock
都是可重入锁
synchronized关键字由jvm实现,ReentrantLock由jdk实现
ReentrantLock提供比synchronized更高级的功能
- 等待可中断
- 公平锁
- 选择性通知
非阻塞同步
无同步
线程本地存储
ThreadLocal类
ThreadLocal 不是用来协调多线程的,而是为同一个线程的不同方法提供共享变量。
一个线程的ThreadLocal变量在任何方法内都是可见的
线程Thread保存了ThreadLocalMap,ThreadLocalMap保存了本线程的所有ThreadLocal数据
ThreadLocal提供的方法
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
线程之间的协调
多线程协作解决问题时,需要对他们的执行顺序进行协调
join
调用另一个线程对象的join方法表示本线程等待另一个线程执行完成后再继续执行
wait、notify、notifyAll
wait、notify是Object类提供的final方法,不可重写。
通过调用某个对象的wait方法,释放本线程对该对象持有的锁,进入阻塞状态。
另一个线程调用该对象的notify方法,唤醒该对象的锁的阻塞队列中的一个线程,notifyAll方法唤醒该对象的等待队列的所有线程。
wait期间,线程会释放锁,不然会死锁。
配合synchronized使用,再同步代码中使用。
wait与sleep的区别:
- wait是Object方法,sleep是Thread的静态方法
- wait会释放锁,sleep不会
生产者消费者问题:
public class Wait {
public static void main(String[] args) throws InterruptedException {
LinkedList<Integer> list = new LinkedList<Integer>();
Factory factory = new Factory();
factory.setList(list);
factory.setMaxSize(10);
for(int i=1;i<=10;i++)
{
Producer producer = new Producer();
producer.setNum(i);
producer.setFactory(factory);
producer.start();
Consumer consumer = new Consumer();
consumer.setNum(i);
consumer.setFactory(factory);
consumer.start();
}
}
}
class Producer extends Thread{
private AbstractFactory factory;
private int num;
public void setNum(int num) {
this.num = num;
}
public void setFactory(AbstractFactory factory) {
this.factory = factory;
}
@Override
public void run() {
factory.produce(num);
}
}
class Consumer extends Thread{
private AbstractFactory factory;
private int num;
public void setFactory(AbstractFactory factory) {
this.factory = factory;
}
public void setNum(int num) {
this.num = num;
}
@Override
public void run() {
factory.consume(num);
}
}
class Factory implements AbstractFactory{
private List<Integer> list;
private int maxSize;
public void setList(List<Integer> list) {
this.list = list;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public void produce(int num) {
synchronized (list)
{
while(num+list.size()>maxSize)
{
try {
System.out.println("仓库已满。要生产的"+num+",库存容量"+maxSize);
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int size=list.size();
for(int i=size;i<size+num;i++)
{
list.add(i);
}
System.out.println("生产"+num+"件,总共"+list.size()+"件");
list.notifyAll();
}
}
public void consume(int num) {
synchronized (list)
{
while(list.size()-num<0)
{
try {
System.out.println("仓库已空。要消费的"+num+",库存容量"+list.size());
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int size=list.size();
for(int i=size-1;i>=size-num;i--)
{
list.remove(i);
}
System.out.println("消耗"+num+"件,总共"+list.size()+"件");
list.notifyAll();
}
}
}
interface AbstractFactory
{
void produce(int num);
void consume(int num);
}
await、signal、signalAll
对应着Object的wait和notify方法,Lock也有类似的机制。
通过Lock.newCondition()方法获取一个condition对象,通过condition对象的await和signal方法进行线程同步。
在使用condition对象的await和signal方法之前必须获得重入锁,调用signal方法后最好释放重入锁。
JUC包
AQS
CountdownLatch
控制一个线程等待其他线程
public static void main(String[] args) throws InterruptedException {
final CountDownLatch count = new CountDownLatch(10);
for(int i=0;i<10;i++){
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
count.countDown();
}
}.start();
}
count.await();
System.out.println("END");
}
输出:
Thread-0
Thread-2
Thread-3
Thread-1
Thread-5
Thread-4
Thread-6
Thread-7
Thread-8
Thread-9
END
Semaphore
信号量,控制并发线程数
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "正在运行");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "结束");
semaphore.release();
}
}
}.start();
}
}
输出:
Thread-5正在运行
Thread-5结束
Thread-0正在运行
Thread-0结束
Thread-3正在运行
Thread-3结束
Thread-8正在运行
Thread-8结束
Thread-6正在运行
Thread-6结束
Thread-2正在运行
Thread-2结束
Thread-7正在运行
Thread-7结束
Thread-1正在运行
Thread-1结束
Thread-4正在运行
Thread-4结束
Thread-9正在运行
Thread-9结束
其他组件
FutureTask
获得任务的返回值
BlockingQueue
阻塞队列,在线程池里有应用
乐观锁和悲观锁
悲观锁
对数据更新的冲突持保守态度,认为总会发生冲突。策略是在处理数据时加锁
乐观锁
乐观锁认为发生冲突的情况比较少,不加锁,而是在更新数据的时候检查是否发生了冲突。
乐观锁的两种实现
版本号机制
为数据库表增加版本号字段,每次更新数据版本号+1。在修改数据前获取版本号,提交修改时检查两次版本号是否一致,如不一致说明数据更新发生了冲突。
CAS
Compare And Swap
现值V、旧值A、新值B,当且仅当V==A,更新B到数据库
硬件实现的原子操作
乐观锁的缺点
ABA问题
CAS,V==A并不能说明V没有发生过改变
自旋锁开销大
CAS与synchronized使用场景
CAS适用读多写少,synchronized适用写多(冲突多)
synchronized和ReenTrantLock的区别
- 都是可重入
- synchronized由jvm实现,ReenTrantLock时jdk api
- ReenTrantLock高级功能:中断等待、公平锁、选择性通知
- 两者性能相差无几