当前位置: 首页 > news >正文

网站设计的含义discuz二次开发

网站设计的含义,discuz二次开发,影视公司经营范围,竞价推广开户多少钱本篇文章以线程同步的相关内容为主。线程的同步机制主要用来解决线程安全问题#xff0c;主要方式有同步代码块、同步方法等。首先来了解何为线程安全问题。 1、线程安全问题 卖票示例#xff0c;4 个窗口卖 100 张票#xff1a; class Ticket implements Runnable {priv…本篇文章以线程同步的相关内容为主。线程的同步机制主要用来解决线程安全问题主要方式有同步代码块、同步方法等。首先来了解何为线程安全问题。 1、线程安全问题 卖票示例4 个窗口卖 100 张票 class Ticket implements Runnable {private int total 100;Overridepublic void run() {while (total 0) {// 因为 Runnable 接口中的 run() 没有 throws 任何异常因此实现类覆盖的方法也不能抛只能try// 根本原因是子类覆盖父类或接口所抛的异常只能是父类方法抛的异常或其子类try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}total--;System.out.println(Thread.currentThread().getName() ... total);}} }public class Test {public static void main(String[] args) {Ticket ticket new Ticket();Thread[] threads new Thread[4];for (int i 0; i threads.length; i) {threads[i] new Thread(ticket);threads[i].start();}} }以上代码是有线程安全问题的。在 while 循环内 sleep() 后再操作 total很容易就会出现票数为负数的情况。原因是有多个线程在操作共享的数据。 在上例中total 变量是共享数据并且被多个线程操作了 public void run() {while (total 0) {// 在这里线程执行权被切换到其它线程上try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}total--;System.out.println(Thread.currentThread().getName() ... total);}}多个线程可能执行完 while 的判断条件进入循环之后CPU 就切换了线程去执行其他线程了其他线程也能进到这个循环然后执行 total–。当执行权切换回来此时可能 total 0 的条件已经不满足了但是程序仍会会接着执行 total–导致 total 变为负数。 2、synchronized 使用 synchronized 关键字构造一个同步代码块或同步方法可以有效的解决线程安全问题。实际上相当于将 synchronized 范围内的所有代码都变成了一个原子操作来保证线程安全的。 2.1 同步代码块 使用同步代码块将可能出现线程安全的代码包起来 class Ticket implements Runnable {private int total 100;private Object obj new Object();Overridepublic void run() {// 同步代码块任何对象都可以作为锁比如 Ticket.class 也可以作为锁synchronized (obj) {while (total 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}total--;System.out.println(Thread.currentThread().getName() ... total);}}} }假如4个线程中Thread-0 拿到了执行权那么它就会把 obj 从 1 置为 0这样其它线程在 synchronized(obj) 处进行判断的时候就无法进入同步代码块。虽然 Thread-0 会在 sleep(100) 期间释放掉执行权但不会释放锁所以其它线程也是无法进入同步代码块的。等 Thread-0 完全执行完同步代码会把 obj 从 0 置为 1其它线程就可以争夺执行权、加锁、执行代码了。 同步解决了线程的安全问题但因为同步锁外的线程需要等待拿到锁之后才可以执行其任务所以相对的降低了效率。 必须要注意一下同步的使用前提即需要同步的线程必须有使用同一个锁。例如还是刚才的例子改一处 class Ticket implements Runnable {private int total 100;Overridepublic void run() {// 把同步锁声明成方法内的局部变量Object obj new Object();// 同步代码块synchronized (obj) {while (total 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}total--;System.out.println(Thread.currentThread().getName() ... total);}}} }把所 obj 从成员变量变成了线程内的局部变量这样就从 4 个线程共用 1 个 obj 锁变成了每个线程中都有一个 obj 锁每个线程只使用自己的锁使得同步失败。 2.2 同步方法 把 synchronized 关键字加在方法前就可以不用显式地使用对象来进行同步了这就是同步方法。 仍然是卖票的例子使用同步方法来做该怎么做呢直接这样 class Ticket implements Runnable {private int total 100;Overridepublic synchronized void run() {while (total 0) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}total--;System.out.println(Thread.currentThread().getName() ... total);}} }简单粗暴的把 synchronized 关键字加在 run 方法前运行程序你会发现一直都是 Thread-0 在执行其它线程根本没有卖票 这是因为 Thread-0 进入方法后一直满足 while 循环的条件所以它会一直循环直到 total 0 走出循环再结束方法。也就是说while 循环语句并不需要同步产生线程安全问题的代码是 while 循环体内的代码。因此这样修改 class Ticket implements Runnable {private int total 100;Overridepublic void run() {while (true) {show();}}public synchronized void show() {if (total 0) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}total--;System.out.println(Thread.currentThread().getName() ... total);}} }把产生安全问题的代码提到一个方法中然后用 synchronized 修饰这个方法使其变成同步方法。 我们开头提过同步方法不用像同步代码块那样显式的指定锁对象实际上它内部还是使用了锁对象的。对于成员同步方法而言调用该方法的对象就是锁对象即 this对于静态同步方法而言该方法所在的类对象就是锁对象即 Xxx.class。这里要注意有可能不同的线程产生不同的对象调用各自的成员同步方法打破了唯一锁的规则使得同步失败因此建议使用同步代码块。 2.3 注意事项 同步代码块一般使用可能被并发访问的共享资源充当同步锁或者干脆使用所在的类对象 Xxx.class。 synchronized 关键字可以修饰方法和代码块但不能修饰构造器、成员变量。 可变类的线程安全是以降低程序的运行效率作为代价的为了减少线程安全所带来的的负面影响程序可以采用如下策略 不要对线程安全类的所有方法都进行同步只对那些会改变竞争资源共享资源的方法进行同步。如果可变类有两种运行环境单线程环境和多线程环境则应该为该可变类提供两种版本即线程安全版本多线程环境中使用和线程不安全版本单线程环境中使用例如 StringBuffer安全 和 StringBuilder不安全。 synchronized 同步锁的释放不是由程序显式控制的以下介绍了哪些情况会释放锁哪些不会 3、线程间通信 synchronized 保证了线程安全但是只有 synchronized 还远远不足以面对复杂的线程使用场景比如多个线程在处理同一资源但是任务却不同。举个例子假设资源 Resource 有属性 count现在有两个线程一个去写 count另一个读 count要求两个线程交替执行。这个需求就需要用到线程间通信了。 3.1 wait()、notify() 与 notifyAll() 实现例子的思路 在 Resource 内定义一个标记位 flag表示当前数据是可读还是可写。写线程拿到锁后如果 Resource 可写那么就写入数据并通知读线程否则进入等待状态直到被读线程通知可以进行写操作。读线程拿到锁后如果 Resource 可读那么就读取数据并通知写线程否则进入等待状态直到被写线程通知可以进行读操作。 上述思路的实现需要用到 Object 中定义的方法 wait()让线程处于等待状态被 wait() 的线程会被存储到线程池中。notify()随机唤醒线程池中的一个线程。notifyAll()唤醒线程池中的所有线程。 注意 上述三个方法只能由拥有对象锁的线程调用一个线程有三种方式拥有对象锁 a) 执行 synchronized 修饰的对象同步方法 b) 执行 synchronized 修饰的静态同步方法 c) 执行持有该对象锁的 synchronized 同步代码块 也就是说必须在 synchronized 范围内使用否则会抛出 IllegalMonitorStateException。 必须要明确到底操作的是哪个锁上的线程。只有知道了所属的锁才能去唤醒这个锁上的其它线程而处于等待状态的线程才能放到这个锁的线程池当中。 关于为什么这三个操作线程的方法被定义在 Object 类中因为所有的对象都可以作为锁也就是这个锁的方法存在于所有对象中在 Java 中没有其它比 Object 这个所有类的父类更合适的定义地方了。 那么例子的实现代码可以这样 public class ThreadCommunicationDemo1 {static class Resource {private int count;// false 可写不可读true 可读不可写private boolean flag false;public void setCount(int count) {this.count count;}public int getCount() {return count;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag flag;}}static class WriteThread implements Runnable {private Resource mResource;public WriteThread(Resource resource) {mResource resource;}Overridepublic void run() {while (true) {synchronized (mResource) {if (mResource.isFlag()) {try {// 一定要调用锁的 wait()而不是直接调用 wait()mResource.wait();} catch (InterruptedException e) {e.printStackTrace();}}int count new Random().nextInt(100);mResource.setCount(count);mResource.setFlag(true);System.out.println(写入数据 count count);mResource.notify();}}}}static class ReadThread implements Runnable {private Resource mResource;public ReadThread(Resource resource) {mResource resource;}Overridepublic void run() {while (true) {synchronized (mResource) {if (!mResource.isFlag()) {try {mResource.wait();} catch (InterruptedException e) {e.printStackTrace();}}mResource.setFlag(false);System.out.println(读取数据 count mResource.getCount());mResource.notify();}}}}public static void main(String[] args) {Resource resource new Resource();new Thread(new WriteThread(resource)).start();new Thread(new ReadThread(resource)).start();} }虽然上述代码确实能实现要求的功能但是实现方式却很粗糙。把等待和唤醒操作从线程移入 Resource 中会好一点 public class ThreadCommunicationDemo2 {static class Resource {private int count;// false 可写不可读true 可读不可写private boolean flag false;public synchronized int getCount() {if (!flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}flag false;notify();return count;}public synchronized void setCount(int count) {if (flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}this.count count;flag true;notify();}}static class WriteThread implements Runnable {private Resource mResource;public WriteThread(Resource resource) {mResource resource;}Overridepublic void run() {while (true) {synchronized (mResource) {int count new Random().nextInt(100);mResource.setCount(count);System.out.println(Thread.currentThread().getName() 写入数据 count count);}}}}static class ReadThread implements Runnable {private Resource mResource;public ReadThread(Resource resource) {mResource resource;}Overridepublic void run() {while (true) {synchronized (mResource) {System.out.println(Thread.currentThread().getName() 读取数据 count mResource.getCount());}}}}public static void main(String[] args) {Resource resource new Resource();new Thread(new WriteThread(resource)).start();new Thread(new ReadThread(resource)).start();} }3.2 多生产者与多消费者 上面的例子如果放在多生产者多消费者模型中会暴露出线程安全问题假如我们创建两个读线程两个写线程运行上面的程序会得到如下输出Thread-0、1 是写线程Thread-2、3 是读线程 Thread-1写入数据 count 89 --- 正常的部分 Thread-2读取数据 count 89 Thread-0写入数据 count 70 --- 写一次读两次 Thread-2读取数据 count 70 Thread-3读取数据 count 70 Thread-0写入数据 count 73 Thread-2读取数据 count 73 Thread-3读取数据 count 73 ... Thread-1写入数据 count 72 --- 写两次读一次 Thread-0写入数据 count 7 Thread-3读取数据 count 7以写一次读两次的异常状况为例从 “Thread-2读取数据 count 89” 这一行开始分析其中原因 Thread-2 刚读了一次 89那么 flag 会被置成 false表示可以写入4 个线程争夺执行权假设 Thread-3 拿到了执行权由于 flag false不能读取所以调用到 wait() 进入线程池等待剩余的 3 个线程争夺执行权Thread-2 拿到了执行权同样的原因它也要执行 wait() 进入线程池等待这下只剩 2 个写线程争夺执行权了Thread-0 拿到执行权写入了数据 70并将 flag 置为 true最后调用 notify() 唤醒一个线程池中等待的线程Thread-2 被唤醒并且抢到执行权它会接着 wait() 后面的代码继续执行读取到数据并且唤醒线程池中仅剩的 Thread-3Thread-3 被唤醒并且抢到执行权它也不用再做 if 判断 flag 了也是接着执行 wait() 之后的代码也读取到数据从而发生了线程安全问题。 可以判断造成问题的原因是执行过 wait() 被唤醒的线程没有再次判断 flag。因此可以考虑把 if(flag) 改为 while(flag)这样在线程被唤醒之后会再次在循环条件处判断 flag不过可能会出现死锁 假设 Thread-0 和 Thread-1 在线程池中 wait()这时候进来一个消费者 Thread-2消费并唤醒了 Thread-0。1等待2、3、0活Thread-2 和 Thread-3 先后执行被 wait()。1、2、3等待0活Thread-0 执行并生产一次唤醒了 Thread-1。0、1活2、3等待此时 flag 为 true了Thread-0 和 Thread-1 先后执行由于 flag 为 true 结果 wait()至此全部线程 wait() 发生死锁。 也就是说如果出现唤醒本方的情况就可能造成死锁。因此需要唤醒所有线程把 notify() 换成 notifyAll()。这样在第 3 步时就唤醒了1、2、3消费者被唤醒了就不会死锁了。 3.3 其它线程间通信方式 通过 synchronized 配合 Object 类的 wait()、notify()、notifyAll() 三个方法是属于传统的实现线程间通信的方式。相对新兴一点的方式是下一节要介绍的 Lock 搭配 Condition 的 await()、signal()、signalAll()。 此外使用阻塞队列BlockingQueue也能控制线程通信。BlockingQueue 接口作为 Queue 的子接口主要作用不是作为容器而是作为线程同步的工具。它有一个特征生产者线程试图向 BlockingQueue 放入元素时如果该队列已满则该线程被阻塞消费者线程试图从 BlockingQueue 中取出元素时如果该队列已空则该线程被阻塞。 BlockingQueue 除了可以使用 Queue 中提供的方法之外还提供了一对儿阻塞方法 put() 和 take()对应关系如下 最后附上 BlockingQueue 接口的继承体系 4、Lock 接口 Lock 是在 JDK 1.5 加入的特性它允许实现比同步代码块和同步方法更灵活的结构并且支持多个相关的 Condition 对象。Lock 是一个接口定义了如下方法 使用 Lock 接口必须显式地调用 lock()/unlock() 给临界区上锁/解锁因此它是一个显式锁。而 synchronized 不用显式调用方法来上锁与解锁因此 synchronized 是一个隐式锁。 4.1 ReentrantLock ReentrantLock 是 Lock 接口最常用的实现类翻译过来是可重入锁顾名思义即一个线程可以对已被加锁的 ReentrantLock 再次加锁而不发生死锁。ReentrantLock 对象内部会维护一个计数器来追踪 lock() 的嵌套调用以确保程序调用 unlock() 释放锁。ReentrantLock 的基本代码框架如下 为了确保释放锁的动作不会因为其他代码抛出异常而不被执行通常情况下 unlock() 要在 finally 代码块中调用这是非常重要的一点。 4.2 Condition Condition 接口一般被称作条件对象主要提供了 await()、signal()、signalAll() 三个方法分别对应 Object 的 wait()、notify()、notifyAll()它将 Object 的这三个监视器方法分解成截然不同的对象以便通过将这些对象与任意 Lock 实现组合使用。 Lock-Condition 这套接口与 synchronized 关键字的不同之处在于synchronized 的锁只能有一组属于这个锁的线程通过 wait()、notify()、notifyAll() 进行通信而一个 Lock 上可以绑定多个 Condition每个 Condition 里边可以用 await()、signal()、signalAll() 通信。利用这一点可以对 3.2 节中多生产者多消费者的例子再次做出优化。 在 3.2 节中即便是使用 notifyAll() 也是有几率唤醒本方线程的虽然这个例子中没有造成严重的后果但是也算是可以优化的地方。使用 Lock-Condition 可以指定唤醒对方的线程即在 Lock 对象通过 newCondition() 生成生产者和消费者的两个 Condition 对象那么等待/唤醒就需要指定是在哪个 Condition 上执行获取锁的位置也要修改为等待 Lock 的哪一个 Condition public class ThreadCommunicationDemo4 {static class Resource {private int count;// false 可写不可读true 可读不可写private boolean flag false;Lock lock new ReentrantLock();// 生产者条件final Condition producerCon lock.newCondition();// 消费者条件final Condition consumerCon lock.newCondition();public int getCount() {lock.lock();try {while (!flag) {try {// 消费者等待consumerCon.await();} catch (InterruptedException e) {e.printStackTrace();}}flag false;// 唤醒生产者不必唤醒全部producerCon.signal();} finally {lock.unlock();}return count;}public void setCount(int count) {lock.lock();try {while (flag) {try {// 生产者等待producerCon.await();} catch (InterruptedException e) {e.printStackTrace();}}this.count count;flag true;// 唤醒消费者不必唤醒全部consumerCon.signal();} finally {lock.unlock();}}}static class WriteThread implements Runnable {private final Resource mResource;public WriteThread(Resource resource) {mResource resource;}Overridepublic void run() {while (true) {// 生产者需要拿到锁上面的“生产者条件”synchronized (mResource.producerCon) {int count new Random().nextInt(100);mResource.setCount(count);System.out.println(Thread.currentThread().getName() 写入数据 count count);}}}}static class ReadThread implements Runnable {private final Resource mResource;public ReadThread(Resource resource) {mResource resource;}Overridepublic void run() {while (true) {// 消费者需要拿到锁上面的“消费者条件”synchronized (mResource.consumerCon) {System.out.println(Thread.currentThread().getName() 读取数据 count mResource.getCount());}}}}public static void main(String[] args) {Resource resource new Resource();new Thread(new WriteThread(resource)).start();new Thread(new WriteThread(resource)).start();new Thread(new ReadThread(resource)).start();new Thread(new ReadThread(resource)).start();} }这样就实现了分组唤醒。 4.3 synchronized 与 Lock 下面来聊聊 synchronized 与 Lock 之间的区别与联系。 synchronized 是一个隐式锁允许每个对象有一个内部锁该锁有一个内部条件这使得 相对简单编写代码相对简洁每个锁仅有单一的条件可能不够不能中断一个正在等待获得锁的线程不能尝试拿锁更加不能设置尝试拿锁的超时时间 synchronized 还强制要求加锁与释放锁要出现在一个块结构中而且当获取了多个锁时必须以相反的顺序释放且必须在与所有锁被获取时相同的范围内释放所有锁。总结起来就是编程方便也能避免一些错误但是不够灵活。 Lock 是一个显式锁其实现类 ReentrantLock 是可重入锁允许每个对象持有多个锁并且每个锁可以有多个条件变量有如下特点 使用相对复杂编写代码不够简洁容易犯错每个锁有多个条件可以满足复杂的同步使用场景提供了 lockInterruptibly() 可以中断等待获取锁的线程提供了 tryLock() 和 tryLock(long time, TimeUnit unit)可以尝试获取锁并设置超时时间 Lock 相比于 synchronized 更加灵活Condition 也将监视器方法单独进行了封装变成 Condition 监视器对象可以任意锁进行组合。 关于所有同步工具使用的优先顺序 最好既不使用 Lock-Condition 也不使用 synchronized 关键字。如果 java.util.concurrent 包下的机制能满足你的需求应优先使用它们如阻塞队列、并行流等。如果 synchronized 适合你的程序应尽量使用它这样可以减少代码量和出错几率。当特别需要 Lock-Condition 结构提供的独有特性时才使用它们。 4.4 锁的分类 通过前面的介绍我们也能发现有多个角度可以对锁进行分类比如前面已经说过的显式锁 Lock 与隐式锁 synchronized。除此之外还有可重入锁。 可重入锁是指某个线程已经获得某个锁可以再次获取锁而不会出现死锁。除了前面提到的 ReentrantLock 外synchronized 也是可重入锁。比如说 public void synchronized test() {count;test();}不会出现死锁原因就是 synchronized 是可重入锁。 参考文章 Java可重入锁详解 可重入锁详解什么是可重入 此外根据执行原子操作之前还是之后获得锁这一点可以分为乐观锁和悲观锁。像 CAS 这种先进行计算后进行校验的锁称为乐观锁而像 synchronized 这种先进行校验没有锁就不能执行的锁称为悲观锁。 读写锁接口 ReadWriteLock 的唯一实现类 ReentrantReadWriteLock 的性能要比普通的 同步锁高很多原因是多线程读取并不会引发线程安全问题因此读取锁使得所有要读取的线程都能访问到共享资源并获取最新的数据加了读锁能获取到最新不加读锁也可拿到数据不过不是最新的。读写锁适用于读取请求较多的情况下例模拟一个购物 App 访问商品数据 // 商品 JavaBean public class GoodsInfo {private final String name;private double totalMoney; //总销售额private int storeNumber; //库存数public GoodsInfo(String name, int totalMoney, int storeNumber) {this.name name;this.totalMoney totalMoney;this.storeNumber storeNumber;}public double getTotalMoney() {return totalMoney;}public int getStoreNumber() {return storeNumber;}// 卖出 sellNumber 件商品后更新库存和销售额public void updateStoreNumber(int sellNumber) {this.totalMoney sellNumber * 25;this.storeNumber - sellNumber;} }GoodsService 接口用来规定读取/写入商品数据 public interface GoodsService {GoodsInfo getGoodsInfo();void setNum(int num); }它的两个实现类 SynService 和 RwLockService 分别使用 synchronized 同步方法和读写锁的方式实现了读写商品方法 public class SynService implements GoodsService {private GoodsInfo goodsInfo;public SynService(GoodsInfo goodsInfo) {this.goodsInfo goodsInfo;}Overridepublic synchronized GoodsInfo getGoodsInfo() {try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}return goodsInfo;}Overridepublic synchronized void setNum(int num) {try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}goodsInfo.updateStoreNumber(num);} }public class RwLockService implements GoodsService {private GoodsInfo goodsInfo;private final ReadWriteLock readWriteLock new ReentrantReadWriteLock();private final Lock getLock readWriteLock.readLock();private final Lock setLock readWriteLock.writeLock();public RwLockService(GoodsInfo goodsInfo) {this.goodsInfo goodsInfo;}Overridepublic GoodsInfo getGoodsInfo() {getLock.lock();try {Thread.sleep(5);return goodsInfo;} catch (InterruptedException e) {e.printStackTrace();return goodsInfo;} finally {getLock.unlock();}}Overridepublic void setNum(int num) {setLock.lock();try {Thread.sleep(5);goodsInfo.updateStoreNumber(num);} catch (InterruptedException e) {e.printStackTrace();} finally {setLock.unlock();}} }我们还需要自定义两个线程类一个只负责读另一个只负责写同时用线程对象的数量来模拟实际项目中读请求数量大于写请求的数量 public class BusinessApp {private static final int readWriteRatio 10; // 读写线程的比例private static final int minThreadCount 3; // 最少线程数private static class ReadThread implements Runnable {private GoodsService goodsService;public ReadThread(GoodsService goodsService) {this.goodsService goodsService;}Overridepublic void run() {long start System.currentTimeMillis();for (int i 0; i 100; i) { //操作100次goodsService.getGoodsInfo();}System.out.println(Thread.currentThread().getName() 读取商品数据耗时 (System.currentTimeMillis() - start) ms);}}private static class WriteThread implements Runnable {private GoodsService goodsService;public WriteThread(GoodsService goodsService) {this.goodsService goodsService;}Overridepublic void run() {long start System.currentTimeMillis();for (int i 0; i 10; i) { //操作10次try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}goodsService.setNum(new Random().nextInt(10));}System.out.println(Thread.currentThread().getName() 写商品数据耗时 (System.currentTimeMillis() - start) ms---------);}}public static void main(String[] args) {GoodsInfo goodsInfo new GoodsInfo(Goods, 100, 100);// 内置锁 // GoodsService goodsService new SynService(goodsInfo);// 读写锁GoodsService goodsService new RwLockService(goodsInfo);for (int i 0; i minThreadCount; i) {Thread writeThread new Thread(new WriteThread(goodsService));for (int j 0; j readWriteRatio; j) {Thread readThread new Thread(new ReadThread(goodsService));readThread.start();}writeThread.start();}} }main() 中通过给 GoodsService 创建两种锁实例的方式来进行对比可以看到使用读写锁的耗时要远远小于 synchronized 的同步锁 // 读写锁 Thread-22写商品数据耗时636ms--------- Thread-26读取商品数据耗时747ms// synchronized 同步锁 Thread-2读取商品数据耗时17050ms Thread-11写商品数据耗时17348ms---------读写锁可以参考以下文章 深入理解读写锁—ReadWriteLock源码分析 公平锁 FairSync 与非公平锁 NonFairSync 其实是 ReentrantLock 的静态内部类并且是 final 的。创建 ReentrantLock 对象时如果在构造方法中传入 true 就会构造出一个公平锁否则创建非公平锁 // 默认创建非公平锁public ReentrantLock() {sync new NonfairSync();}public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();}公平锁与非公平锁 公平锁按照线程申请锁的顺序执行线程。哪个线程先申请的锁、等待的时间最长就先获得锁后申请锁的线程会被暂时挂起等待。到了执行顺序后再把该线程由挂起状态转换成可执行状态这个上下文切换的过程是非常耗时的大概需要20000个时间周期1个时间周期就是执行1条语句所需的时间。因此公平锁的效率要大大低于非公平锁故默认情况下创建的是非公平锁。非公平锁线程的执行顺序与申请锁的顺序无关全凭操作系统调度。synchronized 就是非公平锁。 听起来公平锁更合理一些但是使用公平锁要比常规锁慢很多并且即使使用公平锁也无法保证线程调度器是公平的。因此只有当你确实了解自己要做什么并且对你要解决的问题确实有一个特定的理由必须使用公平锁的时候才可以使用公平锁。 5、死锁 死锁是指两个或两个以上的线程在执行过程中由于竞争资源或者由于彼此通信而造成的一种阻塞的现象若无外力作用它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。 死锁的危害很大主要有以下三方面 线程不工作了但是整个程序还是活着的没有任何的异常信息可以供我们检查一旦程序发生了发生了死锁是没有任何的办法恢复的只能重启程序对正式已发布程序来说这是个很严重的问题。 5.1 形成死锁的条件 同步机制使用不当可能会造成死锁最常见的情景之一就是同步的嵌套 public class NormalDeadLock {private static final Object lock1 new Object();private static final Object lock2 new Object();private static class RunnableA implements Runnable {Overridepublic void run() {String threadName Thread.currentThread().getName();synchronized (lock1) {System.out.println(threadName got lock1);try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println(threadName got lock2);}}}}private static class RunnableB implements Runnable {Overridepublic void run() {String threadName Thread.currentThread().getName();synchronized (lock2) {System.out.println(threadName got lock2);try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1) {System.out.println(threadName got lock1);}}}}public static void main(String[] args) {new Thread(new RunnableA()).start();new Thread(new RunnableB()).start();} }产生死锁的必要条件 多个操作者m2争夺多个资源n2且 nm。争夺资源的顺序不对。拿到资源不放手。 学术化定义的死锁条件 互斥拿到资源后独占。请求和保持已经拿到了资源还要请求新的资源。不剥夺线程已经拿到的资源在使用完成前不能被剥夺只能在使用完后自己释放。环路等待线程 0 拿了 A 锁请求 B 锁而线程 1 拿了 B 锁请求 A 锁。 5.2 避免死锁的方法 避免死锁要从破坏死锁的必要条件上入手 打破互斥条件改造独占性资源为虚拟资源大部分资源已无法改造。打破不可抢占条件当一线程占有一独占性资源后又申请一独占性资源而无法满足则退出原占有的资源。打破占有且申请条件采用资源预先分配策略即线程运行前申请全部资源满足则运行不然就等待这样就不会占有且申请。打破循环等待条件实现资源有序分配策略对所有设备实现分类编号所有线程只能采用按序号递增的形式申请资源。 破坏以上任一条件都可以打破死锁。以上面的代码举例来说从“打破循环等待条件”这一条出发可以让两个线程都先争夺同一个锁而不是一个先争夺 lock1 另一个先争夺 lock2 从“打破不可抢占条件”这一条出发可以使用 Lock 锁的“尝试拿锁”机制代替 synchronized 锁 public class TryLock {private static final Lock lock1 new ReentrantLock();private static final Lock lock2 new ReentrantLock();private static class RunnableA implements Runnable {Overridepublic void run() {String threadName Thread.currentThread().getName();boolean flagLock2 false;while (true) {if (lock1.tryLock()) {try {System.out.println(threadName got lock1);if (lock2.tryLock()) {flagLock2 true;try {System.out.println(threadName got lock2);System.out.println(threadName do work---);break;} finally {lock2.unlock();}}if (!flagLock2) {System.out.println(threadName didnt get lock2.Release lock1.);}} finally {lock1.unlock();}}try {Thread.sleep(new Random().nextInt(3));} catch (InterruptedException e) {e.printStackTrace();}}}}private static class RunnableB implements Runnable {Overridepublic void run() {String threadName Thread.currentThread().getName();boolean flagLock1 false;while (true) {if (lock2.tryLock()) {try {System.out.println(threadName got lock2);if (lock1.tryLock()) {flagLock1 true;try {System.out.println(threadName got lock1);System.out.println(threadName do work---);break;} finally {lock1.unlock();}}if (!flagLock1) {System.out.println(threadName didnt get lock1.Release lock2.);}} finally {lock2.unlock();}}try {Thread.sleep(new Random().nextInt(3));} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {new Thread(new TryLock.RunnableA()).start();new Thread(new TryLock.RunnableB()).start();} }Lock 的 tryLock() 返回 true 时表明已经拿到锁返回 false 表示没有拿到锁。对于两个线程来说如果只拿到了外层的锁而没有拿到内层的锁那么通过在外层的 finally 块中释放外层锁可以避免死锁。通过死循环与拿到两道锁执行完任务后 break 的搭配可以保证任务一定会被执行一次。一种可能的输出如下 // Thread-0 先拿到 lock1但是没拿到 lock2那么就释放 lock1 Thread-0 got lock1 Thread-0 didnt get lock2.Release lock1 // Thread-1 得到执行权先后拿到 lock2、lock1 Thread-1 got lock2 Thread-1 got lock1 Thread-1 do work--- // Thread-1 执行完任务将两道锁释放最后 Thread-0 拿到两道锁执行完任务 Thread-0 got lock1 Thread-0 got lock2 Thread-0 do work---避免死锁常见的算法有有序资源分配法、银行家算法这里我们就不详细展开了。 死锁与程序阻塞并不是相同的概念。死锁是所有线程都在等待对方释放锁而阻塞并不是因为争抢不到同步锁只是所有线程都处于阻塞状态无法被唤醒去继续执行 5.3 活锁 上例中 RunnableA 和 RunnableB 的 run() 的最后都使用了 Thread.sleep() 使当前线程进行等待如果把它们去掉会得到如下的结果 Thread-0 got lock1 Thread-1 got lock2 Thread-0 didnt get lock2.Release lock1. Thread-1 didnt get lock1.Release lock2. Thread-0 got lock1 Thread-1 got lock2 Thread-0 didnt get lock2.Release lock1. Thread-1 didnt get lock1.Release lock2. Thread-0 got lock1 Thread-1 got lock2 Thread-0 didnt get lock2.Release lock1. Thread-1 didnt get lock1.Release lock2. Thread-0 got lock1 Thread-1 got lock2 Thread-0 didnt get lock2.Release lock1. Thread-1 didnt get lock1.Release lock2. …… 省略n多次重复 Thread-0 got lock1 Thread-0 got lock2 Thread-0 do work--- Thread-1 got lock2 Thread-1 got lock1 Thread-1 do work---可见两个线程拿锁的过程急剧加长了多次出现了 Thread-0 拿到 lock1Thread-1 拿到 lock2然后两个线程拿第二个锁失败的情况后续又重复了多次这种情况。像这种线程仍处于运行状态但实际上却没有执行业务代码一直在做拿锁-释放锁的无用功的情况称为活锁。 解决方式就是在锁的范围之外使用 Thread.sleep() 休眠一小段时间让两个线程拿锁的时间错开一点进而避免双方各拿到一个锁的局面发生。 Thread.sleep() 会让出 CPU但是不会释放锁。如果把休眠动作放在锁内由于本方线程已经拿到的锁并不会释放即使让出 CPU 时间片给对方线程对方线程也是拿不到本方已经持有的锁的。所以上边才强调要在锁的范围之外加 Thread.sleep()。 另外Thread.sleep() 不会释放锁但是 Object.wait() 会释放锁。调用了前者的线程时间到了会自动唤醒而调用了后者的线程会进入线程等待池中等待只有通过 notify()、notifyAll() 等方法唤醒后才能重新进入就绪队列抢锁执行。 6、线程安全集合 使用 Collections 工具类可以把线程不安全的 ArrayList、HashSet、HashMap 等集合变成线程安全集合 除此之外在 java.util.concurrent 包下有大量支持高效并发访问的集合接口和实现类 可以分为如下两类 详细介绍 7、测试题 1.run 和start的区别 答run是函数调用 和线程没有任何关系 .start会走底层 会走系统层 最终调度到 run函数这才是线程。 2.如何控制线程的执行顺序 答join来控制 让t2获取执行权力能够做到顺序执行 3.多线程中的并行和并发是什么 答四个车道四辆车并行的走就是并行 四个车道中五秒钟多少的车流量多少的吞吐量一样 4.在Java中能不能指定CPU去执行某个线程 答不能Java是做不到的唯一能够去干预的就是C语言调用内核的API去指定才行这个你回答的话面试官会觉得你研究点东西 5.在项目开发过程中你会考虑Java线程优先级吗 答不会考虑优先级为什么呢 因为线程的优先级很依赖与系统的平台所以这个优先级无法对号入座无法做到你想象中的优先级属于不稳定有风险 因为某些开源框架也不可能依靠线程优先级来设置自己想要的优先级顺序这个是不可靠的 例如Java线程优先级又十级而此时操作系统优先级只有2~3级那么就对应不上 6.sleep和wait又什么区别 答sleep是休眠等休眠时间一过才有执行权的资格注意只是又有资格了并不代表马上就会被执行什么时候又执行起来取决于操作系统调度 wait是等待需要人家来唤醒唤醒后才有执行权的资格注意只是又有资格了并不代表马上就会被执行什么时候又执行起来取决于操作系统调度 含义的不同sleep无条件可以休眠 wait是某些原因与条件需要等待一下资源不满足拿不到同步锁就等待 7.在Java中能不能强制中断线程的执行 答虽然提供了 stop 等函数但是此函数不推荐使用为什么因为这种暴力的方式很危险例如下载图片5kb只下载了4kb 等 我们可以使用interrupt来处理线程的停止但是注意interrupt只是协作式的方式并不能绝对保证中断并不是抢占式的 8.如何让出当前线程的执行权 答yield方法只在JDK某些实现才能看到是让出执行权 9.sleepwait到底那个函数才会 清除中断标记 答sleep在抛出异常的时候捕获异常之前就已经清除 10.如果错误 错误发生在哪一行 class Test implements Runnable {public void run(Thread t) {} }错误在第一行应该被 abstract 修饰。其实是 Runnable 接口中的 run() 方法是无参的而例子中的 run(Thread t) 以 Thread 对象作为参数其实就是没有实现 Runnable 接口所以这个类要声明成抽象类。 11.运行结果 new Thread(new Runnable(){public void run(){System.out.println(runnable run);}}){public void run(){System.out.println(subThread run);}}.start();subThread run。重写了 run 方法就以它为主没有重写就以 Runnable 接口为主。
http://www.hkea.cn/news/14352320/

相关文章:

  • 网站职能高平市网站建设公司
  • 公司网站建设请示建设银行网站在哪设置查询密码
  • 2021给个最新网站网站服务器租赁费用表格
  • 德州公司做网站山东济南网站建设公司哪家好
  • 软件开网站建设骗术ui培训学费一般多少
  • 网站开发工程师 面试英语阳狮做网站
  • 个人网站对应网站网址静安做网站公司
  • 白酒包装设计网站用php做的博客网站
  • 专门做特卖的网站是什么soho做网站要写品牌吗
  • 清河网站建设设计怎么弄自己的微信公众号
  • 做网站的论坛1微信网站怎么建设
  • 一个网站需要多少网页我想建立一个网站不知道怎么做啊
  • WordPress开网站很慢seo上首页排名
  • 医院网站建设最新报价慢慢来 网站建设
  • 调用别人网站注册表单北京建设部安全员证书查询网站
  • oppo官方网站建e网别墅客厅
  • 网站集约化建设的讲话wordpress 用户组
  • 临沂网站建设公司招聘dw做网站地图
  • 做网站设计工作的报告企业运营的五大系统
  • 官方网站建设流程wordpress评论
  • 购卡链接网站怎么做青岛海川建设集团网站
  • 瑞安做网站公司个人网页是什么意思
  • 网页设计作业可爱的家乡seo学院培训班
  • 收费网站设计方案简单的html模板
  • 网站开发的比较seo课程培训视频
  • 网站降权怎么处理2014网站怎么备案
  • 360建筑网现在叫什么网上seo研究
  • wordpress 下载站模板电商小程序制作一个需要多少钱
  • 网站教育机构排行前十名设计素材网站解析
  • 澄海建设局网站上不了人员优化是什么意思