一、保护共享资源-加锁实现

synchronized加锁实现,悲观锁
jdk8之后加入的新特性:接口中可以创建实现的静态方法,可以拿接口引用对象比实现接口的实现类对象

  1. import java.sql.SQLOutput;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class TestAccount {
  5. public static void main(String[] args) {
  6. AccountUnsafe account = new AccountUnsafe(10000);
  7. Account.demo(account);
  8. }
  9. }
  10. class AccountUnsafe implements Account{
  11. private Integer balance;
  12. public AccountUnsafe(Integer balance){
  13. this.balance=balance;
  14. }
  15. @Override
  16. public synchronized Integer getBalance() {
  17. return balance;
  18. }
  19. @Override
  20. public synchronized void withdraw(Integer amout) {
  21. balance-=amout;
  22. }
  23. }
  24. interface Account{
  25. //获取余额
  26. Integer getBalance();
  27. //取款
  28. void withdraw(Integer amout);
  29. /*
  30. * 方法内会启动1000个线程,每个线程作 -10元 的操作
  31. * 如果初始余额为 10000 那么线程正确执行的结果应为0元
  32. * */
  33. /*
  34. *jdk1.8之后支持接口中定义并实现静态方法
  35. * 本方法即1000个线程去一个银行对象取钱,1000个线程去调用getBalance方法与withdraw方法
  36. * */
  37. static void demo(Account account){
  38. List<Thread> ts = new ArrayList<>();//创建线程集合
  39. for (int i=0;i<1000;i++){
  40. ts.add(new Thread(()->{
  41. account.withdraw(10);
  42. }));
  43. }
  44. long start = System.nanoTime();
  45. ts.forEach(Thread::start);//每个线程执行start方法
  46. ts.forEach(t->{
  47. try{
  48. t.join();
  49. }catch (InterruptedException e){
  50. e.printStackTrace();
  51. }
  52. });
  53. long end= System.nanoTime();
  54. System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+"ms");
  55. }
  56. }

注意AccountUnsafe类的两个实现访问均涉及对共享变量的访问,所以当多个线程调用这两个方法的时候,要加上synchronized保护。

二、保护共享资源-无锁实现

完全无锁实现多线程并发,CAS与volatile结合实现,乐观锁
余额balance对象改成AtomicInteger类(原子整数类)的对象。

  1. import java.sql.SQLOutput;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.atomic.AtomicInteger;
  5. public class TestAccount {
  6. public static void main(String[] args) {
  7. AccountCas account = new AccountCas(10000);
  8. Account.demo(account);
  9. }
  10. }
  11. class AccountCas implements Account{
  12. private AtomicInteger balance;
  13. public AccountCas(int balance){
  14. this.balance=new AtomicInteger(balance);
  15. }
  16. @Override
  17. public Integer getBalance() {
  18. return balance.get();
  19. }
  20. @Override
  21. public void withdraw(Integer amout) {
  22. while(true){
  23. int prev=balance.get();//获取balance的最新值
  24. //要修改后的余额
  25. int next=prev-amout;
  26. //真正修改
  27. if(balance.compareAndSet(prev,next)){
  28. break;
  29. }//修改前和修改后的值传入
  30. }
  31. }
  32. }
  33. interface Account{
  34. //获取余额
  35. Integer getBalance();
  36. //取款
  37. void withdraw(Integer amout);
  38. /*
  39. * 方法内会启动1000个线程,每个线程作 -10元 的操作
  40. * 如果初始余额为 10000 那么线程正确执行的结果应为0元
  41. * */
  42. /*
  43. *jdk1.8之后支持接口中定义并实现静态方法
  44. * 本方法即1000个线程去一个银行对象取钱,1000个线程去调用getBalance方法与withdraw方法
  45. * */
  46. static void demo(Account account){
  47. List<Thread> ts = new ArrayList<>();//创建线程集合
  48. for (int i=0;i<1000;i++){
  49. ts.add(new Thread(()->{
  50. account.withdraw(10);
  51. }));
  52. }
  53. long start = System.nanoTime();
  54. ts.forEach(Thread::start);//每个线程执行start方法
  55. ts.forEach(t->{
  56. try{
  57. t.join();
  58. }catch (InterruptedException e){
  59. e.printStackTrace();
  60. }
  61. });
  62. long end= System.nanoTime();
  63. System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+"ms");
  64. }
  65. }

结果表明此种实现方式正确,而且比有锁的实现方式运行时间更短image.png

三、无锁效率更高的原因

  • 无锁状况下,即使重试失败,线程始终在高速运行,仍处于运行状态,没有停歇,而synchronized会让线程在没有获得锁的时候,发生线程上下文切换,进入阻塞,将一些线程信息保存起来,比较耗费资源,打个比方:
  • 线程好比高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火等被唤醒又需要重新打火、启动、加速….代价较大
  • 但无锁状态下,因为线程要保持运行,需要额外CPU的支持,因为需要保持线程处于自旋状态,CPU在这里好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。

总结:无锁是线程均运行但是条件不满足就不会改变共享变量,有锁是没获得锁的线程直接阻塞住,只有拿到锁的线程才有资格对共享变量作读写。