zoukankan      html  css  js  c++  java
  • Java并发编程实战笔记—— 并发编程1

    1、如何创建并运行java线程

      创建一个线程可以继承java的Thread类,或者实现Runnabe接口。

    public class thread {
         static class MyThread1 extends Thread{
            @Override
            public void run() {
                System.out.println("run a myThread1");
            }
        }
        static class MyThread2 implements Runnable{
            @Override
            public void run() {
                System.out.println("run a myThread2");
            }
        }
        public static void main(String[] args){
            MyThread1 myThread1 = new MyThread1();
            myThread1.start();
    
            Thread myThread2 = new Thread(new MyThread2());
            myThread2.start();
        }
    }

    输出:

    run a myThread1
    run a myThread2

    或者是创建一个实现了Runnable接口的匿名类

    public class thread {
    
        public static void main(String[] args){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("run a myThread3");
                }
            }).start();
        }
    }

    输出:

    run a myThread3

    2、安全性

      线程安全性可能是非常复杂的,在没有充足同步的情况下,多个线程的操作执行顺序是不同的,会产生非常奇怪的结果

    class Fun{
        private int value;
        public  int getNext(){
            return value++;
        }
    }

      这个类中的getNext()是一个非线程安全的方法。如果直接在线程中调用时:

    public class thread {
        public static void main(String[] args) {
            Fun fun = new Fun();
            for (int j = 0; j < 10; j++) {
                new Thread() {
                    @Override
                    public void run() {
                        System.out.println(fun.getNext());
                    }
                }.start();
            }
        }
    }

    输出:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

      结果是未知的,每一次运行的结果都不一样,这是因为递增运算value++看上去是一个单独的操作,但实际上它包含了三个独立的操作:读取value,将value加1,将结果写入value。由于运行时可能是多个线程交替执行的这就可能会导致当线程1还没将计算结果存入value时,线程2已经启动并已经读取了value的值。

      java提供了各种同步机制来协同这种访问,比如这样修改这个类:

    class Fun{
        private int value;
        public synchronized int getNext(){
            return value++;
        }
    }

      这次main函数的输出是确定的:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

      一个对象是否是需要线程安全,取决于它是否被多个线程访问,这里注重的是在程序中访问对象的方式,而不是对象想要实现的功能。要使得对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问。java中的主要同步机制synchronized,它提供一种独占的加锁方式,另外还有volatile类型的变量,显式锁,原子变量。

      线程安全的定义:

      当多个线程访问某个类的时候,不管运行时环境采取何种调度方式,并且在主调代码中不需要任何额外的同步或者协同,这个类都可以表现出正确的行为,那么就称这个类是线程安全的。

    3、竞态条件

      在并发编程中,由于不恰当的执行时序而出现不正确的结果,称作:竞态条件

    class Fun{
        private Object instance = null;
        public Object getNext(){
            if (instance == null)
                instance = new Object();
            return instance;
        }
    }

      这是一个最常见的竞态条件类型:“先检查后执行”,通过一个可能失效的观测结果来决定下一步的动作。这里的目的是将对象初始化的操作推迟到执行的时候才初始化,并确保只被初始化一次。

      假设线程A和线程B需要同时执行getNext(),A看到instance为空,从而创建一个新的Object实例,B同样需要判断instance是否为空,但是线程B到底需不需要实例化,取决于不可确定的时序,线程的调度方式,以及A需要多长时间进行初始化。

      要想避免竞态条件问题,就必须在某个线程修改某个变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改完成之前或是之后读取和修改状态。

      所以,假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么等待B全部执行完,要么完全不执行B,那么A和B对彼此而言都是原子的。原子操作是指,对于访问同一个状态的所有操作来说,这个操作是以原子的方式执行操作的。

    4、加锁机制

      JAVA提供了一种内置的锁机制来支持原子性:同步代码块。同步代码块分为两个部分:一个作为锁得对象引用,一个作为又这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步方法块的所就是方法调用所在的对象,

    synchronized(lock){
    //访问或修改由锁保护的共享状态
    }

      每个JAVA对象,都可以用一个实现同步的锁,线程进入同步代码块之前自动获得锁,并且在退出同步代码块时自动释放锁,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

      当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,直到线程B释放这个锁。如果B不释放这个锁,A也将永远等下去。

      由于一次只能由一个线程执行内置锁持有的代码块,所以由这个锁保护的代码块会以原子方式执行。

    5、可见性

      在单线程环境中,如果向某个变量先写入值,然后 在没有其他写入操作的情况下读取这个变量,那么总能得到正确的值,但是在多线程环境中,我们无法保证执行读操作的线程能适时的看到其他线程写入的值,有时是根本不可能的事。为了保证多个线程之间对内存写入操作得可见性,必须使用同步机制。

    public class Fun {
        private static boolean ready;
        private static int number;
        private static class ReaderThread extends Thread{
            public void run(){
                while (!ready)
                    Thread.yield();
                System.out.println(number);
            }
        }
        public static void main(String[] args) {
           new ReaderThread().start();
           number = 42;
           ready = true;
        }
    }

      Fun可能会永远持续循环下去,因为读线程可能永远都看不到ready得值,也可能是一直输出时0,因为可能看到了写入ready的值,没有看到number的值

    6、Volatile变量

      这是Java的另一种同步机制,volatile变量,用来确保将变量的更新操作通知到其他的线程,把变量声明为volatile类型之后,编译器运行时会注意到这个变量是共享的,因此不会把这个变量上的操作与其他内存操作一起进行重排序,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此对volatile类型的变量总会返回最新写入的值。

      写入volatile变量相当于退出了同步代码块,而读取volatile变量等于进入了同步代码块。

      仅当volatile变量用作验证的时候,才应该使用它,例如,volatile的语义并不足以确保递增操作(count++)的原子性,除非能确保只有一个线程执行写操作,所以如果验证时对可见性进行复杂的判断,就不要使用volatile变量了。

      

    7、发布和逸出

      发布一个对象的意思就是指,是能够使对象在当前作用域之外的代码使用,比如:将一个指向该对象的引用保存到其他代码可以访问的地方,或者在某一处非私有的方法中返回该引用。在许多情况下,我们要确保对象及其内部状态不被发布,例如在对象构造完成之前就发布该对象,就会破坏线程安全性。当一个不该发布的对象被发布时,这种情况称为逸出。

      首先来看一个对象是如何逸出的:

      发布一个对象最简单的方法就是将对象得引用保存到一个公有的静态变量中,以便所有的类都能看见该类。

    public static Set<Secret> knowSecrets;
        public void initialize(){
            knowSecrets = new HashSet<Secret>();
        }

      在initialize方法中 实例化了一个新的HashSet对象,并将对象的引用保存在了knowSecrets中以发布该对象。

      当发布某个对象的时候,可能间接地发布其他对象如果讲一个Secret对象添加到集合knowSecrets中,同样也会发布这个对象,因为任何代码都可以遍历这个集合,并获得新Secrect的引用。

        class Unsafe{
            private String[] states = new String[]{
                    "AK","AL"
            };
            public String[] getstates(){
                return states;
            }
        }

      在这个实例中states原本是一个私有变量,因为getstates方法返回了它的引用,所以任何调用者都可以修改这个数组中的内容。

      无论其他线程会对已发布的引用执行何种操作,其实都不重要,因为误用改引用的风险始终存在。

    8、ThreadLocal类

      ThreadLocal类是一种维持线程封闭性的规范方法,这个类能使使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。

      ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序初始化这个连接对象,从而避免在调用每个方法都要传递一个Connection对象。

      当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用ThreadLocal.

  • 相关阅读:
    Java进阶知识查漏补缺06
    SQL学习记录(concat)
    Restful API学习
    git学习
    获得xmlhttp对象
    vue-cli初接触
    vue初接触
    java使用百度UNIT
    JSON学习
    通用Mapper警告:建议修改基本类型为对应的包装类型!
  • 原文地址:https://www.cnblogs.com/xxbbtt/p/7872598.html
Copyright © 2011-2022 走看看