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

简介: synchronized与Lock的对比、线程的死锁问题、JDK中用Thread.State类定义了线程的几种状态、新建,就绪,运行,阻塞,死亡、使用同步代码块、同步方法解决实现Runnable接口的线程安全问题、使用同步代码块、同步方法处理继承Thread类的方式中的线程安全问题、使用同步机制将单例模式中的懒汉式改写为线程安全的、演示线程的死锁问题、解决线程安全问题的方式三:Lock锁-JDK5.0新增、Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释

@[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

目录
相关文章
|
7天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
|
7天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
7天前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
18 4
|
6天前
|
NoSQL 网络协议 Unix
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
17 1
|
9天前
|
安全 Java API
Java根据URL获取文件内容的实现方法
此示例展示了如何安全、有效地根据URL获取文件内容。它不仅展现了处理网络资源的基本技巧,还体现了良好的异常处理实践。在实际开发中,根据项目需求,你可能还需要添加额外的功能,如设置连接超时、处理HTTP响应码等。
44 4
|
10天前
|
存储 设计模式 安全
Java设计模式-备忘录模式(23)
Java设计模式-备忘录模式(23)
|
9天前
|
Python
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
|
9天前
|
Java 调度
Java-Thread多线程的使用
这篇文章介绍了Java中Thread类多线程的创建、使用、生命周期、状态以及线程同步和死锁的概念和处理方法。
Java-Thread多线程的使用
|
10天前
|
设计模式 存储 缓存
Java设计模式 - 解释器模式(24)
Java设计模式 - 解释器模式(24)
|
7天前
|
Java 数据中心 微服务
Java高级知识:线程池隔离与信号量隔离的实战应用
在Java并发编程中,线程池隔离与信号量隔离是两种常用的资源隔离技术,它们在提高系统稳定性、防止系统过载方面发挥着重要作用。
6 0
下一篇
无影云桌面