1.同步/异步编程
异步编程模型:t1线程与t2线程各自执行,两个线程之间互不干扰;
同步编程模型:t1线程与t2线程执行,当t1线程必须等t2线程结束之后才能执行,即同步编程模型。
2.什么时候进行同步,为什么引入同步机制
- 为了数据的安全性,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制,线程同步机制使程序变成了(等同)单线程;
- 什么条件下使用线程同步:必须是多线程环境;多线程环境共享同一个数据;共享的数据涉及到修改操作。
3.线程安全问题
- 当多个线程同时操作堆区或方法区的同一个数据时,可能导致数据不一致的现象,称为线程安全问题。
- 线程安全问题解决方式:
(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();
}
}
因为使用多线程模拟取款,并且操作同一个账户,所以会出现线程安全问题,如下图。
所以可以使用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代码块,这样较为灵活,可以使同步的代码量降到最低,效率更高。