当前位置:   article > 正文

Java高并发编程:线程锁技术_java方法加锁还可以并发吗

java方法加锁还可以并发吗

目录

1 什么是线程锁

2 synchronized

1. 对象锁

2. 修饰对象方法

3. 类锁

4. 对象锁和类锁

5. 卖火车票示例

6. 生产一个消费一个示例

3 Lock

3.1 重入锁 ReentrantLock

3.2 读写锁

4 Condition

示例1

示例2

5 死锁


1 什么是线程锁

实例的全局变量(共享资源)被修改时,会出现线程安全,需要对修改的方法加锁,注意:只要要访问多个线程共享的字段的方法都需要加锁保护。不能前门(方法A)锁上了,后门(方法B)就不锁了,或者窗户(方法C)开着了,这都不行,都必须锁住。
临界区:只允许单个线程执行的程序范围

多线程可以同时运行多个任务

但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!

so,不使用线程锁, 可能导致错误

线程锁技术:Lock & Condition 实现线程同步通信所属包:java.util.concurrent.locks

线程锁说明
Synchronized同步方法,锁对象是this;同步静态方法,锁对象是字节码.class;同步代码块,锁对象是任意对象,但必须是同一个对象
Lock同步锁接口
ReentrantLocklock(),unlock(),newCondition()
ReadWriteLock读写锁接口
ReentrantReadWriteLockreadLock()获取读锁,writeLock()获取写锁
Condition线程间通信 await()等待 signal()唤醒

 

synchronized和Lock锁的区别

1、synchronized是Java内置的关键字;Lock锁是Java的一个类

2、synchronized无法获取到锁的状态;Lock锁可以判断是否获取到了锁

3、synchronized会自动的释放锁;Lock必须要手动的去释放锁,如果不释放,就会造成死锁

4、synchronized中,线程一在获得锁的情况下阻塞了,第二个线程就只能傻傻的等着;Lock锁出现这种情况,可以使用tryLock()尝试获取锁

5、synchronized是不可中断的、非公平的、可重入锁;Lock锁是非公平的(可以自己设置)、可判断的、可重入锁

6、synchronized适合锁少量的同步代码;Lock锁适合锁大两的同步代码

7、synchronized有代码块锁和方法锁;Lock只有代码块锁

8、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(拥有更多的子类)

2 synchronized

synchronized是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,也就是保证synchronized内的代码块同步执行,而不会并行执行。

synchronized可以作为代码块使用,也可以直接放在方法(对象方法、静态方法)的签名上。作为代码块必须指定一个对象锁,对象锁就是一个对象,可以是this对象(即调用该方法的实例对象)也可以是自己创建的某个对象,也可以是某个类的字节码对象,synchronized修饰的代码属于原子操作,不可再分割!

  • 作用实例方法时,锁住的是对象的实例(this)

  • 作用静态方法时,锁住的是该类,该 Class所有实例,又因为 Class 的相关数据存储在永久带 PermGen(jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程

    • 线程A调用一个实例对象非静态Synchronized方法,允许线程B调用该实例对象所属类的静态s方法而不会发生互斥,前者锁的是当前实例对象,后者锁的是当前类
  • 作用于同步代码块 锁住的当前对象,进入同步代码块前需要获得对象的锁

  1. // 实例方法使用的是this锁,即调用该对象的实例
  2. synchronized(this) {
  3. // code
  4. }
  5. Object object = new Object();
  6. synchronized(object) {
  7. // code
  8. }
  9. // 静态方法是使用的类锁
  10. synchronized(Xxx.class) {
  11. // code
  12. }
  13. // 对象锁(修饰对象方法,锁为this,即调用该方法的实例对象)
  14. public synchronized void foo() {
  15. }
  16. // 类锁(修饰静态方法,锁为这个类,不是某一个对象)
  17. public synchronized static void bar() {
  18. }

 

当多个线程过来同时执行,如何保证同一时刻只有一个线程在执行(即同步执行)?

synchronized无论作为代码块还是直接修饰方法都会指定一个锁,当一个线程要想执行线程体就必须拿到这个锁,只有拿到锁的线程才能执行,这样只有一个线程能拿到锁,其他线程就拿不到锁,这样其它线程就执行不了,就必须等待,当拿到锁的那个线程执行完就去释放这个锁,这样其它等待的线程就去抢这个锁,哪个线程抢到了锁就去执行,抢不到就继续下一轮的抢。锁分为对象锁和类锁。

在用synchronized关键字的时候,尽量缩小代码块的范围,能在代码段上加同步就不要再整个方法上加同步。减小锁的粒度,使代码更大程度的并发。

1. 对象锁

  1. public class ThisLock {
  2. public static void main(String[] args) {
  3. MyRunnable runnable = new MyRunnable();
  4. System.out.println("runnable=" + runnable);
  5. new Thread(runnable, "Thread-A").start();
  6. new Thread(runnable, "Thread-B").start();
  7. }
  8. }
  9. class MyRunnable implements Runnable {
  10. @Override
  11. public void run() {
  12. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + this);
  13. synchronized (this) {
  14. for(int i = 0; i < 5; i++) {
  15. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  16. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  17. }
  18. }
  19. }
  20. }

通过打印结果可以看到this锁就是当前的线程对象runnable,可以看到代码块外的代码是并发执行的,而代码块内部的代码是同步执行的,即同一时刻只有一个线程在执行同步代码块。Thread-A先拿到锁就先执行,直到执行完毕,释放锁,然后Thread-B拿到锁就执行。

示例代码中两个线程都是使用同一个runnable对象,如果两个线程各自使用各自的MyRunnable对象,则是并发的,不会是同步的。

  1. // new Thread(runnable, "Thread-A").start();
  2. // new Thread(runnable, "Thread-B").start();
  3. // 锁无效,因为每个new就是一个新的对象,两个线程使用两个不同的锁,达不到互斥的效果
  4. new Thread(new MyRunnable(), "Thread-A").start();
  5. new Thread(new MyRunnable(), "Thread-B").start();

2. 修饰对象方法

  1. public class MethodLock {
  2. public static void main(String[] args) {
  3. MyRunnable runnable = new MyRunnable();
  4. System.out.println("runnable=" + runnable);
  5. new Thread(runnable, "Thread-A").start();
  6. new Thread(runnable, "Thread-B").start();
  7. }
  8. }
  9. class MyRunnable implements Runnable {
  10. @Override
  11. public void run() {
  12. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + this + "\trunning...");
  13. myRun();
  14. }
  15. private synchronized void myRun() {
  16. for(int i = 0; i < 4; i++) {
  17. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  18. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  19. }
  20. }
  21. }

synchronized修饰方法,表示将会锁住整个方法体,锁的是当前对象,这个synchronized(this)代码块包括住方法的所有代码是一样的效果

  1. // 两种方式效果一样
  2. synchronized(this) {
  3. for(int i = 0; i < 4; i++) {
  4. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  5. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  6. }
  7. }
  8. private synchronized void myRun() {
  9. for(int i = 0; i < 4; i++) {
  10. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  11. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  12. }
  13. }

3. 类锁

  1. public class ClassLock {
  2. public static void main(String[] args) {
  3. new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. ExampleService.test();
  7. }
  8. }, "Thread-A").start();
  9. new Thread(new Runnable() {
  10. @Override
  11. public void run() {
  12. ExampleService.test2();
  13. }
  14. }, "Thread-B").start();
  15. }
  16. }
  17. class ExampleService {
  18. // 静态方法
  19. public synchronized static void test() {
  20. for(int i = 0; i < 4; i++) {
  21. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  22. try { Thread.sleep(500); } catch (InterruptedException e) { }
  23. }
  24. }
  25. // 静态方法
  26. public synchronized static void test2() {
  27. for(int i = 0; i < 4; i++) {
  28. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  29. }
  30. }
  31. }

两个线程分别调用不同的静态方法,两个方法是同步执行,而不是并发执行。因为两个线程都是使用的类锁ExampleService.class, 当Thread-A执行时拿到了ExampleService的类锁,那么Thread-B就拿不到这个类锁了,只有Thread-A执行完毕后释放类锁,Thread-B才能拿到类锁继续执行,所以是同步执行的。

静态同步函数的锁是class对象

4. 对象锁和类锁

  1. public class ObjectClassLock {
  2. public static void main(String[] args) {
  3. TestService testService = new TestService();
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. testService.test();
  8. }
  9. }, "Thread-A").start();
  10. new Thread(new Runnable() {
  11. @Override
  12. public void run() {
  13. testService.test2();
  14. }
  15. }, "Thread-B").start();
  16. }
  17. }
  18. class TestService {
  19. // 锁的是testService这个对象
  20. public synchronized void test() {
  21. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");
  22. try {
  23. Thread.sleep(2000);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println((new Date() + "\t" + Thread.currentThread().getName() + "\t over"));
  28. }
  29. // 静态是锁的TestService这个类(TestService.class)
  30. // 对象锁和类锁不是同一个锁,所以两个方法不会互斥
  31. public synchronized static void test2() {
  32. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");
  33. System.out.println((new Date() + "\t" + Thread.currentThread().getName() + "\t over"));
  34. }
  35. }

Thread-A是访问的对象方法,锁为当前调用者即锁对象为testService,Thread-B虽然是通过对象调用的但是是访问的类方法,锁为类锁,即TestService.class, 因两个线程使用的是不同的锁,自然就互不影响,各自执行各自的,所以是并发执行的,而不是同步执行的。

5. 卖火车票示例

  1. public class TrainTicketTest {
  2. int num = 2;
  3. public static void main(String[] args) {
  4. // 抢火车票功能
  5. TrainTicketTest synchronizedTest = new TrainTicketTest();
  6. for (int i = 0; i < 5; i ++) {
  7. new Thread(synchronizedTest.new TrainTicketThread()).start();
  8. }
  9. }
  10. class TrainTicketThread implements Runnable {
  11. @Override
  12. public void run() {
  13. System.out.println(Thread.currentThread().getName() + "\trunning");
  14. // 模拟业务耗时的时间
  15. try { Thread.sleep(500);} catch (InterruptedException e) { }
  16. if (num > 0) {
  17. System.out.println(Thread.currentThread().getName() + "\t抢到一张票\t目前还有" + num + "张\t");
  18. int temp = num - 1;
  19. num = temp;
  20. System.out.println(Thread.currentThread().getName() + "还剩余" + num + "张");
  21. }
  22. }
  23. }
  24. }

因多线程可能每次运行效果都不同,这里给出一种效果,总共2张票,卖出去3张,卖超了

根据上面的运行结果试图对多线程代码执行分析

出现多卖的原因是由于多线程CPU时间片“随机”的切换,同一段代码被多个线程同时执行,导致值的错乱,解决这种问题的办法就是同一段代码同一时间不允许多个线程执行,也就是只能允许一个线程来执行。
我们可以为代码加上一个锁,哪个线程想要执行这段代码必须手里拿着锁才能执行,当执行完毕就把锁丢出去,其它线程如果想要执行就必须抢到锁(注意多个线程必须使用同一个锁),只有抢到锁了才能执行加锁的代码,为代码加锁有两种方式synchronized和lock,锁就是一个对象。

  1. public class TrainTicketTest {
  2. // 全局变量,必须保证所有线程都在使用同一个锁
  3. Object object = new Object();
  4. int num = 2;
  5. public static void main(String[] args) {
  6. // 抢火车票功能
  7. TrainTicketTest synchronizedTest = new TrainTicketTest();
  8. for (int i = 0; i < 5; i ++) {
  9. new Thread(synchronizedTest.new TrainTicketThread()).start();
  10. }
  11. }
  12. class TrainTicketThread implements Runnable {
  13. @Override
  14. public void run() {
  15. System.out.println(Thread.currentThread().getName() + "running");
  16. try { Thread.sleep(500);} catch (InterruptedException e) { }
  17. synchronized (object) {
  18. if (num > 0) {
  19. System.out.println(Thread.currentThread().getName() + "\t抢到一张票\t目前还有" + num + "张\t");
  20. int temp = num - 1;
  21. num = temp;
  22. System.out.println(Thread.currentThread().getName() + "还剩余" + num + "张");
  23. }
  24. }
  25. }
  26. }
  27. }
  1. public class TrainTicket2Test {
  2. int num = 2;
  3. public static void main(String[] args) {
  4. // 抢火车票功能
  5. TrainTicket2Test synchronizedTest = new TrainTicket2Test();
  6. for (int i = 0; i < 5; i ++) {
  7. new Thread(synchronizedTest.new TrainTicketThread()).start();
  8. }
  9. }
  10. class TrainTicketThread implements Runnable {
  11. @Override
  12. public void run() {
  13. getTrainTicket();
  14. }
  15. }
  16. private synchronized void getTrainTicket() {
  17. System.out.println(Thread.currentThread().getName() + "running");
  18. try { Thread.sleep(500);} catch (InterruptedException e) { }
  19. if (num > 0) {
  20. System.out.println(Thread.currentThread().getName() + "\t抢到一张票\t目前还有" + num + "张\t");
  21. int temp = num - 1;
  22. num = temp;
  23. System.out.println(Thread.currentThread().getName() + "还剩余" + num + "张");
  24. }
  25. }
  26. }

分别是

Thread-0running
Thread-1running
Thread-4running
Thread-3running
Thread-2running
Thread-0    抢到一张票    目前还有2张    
Thread-0还剩余1张
Thread-1    抢到一张票    目前还有1张    
Thread-1还剩余0张

Thread-0running
Thread-0    抢到一张票    目前还有2张    
Thread-0还剩余1张
Thread-4running
Thread-4    抢到一张票    目前还有1张    
Thread-4还剩余0张
Thread-3running
Thread-2running
Thread-1running

三个人在不停的过一个门,一个门一次只能通过一个,每通过一个人就记录通过的总人数及最后通过的人的姓名和地址。

Gate: 模拟门类

  1. public class Gate {
  2. private int counter = 0;
  3. private String name = "Noboday";
  4. private String address = "Nowhere";
  5. public synchronized void pass(String name, String addrees) {
  6. this.counter++;
  7. this.name = name;
  8. this.address = addrees;
  9. check();
  10. }
  11. private void check() {
  12. if (name.charAt(0) != address.charAt(0)) {
  13. System.out.println("****BROKEN****" + toString());
  14. }
  15. }
  16. @Override
  17. public synchronized String toString() {
  18. return "No." + counter + ":" + name + "," + address;
  19. }
  20. }

UserThread: 模拟人不停的过门

  1. public class UserThread extends Thread {
  2. private final Gate gate;
  3. private final String myName;
  4. private final String myAddress;
  5. public UserThread(Gate gate, String myName, String myAddress){
  6. this.gate = gate;
  7. this.myName = myName;
  8. this.myAddress = myAddress;
  9. }
  10. @Override
  11. public void run() {
  12. System.out.println(myName + "BEGIN");
  13. while (true) {
  14. gate.pass(myName, myAddress);
  15. }
  16. }
  17. }

Main: 三个人不停的过门,为了便于区分,每个人的姓名和地址首字母是相同的

  1. public class Main {
  2. public static void main(String[] args) {
  3. Gate gate = new Gate();
  4. // 3个人过的是同一个门gate,即synchronized修饰方法=synchronized(this)=synchronized(gate)
  5. // 3个人是使用的同一把gate锁
  6. new UserThread(gate, "Alice", "Alaska").start();
  7. new UserThread(gate, "Bobby", "Brazil").start();
  8. new UserThread(gate, "Chris", "Canada").start();
  9. }
  10. }

去掉Gate类中的pass和toString方法的synchronized,运行效果如图

  • Alice, Canada: Thread-0先执行this.name = name, 此时name=Chris, 此时CPU切换到Thread-1,然后执行了this.name = name; this.address = address; 此时name=Alice, address= Alaska; 此时CPU切换到Thread-0的address赋值,此时address=Canada, 所以最终的name=Alice,address=Canada, 两个值状态不一致。

  • Bobby,Brazil: 假如Thread-0是Bobby,假如Thread-0先执行this.name = name; 此时name=bobby;此时CPU切换到Thread-1, 假如Thread-1是Chris,Thread分别执行了给name和address分别赋值,此时name=Chris,address=Canada, 然后CPU切回到Thread-0,执行this.address=address, 此时adress为Brazil, 接着Thread-0又执行完了check()方法,此时CPU又切到Thread-1执行,开始执行check()方法,但是这个方法并没有一下子执行完,而是执行到if(name.charAt(0) != address.charAt(0))这个判断的时候又切到Thread-0了,注意此时没有没有执行if体中的代码,只是仅仅执行了条件判断,此时还没有进入if体就被切走了,切到Thread-0执行this.name=name,此时name=Bobby,此时又被切到Thread-1,执行刚才的if体,一打印,结果name=Bobby,address=Brazil, 这种问题的出现就是if判断和if体分开执行了,当再次执行if体的时候结果字段的值改变了

为什么toString方法也加锁?
所有访问或者操作多线程共享的全局变量时都应该加锁。

6. 生产一个消费一个示例

生产一个就要消费一个,消费不完不能生产。此示例生产者和消费者线程使用的是同一个对象锁person

wait和notify通常操作的是锁对象

  • wait的作用:暂停线程执行,将当前线程的状态置为阻塞状态,并释放锁
  • notify的作用:随机将调用notify方法的对象所对应的线程的状态置为就绪状态
  1. public class MQExample {
  2. public static void main(String[] args) {
  3. Person person = new Person();
  4. new Thread(new Producer(person)).start();
  5. new Thread(new Consumer(person)).start();
  6. }
  7. }
  8. class Producer implements Runnable {
  9. private Person person;
  10. public Producer(Person person) {
  11. this.person = person;
  12. }
  13. @Override
  14. public void run() {
  15. synchronized (person) {
  16. int i = 0;
  17. while (true) {
  18. if (i % 2 == 0) {
  19. person.setName("小红");
  20. person.setGender("女");
  21. } else {
  22. person.setName("小明");
  23. person.setGender("男");
  24. }
  25. System.out.println(new Date() + "\t生产者生产了一个对象【" + person.getName() + ", " + person.getGender() +"】");
  26. i++;
  27. // 当生产完一个要释放锁
  28. try {
  29. person.wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println("----------------------------------------------------------");
  34. person.notify();
  35. }
  36. }
  37. }
  38. }
  39. class Consumer implements Runnable {
  40. private Person person;
  41. public Consumer(Person person) {
  42. this.person = person;
  43. }
  44. @Override
  45. public void run() {
  46. synchronized (person) {
  47. while (true) {
  48. System.out.println(new Date() + "\t消费者消费了一个对象《" + person.getName() + ", " + person.getGender() + "》");
  49. person.notify();
  50. try {
  51. person.wait();
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. }
  56. }
  57. }
  58. }
  59. class Person {
  60. private String name;
  61. private String gender;
  62. public String getName() {
  63. return name;
  64. }
  65. public void setName(String name) {
  66. this.name = name;
  67. }
  68. public String getGender() {
  69. return gender;
  70. }
  71. public void setGender(String gender) {
  72. this.gender = gender;
  73. }
  74. }

循环执行以上

该代码比较绕,进行分析一下

  1. 首先启动Producer生产者线程,进入run()方法拿到person锁,执行第一次while循环,打印【小红, 女】,执行person.wait()暂停当前线程执行,释放person对象锁
  2. 因Producer生产者线程在执行person.wait()释放了锁,而且生产者线程还被阻塞了,此时CPU该执行消费者线程Consumer,消费者线程能拿到person对象锁,就可以进入同步代码块从而执行第一个循环打印《小红, 女》,接下来执行person.notify() 会唤醒生产者线程Producer,将生产者线程的状态从阻塞状态设置为就绪状态,再接下来执行消费者的person.wait(),这行代码让消费者线程暂停执行,同时释放person锁
  3. 消费者因执行person.wait()导致消费者线程释放了person对象锁而且使消费者暂停执行,从而CPU就有机会执行生产者线程了,生成者能拿到person对象锁,而且当前状态为就绪状态,就有资格进入运行状态,此时生产者线程会接着上次没有执行完的代码继续执行,所以就执行了打印“——-”,接着执行生产者线程中的person.notify();此行代码的作用是让消费者置为就绪状态,此时因为生产者还持有锁,虽然消费者线程置为就绪状态,因拿不到锁,所以不具备运行的条件。当执行完生产者的person.notify();这行代码时,此时第一轮循环就结束了,接着继续循环第二轮,就继续打印【小明, 男】,然后执行person.wait()暂停当前线程执行,释放person对象锁,就这样步骤2,步骤3 一下循环下去执行

注意:Object.wait()和Object.notify() 只能用在同步方法或者同步代码块中synchronized

3 Lock

Lock比传统线程模型中的synchronized方式更加面向对象,相对于synchronized 方法和语句它具有更广泛的锁定操作,此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

于现实生活中类似,锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的结果,它们必须用同一个Lock对象,锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中。

Lock需要手动加锁,释放锁也需要手动释放,释放锁最好(必须)放到finally代码块中释放。
注意:同一个锁可以加锁多次(调用多次lock() ),相应的加N次锁也必须解N次锁(unlock()),一般情况下都是加一次锁和解一次锁

  1. package java.util.concurrent.locks;
  2. public interface Lock {
  3. // 加锁
  4. void lock();
  5. void lockInterruptibly() throws InterruptedException;
  6. boolean tryLock();
  7. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  8. // 释放锁
  9. void unlock();
  10. Condition newCondition();
  11. }

3.1 重入锁 ReentrantLock

  1. package java.util.concurrent.locks;
  2. public class ReentrantLock implements Lock, java.io.Serializable {
  3. public boolean isLocked();
  4. }
方法声明功能描述
lock()获取锁
tryLock()尝试获取锁
unock()释放锁
newCondition()获取锁的Condition

常用形式如下

  1. public class ReentrantLockTest {
  2. public static void main(String[] args) {
  3. Lock lock = new ReentrantLock();
  4. Runnable runnable = () -> {
  5. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\trunning...");
  6. lock.lock();
  7. try {
  8. for(int i = 0; i < 4; i++) {
  9. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
  10. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  11. }
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. } finally {
  15. lock.unlock();
  16. }
  17. };
  18. new Thread(runnable, "Thread-A").start();
  19. new Thread(runnable, "Thread-B").start();
  20. }
  21. }

使用ReentrantLock和使用synchronized 代码块都能达到上锁的目的。先执行Thread-A线程,当指定到lock.lock()当前线程就拿到锁,然后执行线程体,最后执行lock.unlock()释放锁,这样Thread-B就能拿到线程继续执行。

3.2 读写锁

分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由JVM自己控制的。你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

读写锁的使用情景:

  • 如果代码只读数据,就可以很多人共同读取,但不能同时写。
  • 如果代码修改数据,只能有一个人在写,且不能同时读数据。
  1. public class ReadWriteLockTest {
  2. private volatile static Map<String, Object> cacheMap = new HashMap<>();
  3. private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  4. static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
  5. static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
  6. public static void main(String[] args) {
  7. // 写的时候不能读,读的时候不能写,写的时候不能写,读的时候可以读
  8. new Thread(() -> {
  9. for(int i = 0;i < 5; i++) {
  10. put(i + "", i);
  11. }
  12. }).start();
  13. new Thread(() -> {
  14. for(int i = 0; i < 5; i++) {
  15. get(i + "");
  16. }
  17. }).start();
  18. new Thread(() -> {
  19. for(int i = 0;i < 5; i++) {
  20. put(i + "", i);
  21. }
  22. }).start();
  23. }
  24. public static Object put(String key, Object value) {
  25. try {
  26. writeLock.lock();
  27. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t写\t" + key + "=" + value + "\tstart...");
  28. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  29. cacheMap.put(key, value);
  30. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t写\t" + key + "=" + value + "\tend");
  31. } catch (Exception e) {
  32. } finally {
  33. writeLock.unlock();
  34. }
  35. return value;
  36. }
  37. public static Object get(String key) {
  38. try {
  39. readLock.lock();
  40. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t读\t" + key + "\tstart...");
  41. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  42. Object value = cacheMap.get(key);
  43. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t读\t" + key + "\tend");
  44. return value;
  45. } catch (Exception e) {
  46. } finally {
  47. readLock.unlock();
  48. }
  49. return null;
  50. }
  51. }

4 Condition

用于实现线程间的通信,是为了解决Object.wait()、nitify()、notifyAll()难以使用的问题

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法wait和notify的使用

一个锁内部可以有多个Condition,即有多路等待通知,传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。使用一个监视器往往会产生顾此失彼的情况。

在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

方法声明功能描述
await()线程等待
await(long time, TimeUnit unit)线程等待特定的时间,超过等待时间则为超时
signal()随机唤醒某个等待线程
signalAll()唤醒所有等待中的线程

示例1

Object.wait和notify只能用于synchronized,如果想使用Lock就需要使用Condition来代替

  1. public interface Condition {
  2. // 等待,相当于Object.wait()
  3. void await() throws InterruptedException;
  4. long awaitNanos(long nanosTimeout) throws InterruptedException;
  5. boolean await(long time, TimeUnit unit) throws InterruptedException;
  6. // 唤醒,相当于Object.notify()
  7. void signal();
  8. void signalAll();
  9. }

ConditionTest

  1. public class ConditionTest {
  2. public static void main(String[] args) throws InterruptedException {
  3. // 多个线程必须使用相同的锁和条件
  4. Lock lock = new ReentrantLock();
  5. Condition condition = lock.newCondition();
  6. System.out.println(condition);
  7. new Thread(new MyRunnable(lock, condition), "Thread-A").start();
  8. Thread.sleep(1000);
  9. new Thread(new MyRunnable2(lock, condition), "Thread-B").start();
  10. }
  11. }
  12. class MyRunnable implements Runnable {
  13. private Lock lock;
  14. private Condition condition;
  15. public MyRunnable(Lock lock, Condition condition) {
  16. this.lock = lock;
  17. this.condition = condition;
  18. }
  19. @Override
  20. public void run() {
  21. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t开始等待>>>");
  22. lock.lock();
  23. try {
  24. condition.await();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. } finally {
  28. lock.unlock();
  29. }
  30. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t已 苏 醒^_^");
  31. }
  32. }
  33. class MyRunnable2 implements Runnable {
  34. private Lock lock;
  35. private Condition condition;
  36. public MyRunnable2(Lock lock, Condition condition) {
  37. this.lock = lock;
  38. this.condition = condition;
  39. }
  40. @Override
  41. public void run() {
  42. lock.lock();
  43. try {
  44. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t开始通知...");
  45. condition.signal();
  46. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成。。。");
  47. } catch (Exception e) {
  48. e.printStackTrace();
  49. } finally {
  50. lock.unlock();
  51. }
  52. }
  53. }

这里写图片描述

示例2

一共有3个线程,两个子线程先后循环2次,接着主线程循环3次,接着又回到两 个子线程先后循环2次,再回到主线程又循环3次,如此循环5次。

思路:老二先执行,执行完唤醒老三,老三执行完唤醒老大,老大执行完唤醒老二,以此循环,所以定义3个Condition对象和一个执行标识即可

示例出现的问题:两个文件中有同名类的情况

解决方案:可以将一个文件中的那个同名外部类放进类中,但是静态不能创建内部类的实例对象,所以需要加上static,这样两个类的名称就不一样了。 一个是原来的类名,一个是在自己类名前面加上外部类的类名。

  1. import java.util.concurrent.locks.Condition;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class ThreeConditionCommunication {
  5. public static void main(String[] args){
  6. final Business business = new Business();
  7.    //创建并启动子线程老二
  8. new Thread(new Runnable(){
  9. @Override
  10. public void run() {
  11. for(int i=1;i<=5;i++){
  12. business.sub2(i);
  13. }
  14. }
  15. }).start();
  16.    //创建并启动子线程老三
  17. new Thread(new Runnable(){
  18. @Override
  19. public void run() {
  20. for(int i=1;i<=5;i++){
  21. business.sub3(i);
  22. }
  23. }
  24. }).start();
  25. //主线程
  26. for(int i=1;i<=5;i++){
  27. business.main(i);
  28. }
  29. }
  30. static class Business{
  31. Lock lock = new ReentrantLock();
  32. Condition condition1 = lock.newCondition();
  33. Condition condition2 = lock.newCondition();
  34. Condition condition3 = lock.newCondition();
  35. //定义一个变量来决定线程的执行权
  36. private int ShouldSub = 1;
  37. public void sub2(int i){
  38. //上锁,不让其他线程执行
  39. lock.lock();
  40. try{
  41. if(ShouldSub != 2){ //如果不该老二执行,就等待
  42. try {
  43. condition2.await();
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. for(int j=1;j<=2;j++){
  49. System.out.println("sub thread sequence of"+i+",loop of "+j);
  50. }
  51. ShouldSub = 3; //准备让老三执行
  52. condition3.signal(); //唤醒老三
  53. }finally{
  54. lock.unlock();
  55. }
  56. }
  57. public void sub3(int i){
  58. lock.lock();
  59. try{
  60. if(ShouldSub != 3){
  61. try {
  62. condition3.await();
  63. } catch (InterruptedException e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. for(int j=1;j<=2;j++){
  68. System.out.println("sub2 thread sequence of"+i+",loop of "+j);
  69. }
  70. ShouldSub = 1; //准备让老大执行
  71. condition1.signal(); //唤醒老大
  72. }finally{
  73. lock.unlock();
  74. }
  75. }
  76.    //主线程
  77. public void main(int i){
  78. lock.lock();
  79. try{
  80. if(ShouldSub!=1){
  81. try {
  82. condition1.await();
  83. } catch (InterruptedException e) {
  84. e.printStackTrace();
  85. }
  86. }
  87. for(int j=1;j<=3;j++){
  88. System.out.println("main thread sequence of"+i+", loop of "+j);
  89. }
  90. ShouldSub = 2; //准备让老二执行
  91. condition2.signal(); //唤醒老二
  92. }finally{
  93. lock.unlock();
  94. }
  95. }
  96. }
  97. }
  1. main thread sequence of1, loop of 1
  2. main thread sequence of1, loop of 2
  3. main thread sequence of1, loop of 3
  4. sub thread sequence of1,loop of 1
  5. sub thread sequence of1,loop of 2
  6. sub2 thread sequence of1,loop of 1
  7. sub2 thread sequence of1,loop of 2
  8. main thread sequence of2, loop of 1
  9. main thread sequence of2, loop of 2
  10. main thread sequence of2, loop of 3
  11. sub thread sequence of2,loop of 1
  12. sub thread sequence of2,loop of 2
  13. sub2 thread sequence of2,loop of 1
  14. sub2 thread sequence of2,loop of 2
  15. main thread sequence of3, loop of 1
  16. main thread sequence of3, loop of 2
  17. main thread sequence of3, loop of 3
  18. sub thread sequence of3,loop of 1
  19. sub thread sequence of3,loop of 2
  20. sub2 thread sequence of3,loop of 1
  21. sub2 thread sequence of3,loop of 2
  22. main thread sequence of4, loop of 1
  23. main thread sequence of4, loop of 2
  24. main thread sequence of4, loop of 3
  25. sub thread sequence of4,loop of 1
  26. sub thread sequence of4,loop of 2
  27. sub2 thread sequence of4,loop of 1
  28. sub2 thread sequence of4,loop of 2
  29. main thread sequence of5, loop of 1
  30. main thread sequence of5, loop of 2
  31. main thread sequence of5, loop of 3
  32. sub thread sequence of5,loop of 1
  33. sub thread sequence of5,loop of 2
  34. sub2 thread sequence of5,loop of 1
  35. sub2 thread sequence of5,loop of 2

5 死锁

一个线程中有多个锁,多个线程同时执行,可能线程1获得A锁,线程2获得了B锁,然后线程A想得到B锁就一直等待有人释放,线程2想得到Asuo就等待两个人一直等待,两个线程各自持有一个,而且只有某个线程同时拥有AB锁才能执行,两个人都只得到一个,却谁都不愿意释放,导致两个线程都在等待。
就像一个门同时需要两把钥匙同时打开,张三拿一把,李四拿一把,他们两个谁都不把自己的那把给对方,张三说“李四你先把你的那把给我,我用户就还你”,李四说“张三你先把你的那把给我,我用户就还你”,两个人都比较固执,结果两个人一直僵持着

  1. public class DeadLockTest {
  2. public static String a = "a";
  3. public static String b = "b";
  4. public static void main(String[] args) {
  5. new Thread(new MyTask0()).start();
  6. new Thread(new MyTask1()).start();
  7. }
  8. }
  9. class MyTask0 implements Runnable {
  10. @Override
  11. public void run() {
  12. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");
  13. synchronized (DeadLockTest.a) {
  14. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t first synchronized ...");
  15. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  16. synchronized (DeadLockTest.b) {
  17. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t second synchronized ...");
  18. }
  19. }
  20. }
  21. }
  22. class MyTask1 implements Runnable {
  23. @Override
  24. public void run() {
  25. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t running...");
  26. synchronized (DeadLockTest.b) {
  27. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t first synchronized ...");
  28. try { Thread.sleep(1000); } catch (InterruptedException e) { }
  29. synchronized (DeadLockTest.a) {
  30. System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t second synchronized...");
  31. }
  32. }
  33. }
  34. }

两个线程的第二个代码块都没有执行,main方法也一直在运行状态,没有结束。

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/560582
推荐阅读
相关标签
  

闽ICP备14008679号