synchronized方法、synchronized块
线程的同步
并发:同一个对象被多个线程同时操作。
解决方案:让多个线程排队操作对象。
使用队列和锁解决多线程的并发问题。
同进程的多线程共享同一块存储空间,当多个线程同时访问某块内存空间时,就存在并发问题。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized;当线程获得对象的排它锁,该线程独占资源,其他线程必须等待其使用后释放锁即可。同时也伴随着一些问题:
- 一个线程持有锁导致其它线程所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,导致性能问题
- 也可能存在优先级高的线程等待优先级低的线程释放锁,而导致性能问题。
不安全的线程
通过下面例子说明,线程是不安全。
重复执行下面方法,执行结果可能会有多种情况。
示例1:
java
//每次执行结果都不是我们预想的结果
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
}
}
class BuyTicket implements Runnable {
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
//synchronized 同步方法 锁的是this 即BuyTicket对象
private /**synchronized*/ void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到票" + ticketNums--);
}
}
java
张三买到票9
李四买到票8
王五买到票10
李四买到票7
王五买到票6
张三买到票7
张三买到票5
李四买到票4
王五买到票5
李四买到票3
张三买到票3
王五买到票3
王五买到票2
张三买到票2
李四买到票2
李四买到票1
王五买到票1
张三买到票1
示例2
java
public class UnsafeBank {
public static void main(String[] args) {
Account funds = new Account("家庭基金", 100);
new TakeMoney(funds,60,"you").start();
new TakeMoney(funds,80,"youWife").start();
}
}
class Account{
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
class TakeMoney extends Thread{
Account account;
int takeMoney;//取多钱钱
int nowMoney;//你口袋里面的钱
public TakeMoney(Account account, int takeMoney, String name) {
super(name);
this.account = account;
this.takeMoney = takeMoney;
}
//synchronized run 是不行的
@Override
public void run() {
//synchronized (account) {
if ((account.money - takeMoney) < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足。");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - takeMoney;
nowMoney = nowMoney + takeMoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(this.getName() + "口袋里面的钱:" + nowMoney);
//}
}
}
java
//执行结果
家庭基金余额为:-40
家庭基金余额为:-40
you口袋里面的钱:60
youWife口袋里面的钱:80
示例3
java
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
//synchronized (list) {
list.add(Thread.currentThread().getName());
// }
}).start();
}
Thread.sleep(3000);//确保上面线程能够执行完毕后在打印list.size
System.out.println(list.size());
}
}
java
//执行结果
9999
针对上面示例,添加synchronized关键字,使其变为线程安全的。
synchronized方法、synchronized块
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它
主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性
。
synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得该方法对象的锁才能执行,否则会阻塞线程,方法执行时独占该锁,知道方法执行完毕才会释放锁。
同步块:synchronized(object){}synchronized修饰的代码块,会在object对象上加一个监视器。而同步方法会在当前对象this上增加一个监视器。
synchronized同步代码块一般加载
共享资源
对象上。
java
//示例1 将buy方法改为同步方法
private synchronized void buy()
//示例2 试图按照示例1的方案 将run方法改为同步方法,验证是不行的,因为加上run方法的synchronized将对TakeMoney类添加的监视器,但最终操作的确是account对象,
//此示例 多线程操作的共共享资源是account,并非TakeMoney
//示例3 在共享资源list增加synchronized关键字,为其添加监视器