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

相关文章
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
19 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
31 2
|
13天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
27 4
|
6月前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
65 4
|
3月前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
42 11
|
4月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
3月前
|
设计模式 Java 安全
Java设计模式-单例模式(2)
Java设计模式-单例模式(2)
|
7月前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
4月前
|
设计模式 SQL 缓存
Java编程中的设计模式:单例模式的深入理解与应用
【8月更文挑战第22天】 在Java的世界里,设计模式是构建可维护、可扩展和灵活的软件系统的基石。本文将深入浅出地探讨单例模式这一经典设计模式,揭示其背后的哲学思想,并通过实例演示如何在Java项目中有效运用。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇洞悉软件设计深层逻辑的大门。
35 0