多线程基础

线程生命周期

java多线程详解 - 图2

新建状态
使用new 关键字 Thread 类或者其子类建议一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start() 这个线程

就赌状态
当线程对象调用了start()方法之后,该线程就进入就赌状态。就赌状态的线程处于就赌对列中,等待JVM里线程调度器的调度

运行状态
如果就赌状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态最为复杂,它可以变为阻塞状态、就赌状态、死亡状态。

阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起) 等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间到获取设备资源后可以重新进入就赌状态。可以分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取sunchronized同步锁失败(因为同步锁被其他线程占用)
其他阻塞:通过调用线程的sleep() 或者 jion()发布了I/O请求后,线程会进入到阻塞状态。当sleep()状态超时,join() 等待线程终止或者超时,或者I/O处理完毕,线程重新转入就赌状态。

死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

锁分类

1.公平锁 ReentrantLock

所谓公平锁,就是线程按照执行顺序排成一排,依次获取锁,但是这种方式在高并发的场景下极其损耗性能

创建一个公平锁,模拟库存-1

  1. public class StockReentrantLock extends Thread {
  2. ReentrantLock lock = new ReentrantLock(true);
  3. private int stock = 30;
  4. /**
  5. * 模拟库存-1
  6. */
  7. @Override
  8. public void run() {
  9. while (true) {
  10. try {
  11. lock.lock();
  12. if (stock > 0) {
  13. System.out.println(Thread.currentThread().getName() + "售出一个,剩余:" + --stock);
  14. } else {
  15. break;
  16. }
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }finally {
  20. lock.unlock();
  21. }
  22. }
  23. }
  24. }

可重入

  1. /**
  2. * 可重入锁
  3. * 一个线程获取了几次锁,就必须释放几次锁
  4. * 死锁
  5. * 从例子看出主线程获取了两次锁,但是却释放了一次锁,导致线程t永远不能获取锁
  6. *
  7. * @author hefan
  8. * @version
  9. */
  10. public class ReentrantLock {
  11. public static void main(String[] args) throws InterruptedException {
  12. final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  13. Thread t = new Thread(new Runnable() {
  14. @Override
  15. public void run() {
  16. lock.writeLock().lock();
  17. System.out.println("线程实际执行");
  18. lock.writeLock().unlock();
  19. }
  20. });
  21. lock.writeLock().lock();
  22. lock.writeLock().lock();
  23. t.start();
  24. Thread.sleep(200);
  25. System.out.println("释放一次锁");
  26. lock.writeLock().unlock();
  27. }
  28. }

控制台输出

释放一次锁

可重入 一个线程获取了多少次锁,就必须释放多少次锁 死锁 可通过例子可以看出,由于获取了两次锁,但只释放了一次,导致其他线程无法获取到锁,以至于t线程没有执行

锁升级

  1. /**
  2. * 锁升级
  3. * 同一个线程中,没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLocak是不支持的
  4. *
  5. * @author hefan
  6. * @version
  7. */
  8. public class ReentrantUpGradeLock {
  9. public static void main(String[] args) {
  10. ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  11. lock.readLock().lock();
  12. System.out.println("get read lock");
  13. lock.writeLock().lock();
  14. System.out.println("阻塞");
  15. }
  16. }

get read lock

同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock 都是不支持的

锁降级

  1. /**
  2. * 锁降级
  3. * ReentrantWriteLock支持锁降级,不会产生死锁
  4. * 从写锁降级成为读锁,并不会自动释放当前线程获取的写锁,任然需要显示释放
  5. * 否则别的线程永远也获取不到写锁,但在一个线程中是可以的
  6. *
  7. * @author hefan
  8. * @version
  9. */
  10. public class ReentrantDeGradeLock {
  11. public static void main(String[] args) {
  12. ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  13. lock.writeLock().lock();
  14. System.out.println("write lock");
  15. lock.readLock().lock();
  16. System.out.println("get read lock");
  17. lock.writeLock().lock();
  18. System.out.println("write lock2");
  19. }
  20. }

控制台输出

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 读写锁

共享读,读写互斥、写读互斥、写写互斥。

3.分段锁原理