1、关于线程安全

通过同步机制,synchronized 关键字,可以解决了线程的安全问题,操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低,这也是同步机制的局限性。

  • Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性;
  • 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象;
  • 关键字 synchronized 与对象的互斥锁的联系:当某个对象用 synchronized 修饰时,表明该对象在任意时刻只能由一个线程访问;
  • 同步的局限性:由于同步机制将多线程变成了一个串行化的过程,导致程序的执行效率降低;
  • 同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是同一个对象);
  • 同步方法(静态的)的锁为当前类本身;

补充一下:

  • 同步方法如果没有用 static 修饰,默认锁对象为 this;
  • 如果方法使用 static 修饰,默认锁对象为:当前类.class;

2、如何解决线程安全问题?

2.1、同步代码块

使用 synchronized 修饰代码块

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

说明
1、操作共享数据的代码,即为需要被同步的代码
2、共享数据:多个线程共同操作的变量。比如ticket就是共享数据
3、同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。(多个线程必须要公用同一把锁)

2.2、同步方法

使用 synchronized 修饰方法

public synchronized void f1() {//同步监视器:this
    // 需要被同步的代码
}

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

2.3:Lock锁方式

3、例子

以卖票为例,三个窗口共同售卖100张门票。

3.1、非线程安全的实现方法

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

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

        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 Windows implements Runnable {
    private int ticket = 100;

    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

3.2、线程安全的实现方法

0x1 同步代码块

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

        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 Windows implements Runnable {
    private int ticket = 100;
    Object obj = new Object();

    public void run() {
        while (true) {
            //可以考虑用this
            synchronized (obj) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

0x2 同步方法

实现runnable接口

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

        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 Windows implements Runnable {
    private int ticket = 100;

    public void run() {
        while (true) {
            sell();
        }
    }

    public synchronized void sell() {//同步监视器:this
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
            ticket--;
        }
    }
}

继承Thread类

public class Main {
    public static void main(String[] args) {
        Thread t1 = new Windows();
        Thread t2 = new Windows();
        Thread t3 = new Windows();

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

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

class Windows extends Thread {
    private static int ticket = 100;

    public void run() {
        while (true) {
            sell();
        }
    }

    public static synchronized void sell() {//同步监视器:this
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
            ticket--;
        }
    }
}