Java多线程(二)、线程的生命周期、线程的同步、Synchronized的使用方法、同步代码块、同步方法、同步机制中的锁、同步的范围、Lock(锁、不会释放锁的操作、单例设计模式之懒汉式(线程安全)

简介: Java多线程(二)、线程的生命周期、线程的同步、Synchronized的使用方法、同步代码块、同步方法、同步机制中的锁、同步的范围、Lock(锁、不会释放锁的操作、单例设计模式之懒汉式(线程安全)

@[toc]

1.多线程

1.3线程的生命周期

1.3.1JDK中用Thread.State类定义了线程的几种状态

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

1.3.2线程的生命周期

在这里插入图片描述

1.4线程的同步

1.4.1问题的提出

多个线程执行的不确定性引起执行结果的不稳定
多个线程对账本的共享,会造成操作的不完整性,会破坏数据
在这里插入图片描述

1.4.2例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式

1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

package com.my.java;

/**
 * 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
 *
 * @author Redamancy
 * @create 2022-08-06 15:41
 */

class Window1 implements Runnable{
    private int ticket = 100;



    @Override
    public void run() {


        while (true){
            if(ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }

}

在这里插入图片描述

  1. 多线程出现了安全问题
  2. 问题的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

  1. 解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

1.4.3Synchronized的使用方法

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

1.4.3.1方式一:同步代码块

   synchronized(同步监视器){
      //需要被同步的代码
   }

说明:

  1. 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
  1. 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
  2. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
  3. 要求:多个线程必须要共用同一把锁
  4. 同步的方式,解决了线程的安全问题。---好处

操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性

1.4.3.1.1使用同步代码块解决实现Runnable接口的线程安全问题
package com.my.java;

/**
 * @author Redamancy
 * @create 2022-08-06 15:41
 */

class Window1 implements Runnable{
    private int ticket = 100;
//    Object obj = new Object();//第一种方式,new一个object类
//    Dog dog = new Dog();// 第二种方式创建一个Dog类,然后new一下


    @Override
    public void run() {


        while (true){
//            synchronized(obj) {//第一种方式,new一个object类  obj
//            synchronized(dog) {// 第二种方式创建一个Dog类,然后new一下  dog
            synchronized(this) { //此时的this:唯一的Window1的对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }

}
class Dog{

}
窗口1:买票,票号为:100
窗口1:买票,票号为:99
窗口1:买票,票号为:98
窗口1:买票,票号为:97
窗口1:买票,票号为:96
窗口1:买票,票号为:95
窗口3:买票,票号为:94
窗口3:买票,票号为:93
窗口3:买票,票号为:92
窗口3:买票,票号为:91
窗口3:买票,票号为:90
窗口2:买票,票号为:89
窗口2:买票,票号为:88
窗口2:买票,票号为:87
窗口2:买票,票号为:86
窗口2:买票,票号为:85
窗口2:买票,票号为:84
窗口2:买票,票号为:83
窗口2:买票,票号为:82
窗口2:买票,票号为:81
窗口2:买票,票号为:80
窗口2:买票,票号为:79
窗口2:买票,票号为:78
窗口2:买票,票号为:77
窗口3:买票,票号为:76
窗口3:买票,票号为:75
窗口3:买票,票号为:74
窗口3:买票,票号为:73
窗口3:买票,票号为:72
窗口3:买票,票号为:71
窗口3:买票,票号为:70
窗口3:买票,票号为:69
窗口3:买票,票号为:68
窗口3:买票,票号为:67
窗口3:买票,票号为:66
窗口3:买票,票号为:65
窗口3:买票,票号为:64
窗口1:买票,票号为:63
窗口1:买票,票号为:62
窗口1:买票,票号为:61
窗口1:买票,票号为:60
窗口1:买票,票号为:59
窗口1:买票,票号为:58
窗口1:买票,票号为:57
窗口1:买票,票号为:56
窗口1:买票,票号为:55
窗口1:买票,票号为:54
窗口1:买票,票号为:53
窗口1:买票,票号为:52
窗口1:买票,票号为:51
窗口1:买票,票号为:50
窗口3:买票,票号为:49
窗口3:买票,票号为:48
窗口3:买票,票号为:47
窗口3:买票,票号为:46
窗口3:买票,票号为:45
窗口3:买票,票号为:44
窗口3:买票,票号为:43
窗口3:买票,票号为:42
窗口3:买票,票号为:41
窗口3:买票,票号为:40
窗口3:买票,票号为:39
窗口2:买票,票号为:38
窗口2:买票,票号为:37
窗口3:买票,票号为:36
窗口3:买票,票号为:35
窗口3:买票,票号为:34
窗口3:买票,票号为:33
窗口3:买票,票号为:32
窗口3:买票,票号为:31
窗口1:买票,票号为:30
窗口3:买票,票号为:29
窗口2:买票,票号为:28
窗口2:买票,票号为:27
窗口3:买票,票号为:26
窗口3:买票,票号为:25
窗口3:买票,票号为:24
窗口3:买票,票号为:23
窗口3:买票,票号为:22
窗口3:买票,票号为:21
窗口3:买票,票号为:20
窗口3:买票,票号为:19
窗口3:买票,票号为:18
窗口3:买票,票号为:17
窗口3:买票,票号为:16
窗口3:买票,票号为:15
窗口3:买票,票号为:14
窗口3:买票,票号为:13
窗口3:买票,票号为:12
窗口3:买票,票号为:11
窗口3:买票,票号为:10
窗口3:买票,票号为:9
窗口3:买票,票号为:8
窗口3:买票,票号为:7
窗口3:买票,票号为:6
窗口3:买票,票号为:5
窗口3:买票,票号为:4
窗口3:买票,票号为:3
窗口3:买票,票号为:2
窗口3:买票,票号为:1

Process finished with exit code 0

补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

1.4.3.1.2使用同步代码块处理继承Thread类的方式中的线程安全问题
package com.my.java;

/**
 * @author Redamancy
 * @create 2022-08-07 10:34
 */
//1. 创建一个继承于Thread类的子类
class Window2 extends Thread {
    private static int ticket = 100;

    private static Object obj = new Object();

    //2. 重写Thread类的run()
    @Override
    public void run() {
        while (true){
            synchronized(obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();


    }
}

在这里插入图片描述

说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

1.4.3.2方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

关于同步方法的总结:

  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

    1. 非静态的同步方法,同步监视器是:this

    静态的同步方法,同步监视器是:当前类本身

1.4.3.2.1使用同步方法解决实现Runnable接口的线程安全问题
package com.my.java;

/**
 * @author Redamancy
 * @create 2022-08-07 16:18
 */
class Window3 implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {


        while (true){
                show();
        }
    }
    private synchronized void show(){//同步监视器:this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
            ticket--;
        }
    }
}

public class WindowTest3{
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }

}
窗口1:买票,票号为:100
窗口1:买票,票号为:99
窗口1:买票,票号为:98
窗口1:买票,票号为:97
窗口1:买票,票号为:96
窗口1:买票,票号为:95
窗口1:买票,票号为:94
窗口1:买票,票号为:93
窗口1:买票,票号为:92
窗口1:买票,票号为:91
窗口1:买票,票号为:90
窗口1:买票,票号为:89
窗口1:买票,票号为:88
窗口1:买票,票号为:87
窗口1:买票,票号为:86
窗口1:买票,票号为:85
窗口1:买票,票号为:84
窗口1:买票,票号为:83
窗口1:买票,票号为:82
窗口1:买票,票号为:81
窗口1:买票,票号为:80
窗口1:买票,票号为:79
窗口1:买票,票号为:78
窗口1:买票,票号为:77
窗口1:买票,票号为:76
窗口3:买票,票号为:75
窗口3:买票,票号为:74
窗口3:买票,票号为:73
窗口3:买票,票号为:72
窗口3:买票,票号为:71
窗口3:买票,票号为:70
窗口3:买票,票号为:69
窗口3:买票,票号为:68
窗口3:买票,票号为:67
窗口3:买票,票号为:66
窗口3:买票,票号为:65
窗口3:买票,票号为:64
窗口3:买票,票号为:63
窗口3:买票,票号为:62
窗口3:买票,票号为:61
窗口3:买票,票号为:60
窗口3:买票,票号为:59
窗口2:买票,票号为:58
窗口2:买票,票号为:57
窗口2:买票,票号为:56
窗口2:买票,票号为:55
窗口2:买票,票号为:54
窗口2:买票,票号为:53
窗口2:买票,票号为:52
窗口2:买票,票号为:51
窗口2:买票,票号为:50
窗口2:买票,票号为:49
窗口2:买票,票号为:48
窗口2:买票,票号为:47
窗口2:买票,票号为:46
窗口2:买票,票号为:45
窗口2:买票,票号为:44
窗口2:买票,票号为:43
窗口2:买票,票号为:42
窗口2:买票,票号为:41
窗口2:买票,票号为:40
窗口2:买票,票号为:39
窗口2:买票,票号为:38
窗口2:买票,票号为:37
窗口2:买票,票号为:36
窗口2:买票,票号为:35
窗口2:买票,票号为:34
窗口2:买票,票号为:33
窗口2:买票,票号为:32
窗口2:买票,票号为:31
窗口2:买票,票号为:30
窗口2:买票,票号为:29
窗口2:买票,票号为:28
窗口2:买票,票号为:27
窗口2:买票,票号为:26
窗口2:买票,票号为:25
窗口2:买票,票号为:24
窗口2:买票,票号为:23
窗口2:买票,票号为:22
窗口2:买票,票号为:21
窗口2:买票,票号为:20
窗口2:买票,票号为:19
窗口2:买票,票号为:18
窗口2:买票,票号为:17
窗口2:买票,票号为:16
窗口2:买票,票号为:15
窗口2:买票,票号为:14
窗口2:买票,票号为:13
窗口2:买票,票号为:12
窗口2:买票,票号为:11
窗口2:买票,票号为:10
窗口2:买票,票号为:9
窗口2:买票,票号为:8
窗口2:买票,票号为:7
窗口2:买票,票号为:6
窗口2:买票,票号为:5
窗口2:买票,票号为:4
窗口2:买票,票号为:3
窗口2:买票,票号为:2
窗口2:买票,票号为:1

Process finished with exit code 130

在这里插入图片描述

1.4.3.2.2使用同步方法处理继承Thread类的方式中的线程安全问题
package com.my.java;

/**
 * @author Redamancy
 * @create 2022-08-07 16:28
 */
class Window4 extends Thread {
    private static int ticket = 100;

    //2. 重写Thread类的run()
    @Override
    public void run() {
        while (true){
                show();
        }
    }
    private static synchronized void show(){//同步监视器:Window4.class
        //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
            ticket--;
        }
    }
}

public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();


    }
}
窗口1:买票,票号为:100
窗口1:买票,票号为:99
窗口1:买票,票号为:98
窗口1:买票,票号为:97
窗口3:买票,票号为:96
窗口3:买票,票号为:95
窗口3:买票,票号为:94
窗口3:买票,票号为:93
窗口3:买票,票号为:92
窗口3:买票,票号为:91
窗口3:买票,票号为:90
窗口3:买票,票号为:89
窗口3:买票,票号为:88
窗口3:买票,票号为:87
窗口3:买票,票号为:86
窗口3:买票,票号为:85
窗口3:买票,票号为:84
窗口3:买票,票号为:83
窗口2:买票,票号为:82
窗口2:买票,票号为:81
窗口2:买票,票号为:80
窗口2:买票,票号为:79
窗口2:买票,票号为:78
窗口2:买票,票号为:77
窗口2:买票,票号为:76
窗口2:买票,票号为:75
窗口2:买票,票号为:74
窗口2:买票,票号为:73
窗口2:买票,票号为:72
窗口2:买票,票号为:71
窗口2:买票,票号为:70
窗口2:买票,票号为:69
窗口2:买票,票号为:68
窗口2:买票,票号为:67
窗口2:买票,票号为:66
窗口2:买票,票号为:65
窗口2:买票,票号为:64
窗口2:买票,票号为:63
窗口2:买票,票号为:62
窗口2:买票,票号为:61
窗口2:买票,票号为:60
窗口2:买票,票号为:59
窗口2:买票,票号为:58
窗口2:买票,票号为:57
窗口2:买票,票号为:56
窗口2:买票,票号为:55
窗口2:买票,票号为:54
窗口2:买票,票号为:53
窗口2:买票,票号为:52
窗口2:买票,票号为:51
窗口2:买票,票号为:50
窗口2:买票,票号为:49
窗口2:买票,票号为:48
窗口2:买票,票号为:47
窗口2:买票,票号为:46
窗口2:买票,票号为:45
窗口2:买票,票号为:44
窗口2:买票,票号为:43
窗口2:买票,票号为:42
窗口3:买票,票号为:41
窗口3:买票,票号为:40
窗口3:买票,票号为:39
窗口3:买票,票号为:38
窗口3:买票,票号为:37
窗口3:买票,票号为:36
窗口3:买票,票号为:35
窗口3:买票,票号为:34
窗口3:买票,票号为:33
窗口3:买票,票号为:32
窗口1:买票,票号为:31
窗口1:买票,票号为:30
窗口1:买票,票号为:29
窗口1:买票,票号为:28
窗口1:买票,票号为:27
窗口1:买票,票号为:26
窗口1:买票,票号为:25
窗口1:买票,票号为:24
窗口1:买票,票号为:23
窗口1:买票,票号为:22
窗口1:买票,票号为:21
窗口1:买票,票号为:20
窗口1:买票,票号为:19
窗口1:买票,票号为:18
窗口1:买票,票号为:17
窗口1:买票,票号为:16
窗口1:买票,票号为:15
窗口1:买票,票号为:14
窗口1:买票,票号为:13
窗口1:买票,票号为:12
窗口1:买票,票号为:11
窗口1:买票,票号为:10
窗口1:买票,票号为:9
窗口1:买票,票号为:8
窗口1:买票,票号为:7
窗口1:买票,票号为:6
窗口1:买票,票号为:5
窗口1:买票,票号为:4
窗口1:买票,票号为:3
窗口1:买票,票号为:2
窗口1:买票,票号为:1

Process finished with exit code 130

在这里插入图片描述

1.4.4同步机制中的锁

同步锁机制:
在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
synchronized的锁是什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
注意:
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

1.4.5同步的范围

  1. 如何找问题,即代码是否存在线程安全?(非常重要)

(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据

  1. 如何解决呢?(非常重要)

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中

  1. 切记:

范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。

1.4.6释放锁的操作

当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

1.4.7不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程

1.4.8单例设计模式之懒汉式(线程安全)

1.4.8.1使用同步机制将单例模式中的懒汉式改写为线程安全的

1.4.8.1.1方式一:效率稍差
package com.my.java1;

/**
 * @author Redamancy
 * @create 2022-08-07 16:44
 */
class Bank{
    private Bank(){}
    private static Bank instance = null;
    
    public static Bank getInstance(){
        //方式一:效率稍差
        synchronized(Bank.class){
            if(instance == null){
                instance = new Bank();
            }
            return instance;
        }
    }
}

public class BankTest {
}
1.4.8.1.2方式二:效率更高
package com.my.java1;

/**
 * @author Redamancy
 * @create 2022-08-07 16:44
 */
class Bank{
    private Bank(){}
    private static Bank instance = null;

    public static Bank getInstance(){
//        //方式一:效率稍差
//        synchronized(Bank.class){
//            if(instance == null){
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if(instance==null){
            synchronized(Bank.class){
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

public class BankTest {
}

1.4.9线程的死锁问题

死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

1.4.9.1演示线程的死锁问题

  1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  2. 说明:
    1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

2)我们使用同步时,要避免出现死锁。

package com.my.java1;

/**
 * @author Redamancy
 * @create 2022-08-07 17:22
 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread(){
            @Override
            public void run() {
                synchronized(s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

在这里插入图片描述

在这里插入图片描述
注意看这里的s1和s2的先后顺序

1.4.10Lock(锁)

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

1.4.10.1解决线程安全问题的方式三:Lock锁 --- JDK5.0新增

package com.my.java1;


import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Redamancy
 * @create 2022-08-07 21:44
 */
class Window implements Runnable{
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){

            try {
                //2.调用锁定方法lock()
                lock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            } finally {
                //3.调用解锁方法:umlock()
                lock.unlock();

            }
        }
    }
}

public class LockTest{
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }

}

在这里插入图片描述

窗口1:买票,票号为:100
窗口1:买票,票号为:99
窗口1:买票,票号为:98
窗口1:买票,票号为:97
窗口1:买票,票号为:96
窗口1:买票,票号为:95
窗口1:买票,票号为:94
窗口1:买票,票号为:93
窗口1:买票,票号为:92
窗口1:买票,票号为:91
窗口1:买票,票号为:90
窗口1:买票,票号为:89
窗口1:买票,票号为:88
窗口1:买票,票号为:87
窗口1:买票,票号为:86
窗口1:买票,票号为:85
窗口1:买票,票号为:84
窗口1:买票,票号为:83
窗口1:买票,票号为:82
窗口1:买票,票号为:81
窗口1:买票,票号为:80
窗口1:买票,票号为:79
窗口1:买票,票号为:78
窗口1:买票,票号为:77
窗口1:买票,票号为:76
窗口1:买票,票号为:75
窗口1:买票,票号为:74
窗口1:买票,票号为:73
窗口1:买票,票号为:72
窗口1:买票,票号为:71
窗口1:买票,票号为:70
窗口1:买票,票号为:69
窗口2:买票,票号为:68
窗口2:买票,票号为:67
窗口2:买票,票号为:66
窗口2:买票,票号为:65
窗口2:买票,票号为:64
窗口2:买票,票号为:63
窗口2:买票,票号为:62
窗口2:买票,票号为:61
窗口2:买票,票号为:60
窗口2:买票,票号为:59
窗口2:买票,票号为:58
窗口2:买票,票号为:57
窗口2:买票,票号为:56
窗口2:买票,票号为:55
窗口2:买票,票号为:54
窗口2:买票,票号为:53
窗口2:买票,票号为:52
窗口2:买票,票号为:51
窗口2:买票,票号为:50
窗口2:买票,票号为:49
窗口2:买票,票号为:48
窗口2:买票,票号为:47
窗口2:买票,票号为:46
窗口2:买票,票号为:45
窗口2:买票,票号为:44
窗口2:买票,票号为:43
窗口2:买票,票号为:42
窗口2:买票,票号为:41
窗口2:买票,票号为:40
窗口2:买票,票号为:39
窗口2:买票,票号为:38
窗口2:买票,票号为:37
窗口2:买票,票号为:36
窗口2:买票,票号为:35
窗口2:买票,票号为:34
窗口2:买票,票号为:33
窗口2:买票,票号为:32
窗口2:买票,票号为:31
窗口2:买票,票号为:30
窗口2:买票,票号为:29
窗口2:买票,票号为:28
窗口2:买票,票号为:27
窗口2:买票,票号为:26
窗口2:买票,票号为:25
窗口2:买票,票号为:24
窗口2:买票,票号为:23
窗口2:买票,票号为:22
窗口2:买票,票号为:21
窗口2:买票,票号为:20
窗口2:买票,票号为:19
窗口2:买票,票号为:18
窗口2:买票,票号为:17
窗口2:买票,票号为:16
窗口2:买票,票号为:15
窗口2:买票,票号为:14
窗口2:买票,票号为:13
窗口2:买票,票号为:12
窗口2:买票,票号为:11
窗口2:买票,票号为:10
窗口2:买票,票号为:9
窗口2:买票,票号为:8
窗口2:买票,票号为:7
窗口2:买票,票号为:6
窗口2:买票,票号为:5
窗口2:买票,票号为:4
窗口2:买票,票号为:3
窗口2:买票,票号为:2
窗口2:买票,票号为:1

Process finished with exit code 0

1.4.10.2核心代码

class A{
    private final ReentrantLock lock = new ReenTrantLock();
    public void m(){
        try{
            lock.lock();
            //保证线程安全的代码; 
        }finally{
            lock.unlock(); 
        }
    } 
}

注意:如果同步代码有异常,要将unlock()写入finally语句块

1.4.11synchronized与Lock的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)

想看完整版的多线程看我发的博客https://blog.csdn.net/Redamancy06/article/details/126229545?spm=1001.2014.3001.5502

相关文章
|
14天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
14天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
12 2
|
14天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
12 1
|
14天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
26 1
|
14天前
|
Java
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅。它们用于线程间通信,使线程能够协作完成任务。通过这些方法,生产者和消费者线程可以高效地管理共享资源,确保程序的有序运行。正确使用这些方法需要遵循同步规则,避免虚假唤醒等问题。示例代码展示了如何在生产者-消费者模型中使用`wait()`和`notify()`。
22 1
|
14天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
26 1
|
14天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
21 1
|
18天前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
17 3
|
18天前
|
监控 Java
捕获线程执行异常的多种方法
【10月更文挑战第15天】捕获线程执行异常的方法多种多样,每种方法都有其特点和适用场景。在实际开发中,需要根据具体情况选择合适的方法或结合多种方法来实现全面有效的线程异常捕获。这有助于提高程序的健壮性和稳定性,减少因线程异常带来的潜在风险。
13 1
|
21天前
|
JavaScript 前端开发 安全
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
35 0