多线程基础
线程生命周期
新建状态
使用new 关键字 Thread 类或者其子类建议一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start() 这个线程
就赌状态
当线程对象调用了start()方法之后,该线程就进入就赌状态。就赌状态的线程处于就赌对列中,等待JVM里线程调度器的调度
运行状态
如果就赌状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态最为复杂,它可以变为阻塞状态、就赌状态、死亡状态。
阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起) 等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间到获取设备资源后可以重新进入就赌状态。可以分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取sunchronized同步锁失败(因为同步锁被其他线程占用)
其他阻塞:通过调用线程的sleep() 或者 jion()发布了I/O请求后,线程会进入到阻塞状态。当sleep()状态超时,join() 等待线程终止或者超时,或者I/O处理完毕,线程重新转入就赌状态。
死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
锁分类
1.公平锁 ReentrantLock
所谓公平锁,就是线程按照执行顺序排成一排,依次获取锁,但是这种方式在高并发的场景下极其损耗性能
创建一个公平锁,模拟库存-1
public class StockReentrantLock extends Thread {
ReentrantLock lock = new ReentrantLock(true);
private int stock = 30;
/**
* 模拟库存-1
*/
@Override
public void run() {
while (true) {
try {
lock.lock();
if (stock > 0) {
System.out.println(Thread.currentThread().getName() + "售出一个,剩余:" + --stock);
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
可重入
/**
* 可重入锁
* 一个线程获取了几次锁,就必须释放几次锁
* 死锁
* 从例子看出主线程获取了两次锁,但是却释放了一次锁,导致线程t永远不能获取锁
*
* @author hefan
* @version
*/
public class ReentrantLock {
public static void main(String[] args) throws InterruptedException {
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
lock.writeLock().lock();
System.out.println("线程实际执行");
lock.writeLock().unlock();
}
});
lock.writeLock().lock();
lock.writeLock().lock();
t.start();
Thread.sleep(200);
System.out.println("释放一次锁");
lock.writeLock().unlock();
}
}
控制台输出
释放一次锁
可重入 一个线程获取了多少次锁,就必须释放多少次锁 死锁 可通过例子可以看出,由于获取了两次锁,但只释放了一次,导致其他线程无法获取到锁,以至于t线程没有执行
锁升级
/**
* 锁升级
* 同一个线程中,没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLocak是不支持的
*
* @author hefan
* @version
*/
public class ReentrantUpGradeLock {
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
System.out.println("get read lock");
lock.writeLock().lock();
System.out.println("阻塞");
}
}
get read lock
同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock 都是不支持的
锁降级
/**
* 锁降级
* ReentrantWriteLock支持锁降级,不会产生死锁
* 从写锁降级成为读锁,并不会自动释放当前线程获取的写锁,任然需要显示释放
* 否则别的线程永远也获取不到写锁,但在一个线程中是可以的
*
* @author hefan
* @version
*/
public class ReentrantDeGradeLock {
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();
System.out.println("write lock");
lock.readLock().lock();
System.out.println("get read lock");
lock.writeLock().lock();
System.out.println("write lock2");
}
}
控制台输出
write lock get read lock write lock2
锁降级 ReentrantWriteLock支持锁降级,不会产生死锁 从写锁降级为读锁,并不会释放当前线程的写锁,在多线程的情况下任然需要显示的释放锁,否则会 产生死锁,但同一个线程中是不需要的
读写互斥
/**
* 读写互斥
*
* @author hefan
* @version
*/
public class ReentrantLock {
public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
//同时读、写
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
readFile(Thread.currentThread());
}
});
service.execute(new Runnable() {
@Override
public void run() {
writeFile(Thread.currentThread());
}
});
}
// 读操作
public static void readFile(Thread thread) {
lock.readLock().lock();
boolean readLock = lock.isWriteLocked();
if (!readLock) {
System.out.println("当前为读锁!");
}
try {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在进行读操作……");
}
System.out.println(thread.getName() + ":读操作完毕!");
} finally {
System.out.println("释放读锁!");
lock.readLock().unlock();
}
}
// 写操作
public static void writeFile(Thread thread) {
lock.writeLock().lock();
boolean writeLock = lock.isWriteLocked();
if (writeLock) {
System.out.println("当前为写锁!");
}
try {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在进行写操作……");
}
System.out.println(thread.getName() + ":写操作完毕!");
} finally {
System.out.println("释放写锁!");
lock.writeLock().unlock();
}
}
}
控制台输出
当前为读锁! pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:读操作完毕! 释放读锁! 当前为写锁! pool-1-thread-2:正在进行写操作…… pool-1-thread-2:正在进行写操作…… pool-1-thread-2:正在进行写操作…… pool-1-thread-2:正在进行写操作…… pool-1-thread-2:正在进行写操作…… pool-1-thread-2:写操作完毕! 释放写锁!
总结:
1.Java并发包中ReentrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性
2.ReentrantReadWriteLock读写锁的效率明显高于Synchronized关键字
3.ReentrantReadWriteLock读写锁的显现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
4.ReentrantReadWriteLock读写锁实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获取写锁的这个线程可以获取读锁外,其他线程不能获取到读锁。
2.非公平锁 Synchronized、ReentrantLock、cas
所谓非公平锁,就是不管执行顺序,每个线程获取锁的几率都是相同的,获取失败了,才会采用像公平锁那样的方式,JVM可以花比较少的时间在线程调度上,更多的时间则是用于执行逻辑代码里面。
3.悲观锁 Synchronized
4.乐观锁 cas
CAS 是英文Compare And Swap的缩写,翻译过来就是比较并替换
CAS 机制使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值A,更新一个变量的时候,只有当变量的预期值A在内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
从思想上来说,Synronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守。CAS属于客观锁,乐观的认为程序中的并发情况并不是那么严重,所以让线程不断尝试去更新。
5.独享锁 Synchronized、ReentrantLock
6.共享锁 Semaphore
面试题
1.ThreadLocal和Shnchronized的区别
相同点:都是为了解决多线程中相同变量的访问冲突
不同点:Synchronized同步机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问。
ThreadLocal采用的是“以空间换时间”的方式,每个线程都有自己的一个副本,因此可以同时访问互不影响。
时间换空间:即加锁方式,某个区域的变量只有一份,节省了内存,但是会形成很多线程等待现象,因此浪费了时间而省了空间。
空间换时间:为每个线程提供一个变量,多开销一部分内存,但线程不需要等待,可以一起执行相互不影响。
2.ReentrantReadWriteLocal 读写锁
共享读,读写互斥、写读互斥、写写互斥。