线程的同步

98 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第29天,点击查看活动详情

线程的生命周期

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

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:

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

线程的同步

1.问题提出

1.1多线程出现了安全问题

  • 多个线程执行的不确定性引起执行结果的不稳定
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

1.2问题的原因

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

1.3解决办法

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

2.Synchronized

/**

  • 例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
  • 存在线程安全问题,待解决
  • 1.问题:卖票过程中出现重票和错票--线程安全问题
  • 2.问题出现的原因:当某个线程执行过程中,尚未完成,其他线程参与进来,同时操作导致的
  • 3.如何解决:当一个线程在操作共享数据时,其他线程不能参与进来,直到当前线程操作结束后,
  •     其他线程才能继续操作,即使该线程出现阻塞状态,也不能改变
  • 4.在Java中,我们通过同步机制,来解决线程的安全问题
  •     方式一:同步代码块
  •         synchronized(同步监视器){
  •             //需要被同步的代码
  •         }
  •         说明:1.操作共享数据的代码,即为需要被同步代码
  •               2.共享数据:多个线程共同操作的变量
  •               3.同步监视器,俗称:锁。
  •                 任何一个类的对象都可以充当锁
  •                 要求:多个线程必须要共用同一把锁
  •         补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this来充当同步监视器
  •               在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类来充当同步监视器
  •     方式二:同步方法
  •         如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
  •         总结:
  •             1.同步方法仍然涉及到同步监视器,只是不需要显式的声明
  •             2.非静态的同步方法,同步监视器:this
  •               静态的同步方法,同步监视器:当前类本身
  • 5.同步的方式,解决了线程的安全问题。 ---好处
  •   操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低 ---局限性
  • @author taia
  • @creat 2021-10-12-19:30 */
package com.taiacloud.java;
​

class Window1 implements Runnable{
    private int ticket = 100;
//    Object obj = new Object();
    Dog dog = new Dog();
    @Override
    public void run() {
        while (true){
            synchronized (this){//此时的this:唯一的window1的对象
                // 方式二:synchronized (dog) {
                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 window1 = new Window1();
​
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);
​
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
​
        t1.start();
        t2.start();
        t3.start();
​
    }
}
​
class Dog{
​
}

2.1同步代码块

synchronized (对象){ 
    // 需要被同步的代码;
}

2.2同步方法

synchronized还可以放在方法声明中,表示整个方法为同步方法

public synchronized void show (String name){
    //。。。。。
}

2.3同步机制中的锁

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

2.4同步的范围

2.4.1如何找问题,即代码是否存在线程安全?(非常重要)
  1. 明确哪些代码是多线程运行的代码
  2. 明确多个线程是否有共享数据
  3. 明确多线程运行代码中是否有多条语句操作共享数据
2.4.2如何解决呢?(非常重要)
  1. 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其 他线程不可以参与执行。
  2. 即所有操作共享数据的这些语句都要放在同步范围中
2.4.3切记
  • 范围太小:没锁住所有有安全问题的代码
  • 范围太大:没发挥多线程的功能。

2.5锁操作

2.5.1释放锁操作
  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导 致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。
2.5.2不会释放锁操作
  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程