zoukankan      html  css  js  c++  java
  • java多线程学习笔记(三)

    java多线程下的对象及变量的并发访问

    上一节讲到,并发访问的时候,因为是多线程,变量如果不加锁的话,会出现“脏读”的现象,这个时候需要“临界区”的出现去解决多线程的安全的并发访问。(这个“脏读”的现象不会出现在方法内部的私有变量中,因为其私有的特性,永远都是线程安全的)

    目前锁有三种:synchronized / volatile / Lock

    三类锁各有所长,本节先介绍关键字 :synchronized

    synchronized关键字用来实现线程之间同步互斥。

    public class Test{
        private num = 0;
        public void addId(String username){
            try {
                    if(username.equals("a")){
                        num = 100;
                        System.out.println("a set over!");
                        Thread.sleep(2000);
                    } else {
                        num = 200;
                        System.out.println("b set over!");
                    }
                    System.out.println(username + " num = " + num);
            }catch (InterruptedException e){
                    e.printStackTrace();
            }
        }
    }
    
    public class ThreadA extends Thread {
        private Test test;
        public ThreadA(Test test){
            this.test = test;
        }
        
        @Override
        public void run(){
            super.run();  //在笔记 (一) 里面提到过,其实觉得可以不加上
            test.addId("a");
        }    
    }
    public class ThreadB extends Thread {
    
    private Test test;
        public ThreadB(Test test){
            this.test = test;
        }
        
        @Override
        public void run(){
            super.run();  //在笔记 (一) 里面提到过,其实觉得可以不加上
            test.addId("b");
        }    
    }

     同时运行时,Test类中的num变量会被两个线程不同步的修改,出现错误

    public class Run{
        public static void main(String[] args){
            Test test = new Tess();  //!!!!!!!!!!这里很关键,这里是同一个实例对象test!下文会提到!
            ThreadA athread = new ThreadA(test);
            athread.start();
            ThreadB bthread = new ThreadB(test);
             bthread.start();
             }
    }

    这时,想让他们同步的办法便是给他们的 addId() 方法,加上锁:synchronized 关键字。

    synchronized public void addId(String username){
        //...中间部分全部相同的代码
    }

    结论:两个线程访问同一个对象中的同步方法时,一定是线程安全的。

    既然有同一个对象中的同步方法,肯定就会有多个对象的情况,这个时候就会有多个对象多个锁的情况:

    这里详细说一下synchronized关键字加锁的范围:(本部分加锁范围借鉴了的博文)

    1. 修饰普通方法(锁住的是当前实例对象)
    2. 同步代码块传参this(锁住的是当前实例对象)

    3. 同步代码块传参变量对象 (锁住的是变量对象)

    4. 同步代码块传参class对象(全局锁)

    5. 修饰静态方法(全局锁)

    构造函数,原型对象,实例对象三者之间的关系

    构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。

    Example e = new Example(n); //构造函数。 

    通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。//e 为实例对象

    1 修饰普通方法:

    public class SynchronizedTest {
       //锁住了本类的实例对象
       public synchronized void test1() {
            try {
                logger.info(Thread.currentThread().getName() + " test1 进入了同步方法");
                Thread.sleep(5000);
                logger.info(Thread.currentThread().getName() + " test1 休眠结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
     
        public static void main(String[] args) {
            SynchronizedTest st = new SynchronizedTest();
            SynchronizedTest st2 = new SynchronizedTest();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st.test1();
            }).start();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st2.test1();
            }).start();
        }
    }

    本例的实例对象为 st st2 :

    • 同一个实例调用会阻塞(开篇提到的例子中,两个线程访问同一个对象实例方法,所以会产生阻塞)
    • 不同实例调用不会阻塞

    上文的代码的运行结果是没有阻塞的,因为是不同的实例对象,调用了相同的方法 test1() .

    2 同步代码块穿参this

    • 同一个实例调用会阻塞
    • 不同实例调用不会阻塞
    public class SynchronizedTest {
       //锁住了本类的实例对象
        public void test2() {
            synchronized (this) {
                try {
                    logger.info(Thread.currentThread().getName() + " test2 进入了同步块");
                    Thread.sleep(5000);
                    logger.info(Thread.currentThread().getName() + " test2 休眠结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
     
        public static void main(String[] args) {
            SynchronizedTest st = new SynchronizedTest();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st.test2();
            }).start();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st.test2();
            }).start();
     
        }
    }

    和 1 一样,同样是锁住了当前的实例对象

    3 同步代码块传参变量对象

    • 同一个属性对象才会实现同步
    public class SynchronizedTest {
        
       public Integer lockObject;
     
        public SynchronizedTest(Integer lockObject) {
            this.lockObject = lockObject;
        }
     
       //锁住了实例中的成员变量
        public void test3() {
            synchronized (lockObject) {
                try {
                    logger.info(Thread.currentThread().getName() + " test3 进入了同步块");
                    Thread.sleep(5000);
                    logger.info(Thread.currentThread().getName() + " test3 休眠结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public static void main(String[] args) {
            SynchronizedTest st = new SynchronizedTest(127);
            SynchronizedTest st2 = new SynchronizedTest(127);
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st.test3();
            }).start();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st2.test3();
            }).start();
     
        }
    }

    同一个实例对象的成员属性肯定是同一个,此处列举的是不同实例的情况,但是 依旧实现了同步,原因如下:

    Integer存在静态缓存,范围是-128 ~ 127,当使用Integer A = 127 或者 Integer A = Integer.valueOf(127) 这样的形式,都是从此缓存拿。如果使用 Integer A = new Integer(127),每次都是一个新的对象。此例中,两个对象实例的成员变量 lockObject 其实是同一个对象,因此实现了同步。还有字符串常量池也要注意。所以此处关注是,同步代码块传参的对象是否是同一个。这跟第二个方式其实是同一种。

    4、同步代码块传参class对象(全局锁)

    所有调用该方法的线程都会实现同步。

    public class SynchronizedTest {
     
       //全局锁,类是全局唯一的
        public void test4() {
            synchronized (SynchronizedTest.class) {
                try {
                    logger.info(Thread.currentThread().getName() + " test4 进入了同步块");
                    Thread.sleep(5000);
                    logger.info(Thread.currentThread().getName() + " test4 休眠结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
     
        public static void main(String[] args) {
            SynchronizedTest st = new SynchronizedTest();
            SynchronizedTest st2 = new SynchronizedTest();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st.test4();
            }).start();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st2.test4();
            }).start();
        }
    }

    类锁,直接锁了全局了

    5、修饰静态方法(全局锁)

    • 所有调用该方法的线程都会实现同步
    public class SynchronizedTest {
     
       //全局锁,静态方法全局唯一的
        public synchronized static void test5() {
            try {
                logger.info(Thread.currentThread().getName() + " test5 进入同步方法");
                Thread.sleep(5000);
                logger.info(Thread.currentThread().getName() + " test5 休眠结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            SynchronizedTest st = new SynchronizedTest();
            SynchronizedTest st2 = new SynchronizedTest();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st.test5();
            }).start();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                st2.test5();
            }).start();
            new Thread(() -> {
                logger.info(Thread.currentThread().getName() + " test 准备进入");
                SynchronizedTest.test5();
            }).start();
        }
    }

    结论:synchronized在语法维度上主要分为三个用法

    1. 静态方法加上关键字

    2. 实例方法(也就是普通方法)加上关键字

    3. 方法中使用同步代码块

    前两种方式最为偷懒,第三种方式比前两种性能要好。

    本篇的最后加上一个多线程的题目:利用5个线程并发执行,num数字累计计数到10000,并打印。

    /**
    * Description:
    * 利用5个线程并发执行,num数字累加计数到10000,并打印。
    * 2019-06-13
    * Created with OKevin.
    */
    public class Count {
       private int num = 0;
    
       public static void main(String[] args) throws InterruptedException {
           Count count = new Count();
    
           Thread thread1 = new Thread(count.new MyThread());
           Thread thread2 = new Thread(count.new MyThread());
           Thread thread3 = new Thread(count.new MyThread());
           Thread thread4 = new Thread(count.new MyThread());
           Thread thread5 = new Thread(count.new MyThread());
           thread1.start();
           thread2.start();
           thread3.start();
           thread4.start();
           thread5.start();
           thread1.join();
           thread2.join();
           thread3.join();
           thread4.join();
           thread5.join();
    
           System.out.println(count.num);
    
       }
    
       private synchronized void increse() {
           for (int i = 0; i < 2000; i++) {
               num++;
           }
       }
    
       class MyThread implements Runnable {
           @Override
           public void run() {
               increse();
           }
       }
    }
  • 相关阅读:
    高数基础
    java.io.FileNotFoundException: [WEB-INF/spring-servlet.xml] cannot be opened because it does not exist
    HBase1.0.0 实现数据增删查
    cm 安装cdh 后添加hive服务
    HBase启动错误提示别的机器60000已经存在
    CM集群管理
    CM 安装CDH 错误: 安装失败。 无法接收 Agent 发出的检测信号。
    Js运动框架
    单片机DA转换实现正弦波
    怎样让树莓派接移动硬盘
  • 原文地址:https://www.cnblogs.com/samanian/p/11865162.html
Copyright © 2011-2022 走看看