一、保护共享资源-加锁实现
synchronized加锁实现,悲观锁
jdk8之后加入的新特性:接口中可以创建实现的静态方法,可以拿接口引用对象比实现接口的实现类对象
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;
public class TestAccount {
public static void main(String[] args) {
AccountUnsafe account = new AccountUnsafe(10000);
Account.demo(account);
}
}
class AccountUnsafe implements Account{
private Integer balance;
public AccountUnsafe(Integer balance){
this.balance=balance;
}
@Override
public synchronized Integer getBalance() {
return balance;
}
@Override
public synchronized void withdraw(Integer amout) {
balance-=amout;
}
}
interface Account{
//获取余额
Integer getBalance();
//取款
void withdraw(Integer amout);
/*
* 方法内会启动1000个线程,每个线程作 -10元 的操作
* 如果初始余额为 10000 那么线程正确执行的结果应为0元
* */
/*
*jdk1.8之后支持接口中定义并实现静态方法
* 本方法即1000个线程去一个银行对象取钱,1000个线程去调用getBalance方法与withdraw方法
* */
static void demo(Account account){
List<Thread> ts = new ArrayList<>();//创建线程集合
for (int i=0;i<1000;i++){
ts.add(new Thread(()->{
account.withdraw(10);
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);//每个线程执行start方法
ts.forEach(t->{
try{
t.join();
}catch (InterruptedException e){
e.printStackTrace();
}
});
long end= System.nanoTime();
System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+"ms");
}
}
注意AccountUnsafe类的两个实现访问均涉及对共享变量的访问,所以当多个线程调用这两个方法的时候,要加上synchronized保护。
二、保护共享资源-无锁实现
完全无锁实现多线程并发,CAS与volatile结合实现,乐观锁
余额balance对象改成AtomicInteger类(原子整数类)的对象。
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class TestAccount {
public static void main(String[] args) {
AccountCas account = new AccountCas(10000);
Account.demo(account);
}
}
class AccountCas implements Account{
private AtomicInteger balance;
public AccountCas(int balance){
this.balance=new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amout) {
while(true){
int prev=balance.get();//获取balance的最新值
//要修改后的余额
int next=prev-amout;
//真正修改
if(balance.compareAndSet(prev,next)){
break;
}//修改前和修改后的值传入
}
}
}
interface Account{
//获取余额
Integer getBalance();
//取款
void withdraw(Integer amout);
/*
* 方法内会启动1000个线程,每个线程作 -10元 的操作
* 如果初始余额为 10000 那么线程正确执行的结果应为0元
* */
/*
*jdk1.8之后支持接口中定义并实现静态方法
* 本方法即1000个线程去一个银行对象取钱,1000个线程去调用getBalance方法与withdraw方法
* */
static void demo(Account account){
List<Thread> ts = new ArrayList<>();//创建线程集合
for (int i=0;i<1000;i++){
ts.add(new Thread(()->{
account.withdraw(10);
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);//每个线程执行start方法
ts.forEach(t->{
try{
t.join();
}catch (InterruptedException e){
e.printStackTrace();
}
});
long end= System.nanoTime();
System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+"ms");
}
}
三、无锁效率更高的原因
- 无锁状况下,即使重试失败,线程始终在高速运行,仍处于运行状态,没有停歇,而synchronized会让线程在没有获得锁的时候,发生线程上下文切换,进入阻塞,将一些线程信息保存起来,比较耗费资源,打个比方:
- 线程好比高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火等被唤醒又需要重新打火、启动、加速….代价较大
- 但无锁状态下,因为线程要保持运行,需要额外CPU的支持,因为需要保持线程处于自旋状态,CPU在这里好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
总结:无锁是线程均运行但是条件不满足就不会改变共享变量,有锁是没获得锁的线程直接阻塞住,只有拿到锁的线程才有资格对共享变量作读写。