多线程高并发编程学习笔记一

高并发编程学习笔记一:
    Java高并发编程主要有三个知识点:
        1. synchronizer:同步器,确保各线程之间通讯,同步以保证动作一致
        2. 同步容器
        3. ThreadPool、executor、callable等

1.Synchronized关键字
  

 public class Demo {
        public int count = 10;
        Object o = new Object();
        public void m(){
            synchronized(o){//线程运行到此时,必须先拿到o这把锁
                count--;
            }
        }
    }


    1. 当有第一个线程获取到了锁o时,其他线程要等待其结束,等待当前线程释放锁之后再获取锁。互斥锁
    2. synchronized锁定的是o这个对象的堆内存地址,所以如果对象o发生改变,锁也会发生改变。
    3. synchronized不同用法:
        1.sychronized(this):以当前对象为锁,常用来锁定代码块
        2.public synchronized void m(){} :锁定整个方法
    4. public sychronized static void m(): 当synchronized锁定static方法时,因为static方法是和类绑定的,与对象无关所以相当于synchronized(ClassName.Class)锁定的是class对象。
2.脏读问题:只对set方法锁定没有对get方法锁定,导致当一个线程修改了数据但是还没有立马更新到数据库中,另一个线程获取该数据那么获取到的就是无效数据。所以解决方法是:set与get均加锁,写的时候不能读,读的时候不能写。
  

 public class Demo1 {
        String accountName;
        Double balance = 0.00;
        public synchronized void set(String accountName,double balance){
            this.accountName = accountName;

            try {
                Thread.sleep(2000);//模拟数据修改但是还没有更新到数据库中的状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.balance = balance;
        }
        public /*synchronized*/ void get(String accountName){
            System.out.println("The balance of "+accountName+" is "+this.balance);
        }
        public static void main(String[] args){
            Demo1 demo1 = new Demo1();
            new Thread(()->demo1.set("zhang san",100.00)).start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demo1.get("zhang san");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demo1.get("zhang san");
        }
    }
    result:
            The balance of zhang san is 100.0
            The balance of zhang san is 100.0


3.synchronized的重入性
    //sychronized重入
  

 public class Demo2 {
        public synchronized static void m1(){
            System.out.println("m1 start");
            m2();
            new Demo2().new test().m3();
            System.out.println("m1 end");
        }
        synchronized static void m2(){
            System.out.println("m2 start");
            System.out.println("m2 end");
        }
        class test{
            void m3(){
                synchronized (Demo2.class){
                    System.out.println("m3 start");
                }
            }
        }
        public static void main(String[] args){
            Demo2 demo2 = new Demo2();

            new Thread(()->demo2.m1()).start();

        }
    }


    1.一个同步方法可以调用另一个同步方法。
    2.线程只要获得了锁就可以调用以该锁为锁的所有方法(可以是不同类的方法,如上面代码所示m1调用m3),所以synchronized是可以重入的。
4. synchronized的重入性之子类调用父类方法
  

 public class Demo1 {
        synchronized void m(){
            System.out.println("super start");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("super end");
        }
        public static void main(String[] args){
            new subClass().n();
        }
    }
    class subClass extends Demo1{
        synchronized void n(){
            System.out.println("child start");
            super.m();
            System.out.println("child end");
        }
    }


5.    当同步方法种抛出异常的时候,synchronized的锁会被释放。被其他的线程获取
    

public class Demo2 {
        synchronized void m(){
            int index = 0;
            System.out.println(Thread.currentThread().getName()+ "start");
            while (true){
                index++;
                if(index == 5){ //如果用try catch来handle这个异常 就不会报错 不会释放锁
                    index/=0;
                }
            }
        }
        public static void main(String[] args){
            Demo2 demo2 = new Demo2();
            new Thread(()->demo2.m()).start();
            new Thread(()->demo2.m()).start();
        }
    }

    result:
        Thread-0 start;
        ArithmeticException
        Thread-1 start;


6. volatile关键字
    Java程序在运行时会把需要的参数从主内存copy到自己的内存缓冲区,当调用参数的时候,不再再次请求主内存的参数,而是直接用自己内存缓冲区的参数。所以当多线程运行的时候,假如线程1修改了某一参数的值,线程2并不知道该参数被修改,因为它还是调用自己内存缓冲区中的该参数的值,这样会造成麻烦。
    1. volatile 修饰的参数是线程间透明的,当volatile修饰的参数修改时,其他的线程也会被通知自己内存缓冲区的该参数过期,其他线程会再次去主内存中请求该参数。
    2. volatile 修饰的方法禁止虚拟机对命令重排:为了代码效率,JVM会对程序代码重排执行,加了volatile后代码不会被重排。
    3. volatile 只解决了可见性问题并没有解决原子性问题,所以volatile并不能取代synchronized。java.util.concurrent.aromicXXX包可以创建原子性的参数。      

 public class Demo4 {
            volatile int count = 0;
            private void add(){
                count++;
            }
            public static void main(String[] args){
                List<Thread> threads = new ArrayList<>();
                Demo4 demo4 = new Demo4();
                for(int i=0;i<10;i++){
                    threads.add(new Thread(()->{
                        for (int j=0;j<1000;j++){
                            demo4.add();
                        }
                    }));
                }

                threads.forEach((o)->o.start());
                threads.forEach((o)->{
                    try {
                        o.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                System.out.println("count : "+demo4.count);
            }
        }
        result:8357


        由此可见:volatile只能解决可见性问题,并不能解决原子性问题。所以要想解决原子性问题只能用 synchronized锁定add方法或者用java.util.concurrent.aromicXXX包可以创建原子性的参数。
        1. 使用synchronized:
            int count = 0;
            private synchronized void add(){
                count++;
            }
        2. 使用AtomicXXX
            AtomicInteger count = new AtomicInteger(0);
            private synchronized void add(){
                count.getAndAdd(1);
            }

7.    例题 : 实现一个容器,提供两个方法add和size。写两个线程:线程一将1-10是个元素加入到容器中,线程二监测容器的size,当size=5时,线程二给出提示并结束    简单的用volatile实现:在list上面加上volatile使第二个线程能够得知list的变化。  
        缺点:第二个线程死循环极大的浪费CPU
        

public class Demo3 {
            volatile List container = new ArrayList();

            private int size(){
                return container.size();
            }
            private void add(Object o){
                container.add(o);
            }
            public static void main(String [] args){
                Demo3 demo3 = new Demo3();
                new Thread(()->{
                    for(int i = 0;i<10;i++){
                        demo3.add(i);
                        System.out.println("t1 size : " + demo3.size());
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                new Thread(()->{
                    while (true){
                        if(demo3.size() == 5){
                            break;
                        }
                    }
                    System.out.println("t2 finish");

                }).start();
            }
        }


    使用wait与notify实现:
        1. 当线程调用自己锁定对象的wait方法时,当前线程会进入wait状态,并释放自己的锁定对象。当调用notify方法时,会随机唤醒以这个对象为锁定对象并且处于wait状态的线程。notifyAll就是全部唤醒wait的线程。
        2. notify并不会释放锁(以下代码有一个问题:lock.notify()并不会释放锁但是好像也能实现功能。那是因为for里面的if判断导致lock.notify其实是被运行了5次,等t1结束了,锁自动释放了t2才运行,与我们的设计不符)
        

public class Demo3 {
             List container = new ArrayList();

            private int size(){
                return container.size();
            }
            private void add(Object o){
                container.add(o);
            }
            public static void main(String [] args) {
                Demo3 demo3 = new Demo3();
                Object lock = new Object();
                new Thread(() -> {
                    synchronized (lock){
                        System.out.println("t2 start");
                        if(demo3.size() != 5){
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("t2 finish container size is : " + demo3.size());
                    }
                },"t2").start();
                new Thread(() -> {
                    for (int i = 0; i < 10; i++) {
                        synchronized (lock) {
                            if (demo3.size() != 5) {
                                demo3.add(i);
                                System.out.println("t1 size : " + demo3.size());
                            } else {
                                lock.notify();
                            }
                        }
                    }
                },"t1").start();
            }
        }


        //更新后更符合逻辑的代码
        缺点:第二个线程运行的时候,第一个线程无法同步运行只能等待。
        

public class Demo3 {
            List container = new ArrayList();

            private int size(){
                return container.size();
            }
            private void add(Object o){
                container.add(o);
            }
            public static void main(String [] args) {
                Demo3 demo3 = new Demo3();
                Object lock = new Object();
                new Thread(() -> {
                    synchronized (lock){
                        System.out.println("t2 start");
                        if(demo3.size() != 5){
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("t2 finish container size is : " + demo3.size());
                    }
                },"t2").start();
                new Thread(() -> {
                    System.out.println("t1 start");
                    for (int i = 0; i < 10; i++) {
                        synchronized (lock) {
                            demo3.add(i);
                            System.out.println("t1 size : " + demo3.size());
                            if(demo3.size() == 5){
                                //唤醒t2
                                lock.notify();
                                try {
                                    //释放lock锁
                                    lock.wait();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                    System.out.println("t1 finish ");
                },"t1").start();
            }
        }

    使用countdownlatch
    1.CountDownLatch主要有两个方法:countDown 与 await ,countDownLatch在创建的时候需要提供一个整数,这个整数表示有几个门闩,也表示处于await状态的线程执行之前应该完成多少个任务,其他线程每完成一个任务就调用countdown让这个整数减一,拿掉一根门闩。当门闩=0的时候,处于await状态的线程开始运行。 适合多任务的环境下,一个任务分成几个线程去完成,提高工作效率。
  

 public class Demo3 {
        volatile List container = new ArrayList();


        private int size(){
            return container.size();
        }
        private void add(Object o){
            container.add(o);
        }
        public static void main(String [] args) {
            Demo3 demo3 = new Demo3();
            CountDownLatch countDownLatch = new CountDownLatch(1);
            new Thread(() -> {
                System.out.println("t2 start");
                if(demo3.size() != 5){
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2 finish container size is : " + demo3.size());
            },"t2").start();
            new Thread(() -> {
                System.out.println("t1 start");
                for (int i = 0; i < 10; i++) {
                        demo3.add(i);
                        System.out.println("t1 size : " + demo3.size());
                        if(demo3.size() == 5){
                            countDownLatch.countDown();
                        }
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t1 finish ");
            },"t1").start();
        }
    }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值