Java基础——线程同步

1.同步/异步编程

异步编程模型:t1线程与t2线程各自执行,两个线程之间互不干扰;

同步编程模型:t1线程与t2线程执行,当t1线程必须等t2线程结束之后才能执行,即同步编程模型。

2.什么时候进行同步,为什么引入同步机制

  1. 为了数据的安全性,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制,线程同步机制使程序变成了(等同)单线程;
  2. 什么条件下使用线程同步:必须是多线程环境;多线程环境共享同一个数据;共享的数据涉及到修改操作。

3.线程安全问题

  1. 当多个线程同时操作堆区或方法区的同一个数据时,可能导致数据不一致的现象,称为线程安全问题。
  2. 线程安全问题解决方式:

                (1)每个线程都访问自己的局部变量,不会产生线程安全问题;

                (2)如果多线程必须同时操作堆区或方法区同一个数据时,可以采用线程同步机制。

4.线程同步

4.1synchronized同步代码块:

语法格式:

synchronized(锁对象){

代码块

}

原理:

(1)任何对象都可以作为对象锁,每一个对象都有一个内置锁;

(2)线程想要执行同步代码块,必须先获得锁对象;

(3)线程a获得锁对象,可以执行同步代码块,会一直持有这个锁对象,直到同步代码块执行完释放对象锁;

(4)一个锁对象,在某一时刻最多只能被一个线程持有。

4.2对象锁

语法格式:

public synchronized void m(){代码};

当某个实例方法的整个方法体都需要进行同步,并且锁对象是this时,可以直接使用synchronized关键字修饰这个实例方法,称为同步实例方法。同步实例方法把整个方法体作为同步代码块,默认对象锁是this,也叫对象锁。

4.3类锁

语法格式:

public static synchronized void m(){代码};

当某个静态方法的整个方法体都需要进行同步,并且锁对象是当前类的运行时类时,可以直接使用synchronized修饰这个静态方法,称为同步静态方法。同步静态方法是把整个方法体作为同步代码块,默认锁对象是当前类的运行时类对象,也叫类锁。

5.例

模拟银行账户取款,多线程进行。

账户对象:

public class Account {

    private String account;
    private double balance;

    public Account(String account, double balance) {
        this.account = account;
        this.balance = balance;
    }

    public void withdraw(double money){
        double after = balance - money;
        this.setBalance(after);
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

取款线程:

public class Thread04 implements Runnable {

    Account account;

    public Thread04(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        account.withdraw(100);
        System.out.println("取款:" + 100 + "元,余额:" + account.getBalance());
    }
}

主方法测试:

public class SynchronizedTest {

    public static void main(String[] args) {
        Account account = new Account("招商银行", 1000);

        Thread t1 = new Thread(new Thread04(account));
        Thread t2 = new Thread(new Thread04(account));

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

}

因为使用多线程模拟取款,并且操作同一个账户,所以会出现线程安全问题,如下图。

Java基础——线程同步_第1张图片

所以可以使用synchronized关键字。

在withdraw方法中使用synchronized代码块:

public void withdraw(double money){
    //这里使用字符串作为锁对象
    synchronized ("锁对象"){
        double after = balance - money;
        this.setBalance(after);
    }
}

将withdraw方法变为同步实例方法:

public synchronized void withdraw(double money){
    double after = balance - money;
    this.setBalance(after);
}

6.其他:

(1)同步代码块想要实现同步必须保证同一个锁对象,同步代码块只要使用了同一个对象锁就可以实现同步;

(2)经常使用一个常量作为锁对象,在实例方法中也会使用this对象作为锁对象;

(3)有时在静态方法中也会使用当前类的运行时类作为锁对象,也叫类锁;

(4)最好使用synchronized代码块,这样较为灵活,可以使同步的代码量降到最低,效率更高。

 

你可能感兴趣的:(Java基础)