线程的同步

在Java中,通过同步机制,来解决线程的安全问题

同步代码块

  1. synchronized(同步监视器){
  2. //需要被同步的代码
  3. }
  4. 说明:操作贡献数据的代码,就是需要被同步的代码
  5. 同步监视器:锁,一般来说任何类的对象都可以作为锁
  6. 要求:多线程必须使用同一把锁
  7. 在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器

同步代码块解决实现接口的线程安全问题

  1. /**
  2. * @author hsy
  3. * @date 2022/12/19
  4. */
  5. class Windows implements Runnable{
  6. private int ticket = 100;
  7. Object bj = new Object();
  8. @Override
  9. public void run() {
  10. while (true){
  11. synchronized (bj) { //也可以用synchronized(this)
  12. if (ticket > 0) {
  13. try {
  14. Thread.sleep(100);
  15. } catch (InterruptedException e) {
  16. throw new RuntimeException(e);
  17. }
  18. System.out.println(Thread.currentThread().getName()+":买票,票号为:" + ticket);
  19. ticket--;
  20. } else {
  21. break;
  22. }
  23. }
  24. }
  25. }
  26. }
  27. public class WindowTest {
  28. public static void main(String[] args) {
  29. Windows w = new Windows();
  30. Thread w2 = new Thread(w);
  31. Thread w3 = new Thread(w);
  32. Thread w1 = new Thread(w);
  33. w1.setName("窗口1");
  34. w2.setName("窗口2");
  35. w3.setName("窗口3");
  36. w1.start();
  37. w2.start();
  38. w3.start();
  39. }
  40. }

同步代码块解决继承的线程安全问题

  1. /**
  2. * 创建多线程:方式二:实现Runnable接口
  3. * @author hsy
  4. * @date 2022/12/19
  5. */
  6. class Window2 extends Thread{
  7. private static int ticket = 100;
  8. private static Object obj = new Object();
  9. @Override
  10. public void run() {
  11. while (true){
  12. synchronized (obj){
  13. if(ticket>0){
  14. try {
  15. Thread.sleep(100);
  16. } catch (InterruptedException e) {
  17. throw new RuntimeException(e);
  18. }
  19. System.out.println(getName()+":买票,票号:"+ticket);
  20. ticket--;
  21. }else {
  22. break;
  23. }
  24. }
  25. }
  26. }
  27. }
  28. public class WindowTest2 {
  29. public static void main(String[] args) {
  30. Window2 t1 = new Window2();
  31. Window2 t2 = new Window2();
  32. Window2 t3 = new Window2();
  33. t1.setName("窗口1");
  34. t2.setName("窗口2");
  35. t3.setName("窗口3");
  36. t1.start();
  37. t2.start();
  38. t3.start();
  39. }
  40. }

同步方法

  1. //在方法声明中加上synchronized
  2. private synchronized void show(){
  3. //方法体
  4. }

同步方法解决实现接口的线程安全问题

  1. /**
  2. * @author hsy
  3. * @date 2022/12/19
  4. */
  5. class Windows3 implements Runnable{
  6. private int ticket = 100;
  7. @Override
  8. public void run() {
  9. while (true){
  10. show();
  11. }
  12. }
  13. private synchronized void show(){
  14. if (ticket > 0) {
  15. try {
  16. Thread.sleep(100);
  17. } catch (InterruptedException e) {
  18. throw new RuntimeException(e);
  19. }
  20. System.out.println(Thread.currentThread().getName()+":买票,票号为:" + ticket);
  21. ticket--;
  22. }
  23. }
  24. }
  25. public class WindowTest3 {
  26. public static void main(String[] args) {
  27. Windows3 w = new Windows3();
  28. Thread w2 = new Thread(w);
  29. Thread w3 = new Thread(w);
  30. Thread w1 = new Thread(w);
  31. w1.setName("窗口1");
  32. w2.setName("窗口2");
  33. w3.setName("窗口3");
  34. w1.start();
  35. w2.start();
  36. w3.start();
  37. }
  38. }

同步方法解决继承的线程安全问题

  1. /**
  2. * @author hsy
  3. * @date 2022/12/19
  4. */
  5. class Window4 extends Thread{
  6. private static int ticket = 100;
  7. @Override
  8. public void run() {
  9. while (true){
  10. show();
  11. }
  12. }
  13. private static synchronized void show(){
  14. if(ticket>0){
  15. try {
  16. Thread.sleep(100);
  17. } catch (InterruptedException e) {
  18. throw new RuntimeException(e);
  19. }
  20. System.out.println(Thread.currentThread().getName()+":买票,票号:"+ticket);
  21. ticket--;
  22. }
  23. }
  24. }
  25. public class WindowTest4 {
  26. public static void main(String[] args) {
  27. Window4 t1 = new Window4();
  28. Window4 t2 = new Window4();
  29. Window4 t3 = new Window4();
  30. t1.setName("窗口1");
  31. t2.setName("窗口2");
  32. t3.setName("窗口3");
  33. t1.start();
  34. t2.start();
  35. t3.start();
  36. }
  37. }

关于同步方法:

  1. 同步方法仍然涉及到同步监视器本身,只是不需要我们显示的声明
  2. 非静态的同步方法,同步监视器是this
    静态的同步方法,同步监视器是:当前类本身

同步的方式解决了线程的安全问题,操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。

死锁问题

死锁:

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就会形成线程的死锁
  • 出现死锁之后不会出现异常,不会出现提示,只是所有的线程都处在阻塞状态,无法继续

解决方法:

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

实例:

  1. /**
  2. * 演示死锁问题
  3. * @author hsy
  4. * @date 2022/12/20
  5. */
  6. public class ThreadTest {
  7. public static void main(String[] args) {
  8. StringBuffer s1 = new StringBuffer();
  9. StringBuffer s2 = new StringBuffer();
  10. new Thread(){
  11. @Override
  12. public void run() {
  13. synchronized (s1){
  14. s1.append("a");
  15. s2.append("1");
  16. try {
  17. Thread.sleep(100);
  18. } catch (InterruptedException e) {
  19. throw new RuntimeException(e);
  20. }
  21. synchronized (s2){
  22. s1.append("b");
  23. s2.append("2");
  24. System.out.println(s1);
  25. System.out.println(s2);
  26. }
  27. }
  28. }
  29. }.start();
  30. new Thread(new Runnable() {
  31. @Override
  32. public void run() {
  33. synchronized (s2){
  34. s1.append("c");
  35. s2.append("3");
  36. try {
  37. Thread.sleep(100);
  38. } catch (InterruptedException e) {
  39. throw new RuntimeException(e);
  40. }
  41. synchronized (s1){
  42. s1.append("d");
  43. s2.append("4");
  44. System.out.println(s1);
  45. System.out.println(s2);
  46. }
  47. }
  48. }
  49. }).start();
  50. }
  51. }

Lock(锁)

  • 从JDK5.0开始,Java提供了更强大的线程同步机制———-通过显示定义同步锁对象来实现对象,同步锁使用Lock对象充当
    • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
    • ReentrantLock实现了Lock,它拥有与synchronized相同的并发性和内存语句,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁

synchronized与Lock的一同

相同:二者都可以解决线程安全问题

不同:synchronized机制在执行完相应的同步代码之后,自动释放代码监视器

  1. Lock需要手动启动同步和关闭同步

image.png