1、多窗口卖票

  • 三个窗口卖100张票 ```json public class Main { public static void main(String[] args) {
  1. MyRunnable myRunnable = new MyRunnable();
  2. new Thread(myRunnable).start();
  3. new Thread(myRunnable).start();
  4. new Thread(myRunnable).start();
  5. }
  6. static class MyRunnable implements Runnable {
  7. private int ticket = 100;
  8. @Override
  9. public void run() {
  10. while (true) {
  11. if (ticket > 0) {
  12. try {
  13. Thread.sleep(10);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
  18. ticket--;
  19. } else {
  20. System.out.println(Thread.currentThread().getName() + "卖完了");
  21. break;
  22. }
  23. }
  24. }
  25. }

}

  1. - CPU在不同线程间切换,共享数据不是安全的;会出现卖了同一张票、不存在的票问题
  2. - 最终原因:CPU在三个线程间轮流切换执行(例如CPU先执行线程1,执行到某一行代码,CPU又去执行了线程2,这期间的共享数据就是不准确的)<br />
  3. ```json
  4. Thread-0正在卖第3张票
  5. Thread-2正在卖第2张票
  6. Thread-1正在卖第2张票
  7. Thread-1卖完了
  8. Thread-2正在卖第0张票
  9. Thread-2卖完了
  10. Thread-0正在卖第-1张票
  11. Thread-0卖完了
  • 这些线程安全问题,可以通过添加synchronized同步来解决

    2、同步代码块

  • 测试类 ```json public class Main { public static void main(String[] args) {

  1. MyRunnable runnable = new MyRunnable();
  2. new Thread(runnable, "SyncThread1").start();
  3. new Thread(runnable, "SyncThread2").start();
  4. /**
  5. SyncThread1:0
  6. SyncThread1:1
  7. SyncThread1:2
  8. SyncThread1:3
  9. SyncThread1:4
  10. SyncThread2:5
  11. SyncThread2:6
  12. SyncThread2:7
  13. SyncThread2:8
  14. SyncThread2:9
  15. */
  16. // 使用的不同MyRunnable对象,所以不能同步
  17. MyRunnable r1 = new MyRunnable();
  18. MyRunnable r2 = new MyRunnable();
  19. new Thread(r1, "SyncThread1").start();
  20. new Thread(r2, "SyncThread2").start();
  21. /**
  22. SyncThread1:1
  23. SyncThread2:0
  24. SyncThread2:3
  25. SyncThread1:2
  26. SyncThread2:4
  27. SyncThread1:5
  28. SyncThread1:6
  29. SyncThread2:6
  30. SyncThread2:7
  31. SyncThread1:7
  32. */
  33. }
  34. static class MyRunnable implements Runnable {
  35. // 此处是静态变量,归类所有,所有的实例访问是同一个
  36. public static int count;
  37. public MyRunnable() {
  38. count = 0;
  39. }
  40. public void run() {
  41. // 此处锁的是MyRunnable对象
  42. synchronized (this) {
  43. for (int i = 0; i < 5; i++) {
  44. try {
  45. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  46. Thread.sleep(100);
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. }
  52. }
  53. }

}

  1. - 结论
  2. - synchronized (this):锁的是当前MyRunnable对象
  3. - 例子一结论:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞<br />
  4. - 例子二结论:synchronized锁定的是对象,这时会有两把锁分别锁定r1对象和r2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行<br />
  5. <a name="1cAMQ"></a>
  6. ### 3、同步非静态方法
  7. - synchronized修饰一个非静态方法,锁的是Runnable的一个对象<br />
  8. - synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数<br />
  9. ```json
  10. public class Main {
  11. public static void main(String[] args) {
  12. MyRunnable runnable = new MyRunnable();
  13. new Thread(runnable, "SyncThread1").start();
  14. new Thread(runnable, "SyncThread2").start();
  15. /**
  16. SyncThread1:0
  17. SyncThread1:1
  18. SyncThread1:2
  19. SyncThread1:3
  20. SyncThread1:4
  21. SyncThread2:5
  22. SyncThread2:6
  23. SyncThread2:7
  24. SyncThread2:8
  25. SyncThread2:9
  26. */
  27. // 使用的不同MyRunnable对象,所以不能同步
  28. MyRunnable r1 = new MyRunnable();
  29. MyRunnable r2 = new MyRunnable();
  30. new Thread(r1, "SyncThread1").start();
  31. new Thread(r2, "SyncThread2").start();
  32. /**
  33. SyncThread2:0
  34. SyncThread1:1
  35. SyncThread1:2
  36. SyncThread2:2
  37. SyncThread2:3
  38. SyncThread1:3
  39. SyncThread1:4
  40. SyncThread2:5
  41. SyncThread1:6
  42. SyncThread2:6
  43. */
  44. }
  45. static class MyRunnable implements Runnable {
  46. public static int count;
  47. public MyRunnable() {
  48. count = 0;
  49. }
  50. public synchronized void run() {
  51. // 此处锁的是MyRunnable对象
  52. for (int i = 0; i < 5; i++) {
  53. try {
  54. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  55. Thread.sleep(100);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. }
  61. }
  62. }
  • 结论

    • synchronized void run():锁的是当前MyRunnable对象
    • 例子一结论:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
    • 例子二结论:synchronized锁定的是对象,这时会有两把锁分别锁定r1对象和r2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行

      4、同步静态方法

  • synchronized修饰一个静态方法,锁的是Runnable类

  • thread1和thread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以thread1和thread2相当于用了同一把锁

    1. public class Main {
    2. public static void main(String[] args) {
    3. MyRunnable runnable = new MyRunnable();
    4. new Thread(runnable, "SyncThread1").start();
    5. new Thread(runnable, "SyncThread2").start();
    6. /**
    7. SyncThread1:0
    8. SyncThread1:1
    9. SyncThread1:2
    10. SyncThread1:3
    11. SyncThread1:4
    12. SyncThread2:5
    13. SyncThread2:6
    14. SyncThread2:7
    15. SyncThread2:8
    16. SyncThread2:9
    17. */
    18. MyRunnable r1 = new MyRunnable();
    19. MyRunnable r2 = new MyRunnable();
    20. new Thread(r1, "SyncThread1").start();
    21. new Thread(r2, "SyncThread2").start();
    22. /**
    23. SyncThread1:0
    24. SyncThread1:1
    25. SyncThread1:2
    26. SyncThread1:3
    27. SyncThread1:4
    28. SyncThread2:5
    29. SyncThread2:6
    30. SyncThread2:7
    31. SyncThread2:8
    32. SyncThread2:9
    33. */
    34. }
    35. static class MyRunnable implements Runnable {
    36. public static int count;
    37. public MyRunnable() {
    38. count = 0;
    39. }
    40. public void run() {
    41. method();
    42. }
    43. // 此处锁的是MyRunnable类,包含所有类实例
    44. private synchronized static void method() {
    45. for (int i = 0; i < 5; i++) {
    46. try {
    47. System.out.println(Thread.currentThread().getName() + ":" + (count++));
    48. Thread.sleep(100);
    49. } catch (InterruptedException e) {
    50. e.printStackTrace();
    51. }
    52. }
    53. }
    54. }
    55. }
  • 结论

    • r1和r2是MyRunnable的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁

5、同步类

  • synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁
    ```json public class Main { public static void main(String[] args) {

// MyRunnable runnable = new MyRunnable(); // new Thread(runnable, “SyncThread1”).start(); // new Thread(runnable, “SyncThread2”).start();

  1. /**
  2. SyncThread1:0
  3. SyncThread1:1
  4. SyncThread1:2
  5. SyncThread1:3
  6. SyncThread1:4
  7. SyncThread2:5
  8. SyncThread2:6
  9. SyncThread2:7
  10. SyncThread2:8
  11. SyncThread2:9
  12. */
  13. MyRunnable r1 = new MyRunnable();
  14. MyRunnable r2 = new MyRunnable();
  15. new Thread(r1, "SyncThread1").start();
  16. new Thread(r2, "SyncThread2").start();
  17. /**
  18. SyncThread1:0
  19. SyncThread1:1
  20. SyncThread1:2
  21. SyncThread1:3
  22. SyncThread1:4
  23. SyncThread2:5
  24. SyncThread2:6
  25. SyncThread2:7
  26. SyncThread2:8
  27. SyncThread2:9
  28. */
  29. }
  30. static class MyRunnable implements Runnable {
  31. public static int count;
  32. public MyRunnable() {
  33. count = 0;
  34. }
  35. public void run() {
  36. synchronized(MyRunnable.class){
  37. for (int i = 0; i < 5; i++) {
  38. try {
  39. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  40. Thread.sleep(100);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. }
  47. }

}

  1. <a name="c3d3b779"></a>
  2. ### 5、synchronized使用限制
  3. - synchronized关键字不能继承<br />
  4. - 如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以<br />
  5. ```json
  6. class Parent {
  7. public synchronized void method() { }
  8. }
  9. class Child extends Parent {
  10. public synchronized void method() { }
  11. }
  • 当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了

    1. class Parent {
    2. public synchronized void method() { }
    3. }
    4. class Child extends Parent {
    5. public void method() { super.method(); }
    6. }
  • 在定义接口方法时不能使用synchronized关键字

    1. interface MyClick{
    2. // 此处不允许使用修饰符synchronized
    3. synchronized void add();
    4. }
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步