• java并发编程 线程基础


    java并发编程 线程基础

    1. java中的多线程

    java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动

    public class OnlyMain {
        public static void main(String[] args) {
            //虚拟机线程管理接口
            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
            for (ThreadInfo info : threadInfos) {
                System.out.printf("[%s] %s
    ",info.getThreadId(),info.getThreadName());
            }
        }
    }
    ////////////////////////////////控制台输出
    [6] Monitor Ctrl-Break   
    [5] Attach Listener    
    [4] Signal Dispatcher   
    [3] Finalizer    
    [2] Reference Handler   
    [1] main   
    
    • Monitor Ctrl-Break:IntelliJ IDEA执行用户代码的时候,实际是通过反射方式去调用,而与此同时会创建一个Monitor Ctrl-Break 用于监控目的。
    • Attach Listener:该线程是负责接收到外部的命令,执行该命令,并且把结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。
    • signal dispather: 前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
    • Finalizer: JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收。
    • Reference Handler :它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
    • main:主线程,用于执行我们编写的java程序的main方法。

    2. 启动多线程的方式

    1. 继承Thread类

    2. 实现runnable接口

    3. 实现callable接口

    private static class ThreadClass extends Thread{
    @Override
    public void run() {
    System.out.println("this is threadClass");;
    }
    }
    
    private static class RunnableClass implements Runnable{
    @Override
    public void run() {
    System.out.println("this is runnableClass");;
    }
    }
    
    private static class CallableClass implements Callable<String>{
    @Override
    public String call() throws Exception {
    System.out.println("this is callableClass");;
    return "callableClass";
    }
    }
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadClass threadClass = new ThreadClass();
    RunnableClass runnableClass = new RunnableClass();
    CallableClass callableClass = new CallableClass();
    // extends Thread start
    threadClass.start();
    // implements runnable start
    new Thread(runnableClass).start();
    // implements callable start
    FutureTask<String> futureTask = new FutureTask<>(callableClass);
    new Thread(futureTask).start();
    System.out.println(futureTask.get());;
    }
    ////////////////////////////////控制台输出
    this is threadClass
    this is runnableClass
    this is callableClass
    callableClass
    

    3. 线程的停止

    1. 自然执行完或抛出异常

    2. 调用stop(),resume(),suspend()方法,但这些方法线程不会释放资源,会造成死锁;所以已经被jdk废弃

    3. interrupt(),isInterrupted(),static interrupted()

      interrupt() 中断一个线程,不是强制停止,通过协作的方式进行,将中断标志位置为true

      isInterrupted() 判定当前线程是否处于中断状态

      static interrupted() 判定当前线程是否处于中断状态,将中断标志位置为false

      private static class ThreadClass extends Thread {
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              while (!isInterrupted()) {
                  System.out.println(threadName +" is run!");
              }
              System.out.println(threadName + " flag is " + isInterrupted());
          }
      }
      
      private static class RunnableClass implements Runnable {
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              while (!Thread.currentThread().isInterrupted()) {
                  System.out.println(threadName +" is run!");
              }
              System.out.println(threadName + " flag is " + Thread.currentThread().isInterrupted());
          }
      }
      
      public static void main(String[] args) throws InterruptedException {
          ThreadClass threadClass = new ThreadClass();
          threadClass.start();
          Thread.sleep(10);
          threadClass.interrupt();
      
          RunnableClass runnableClass = new RunnableClass();
          Thread runnableThread = new Thread(runnableClass,"runnableClass");
          runnableThread.start();
          Thread.sleep(10);
          runnableThread.interrupt();
      }
      

      warning:如果线程中有InterruptedException异常的话,这是因为InterruptedException会重置异常标志位为false,会对异常中断位有影响,下面程序重复运行几次就会产生这样的异常情况。解决的方法就是在catch块中添加interrupt()

      private static class ThreadClass extends Thread {
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              while (!isInterrupted()) {
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      System.out.println(threadName + " flag1 is " + isInterrupted());
                      //  interrupt();
                      e.printStackTrace();
                  }
                  System.out.println(threadName + " flag2 is " + isInterrupted());
              }
              System.out.println(threadName + " flag3 is " + isInterrupted());
          }
      }
      
      public static void main(String[] args) throws InterruptedException {
          ThreadClass threadClass = new ThreadClass();
          threadClass.start();
          Thread.sleep(200);
          threadClass.interrupt();
      }
      
      
      //////////////////////////异常结果
      Thread-0 flag2 is false
      Thread-0 flag2 is false
      Thread-0 flag1 is false
      java.lang.InterruptedException: sleep interrupted
      	at java.lang.Thread.sleep(Native Method)
      	at com.thread.demo.InterruptExceptionThread$ThreadClass.run(InterruptExceptionThread.java:16)
      Thread-0 flag2 is false
      Thread-0 flag2 is false
          
      //////////////////////////正常结果
      Thread-0 flag2 is false
      Thread-0 flag2 is false
      Thread-0 flag3 is true
      
      

    4. 守护线程

    当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

    在java中可以通过setDaemon(true)的方式将一个线程设置为守护线程

    守护线程值得注意的地方:

    • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。
    • 在Daemon线程中产生的新线程也是Daemon的。
    • 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。
    • 守护线程中包含try...catch...finally的时候,finally中的内容不一定能实现。
    //读写操作
    private static class WriteFileRunnable implements Runnable {
        @Override
        public void run() {
            try {
                File f = new File("daemon.txt");
                FileOutputStream os = new FileOutputStream(f, true);
                os.write("daemon".getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        WriteFileRunnable writeFileRunnable = new WriteFileRunnable();
        Thread writeThread = new Thread(writeFileRunnable);
        writeThread.setDaemon(true);
        writeThread.start();
    }
    //////////////////////////结果
    //文件daemon.txt中没有"daemon"字符串。 
    
    //包含try...catch...finally
    private static class ThreadClass extends Thread {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            try {
                while (true) {
                    System.out.println(threadName + " is run!");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println("....................finally");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadClass threadClass = new ThreadClass();
        threadClass.setDaemon(true);
        threadClass.start();
        Thread.sleep(10);
    }
    //////////////////////////结果
    Thread-0 is run!
    Thread-0 is run!
    Thread-0 is run!
    Thread-0 is run!
    
    Process finished with exit code 0
    

    5. synchronized内置锁

    在并发编程中存在线程安全问题,主要原因有:存在共享数据多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)。

    synchronized分为类锁对象锁,synchronized添加在方法中和使用synchronized(this)都是使用了对象锁,如果方法定义成静态的,再添加synchronized,此时锁为类锁。类锁和对象锁可以并行运行,不同对象的对象锁也是可以并行运行的。

    1. synchronized作用于实例方法
    public class SynchronizedDemo {
        private static class InstanceSyncMethod implements Runnable {
            static int tag = 1;   //共享资源
    
            public synchronized void increase() {
                tag++;
            }
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    increase();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            InstanceSyncMethod instance = new InstanceSyncMethod();
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(InstanceSyncMethod.tag);
        }
    }
    //////////////////////////输出结果
    2001
    

    synchronized用于实例对象的时候存在一个问题,当创建多个实例的时候,虽然方法上使用了synchronized,但是因为存在多个不同的实例对象锁,因此t都会进入各自的对象锁,也就是说多个线程使用的是不同的锁,因此线程安全是无法保证的。

    //new新实例
    Thread t1=new Thread(new InstanceSyncMethod());
    //new新实例
    Thread t2=new Thread(new InstanceSyncMethod());
    t1.start();
    t2.start();
    //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
    t1.join();
    t2.join();
    System.out.println(InstanceSyncMethod.tag);
    ////////////////////////////这个时候输出的结果就不确定是多少
    

    解决这种困境的的方式是将synchronized作用于静态的方法,这样的话,对象锁就当前类对象,由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的。

    1. synchronized作用于静态方法

    synchronized作用于静态方法时,其锁就是当前类的class对象锁。调用一个实例对象的非static synchronized方法,和静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

    private static class InstanceSyncMethod implements Runnable {
        static int tag = 1;   //共享资源
    
        public static synchronized void increase() {
            tag++;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                increase();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1 = new Thread(new InstanceSyncMethod());
        //new新实例
        Thread t2 = new Thread(new InstanceSyncMethod());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(InstanceSyncMethod.tag);
    }
    //////////////////////////输出结果
    2001
    
    1. synchronized同步代码块

    在某些情况下,方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,就不建议直接对整个方法进行同步操作,此时可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作。

    private static class InstanceSyncMethod implements Runnable {
        static int tag = 1;   //共享资源
    
        @Override
        public void run() {
            synchronized (this){
                for (int i = 0; i < 1000; i++) {
                    tag++;
                }
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1 = new Thread(new InstanceSyncMethod());
        //new新实例
        Thread t2 = new Thread(new InstanceSyncMethod());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(InstanceSyncMethod.tag);
    }
    //////////////////////////输出结果
    2001
        
    
    //////////////////////////类似的方法块
    //this,当前实例对象锁
    synchronized(this){
    	...
    }
    
    //class对象锁
    synchronized(XXX.class){
        ...
    }
    

    6. volatile

    volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。

    volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。在Java内存模型中,有main memory,每个线程也有自己的memory。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。

    一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。

    也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。

    下面程序就是验证volatile不能保证线程执行的有序性。

    private static class VolatileClass implements Runnable {
        private volatile int a = 1;
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            a = a + 1;
            System.out.println(threadName +":=" + a);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            a = a + 1;
            System.out.println(threadName +":=" + a);
        }
    }
    
    public static void main(String[] args) {
        VolatileClass volatileClass = new VolatileClass();
    
        Thread t1 = new Thread(volatileClass);
        Thread t2 = new Thread(volatileClass);
        Thread t3 = new Thread(volatileClass);
        Thread t4 = new Thread(volatileClass);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
    //////////////////////////输出结果
    Thread-0:=2
    Thread-3:=5
    Thread-2:=4
    Thread-1:=3
    Thread-3:=7
    Thread-1:=7
    Thread-0:=7
    Thread-2:=7
    

    7. ThreadLocal

    threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。

    private static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>() {
        @Override
        public Integer initialValue() {
            return 1;
        }
    };
    private static class ThreadLoaclClass implements Runnable {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            localNum.set(localNum.get() + 1);
            System.out.println(threadName +":=" + localNum.get());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            localNum.set(localNum.get() + 1);
            System.out.println(threadName +":=" + localNum.get());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadLoaclClass loaclClass = new ThreadLoaclClass();
    
        Thread t1 = new Thread(loaclClass);
        Thread t2 = new Thread(loaclClass);
        Thread t3 = new Thread(loaclClass);
        Thread t4 = new Thread(loaclClass);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
    //////////////////////////输出结果
    Thread-0:=2
    Thread-3:=2
    Thread-1:=2
    Thread-2:=2
    Thread-1:=3
    Thread-2:=3
    Thread-0:=3
    Thread-3:=3
    

    每个线程所产生的序号虽然都共享同一个实例,但它们并没有发生相互干扰的情况,而是各自产生独立的数字,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

    ThreadLocal和Synchronized都能解决多线程中相同变量的访问冲突问题,不同的是

    • Synchronized是通过线程等待,牺牲时间来解决访问冲突
    • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
  • 相关阅读:
    读书笔记 effective c++ Item 53 关注编译器发出的警告
    读书笔记 effective c++ Item 52 如果你实现了placement new,你也要实现placement delete
    读书笔记 effective c++ Item 51 实现new和delete的时候要遵守约定
    读书笔记 effective c++ Item 50 了解何时替换new和delete 是有意义的
    读书笔记 effective c++ Item 49 理解new-handler的行为
    读书笔记 effective c++ Item 48 了解模板元编程
    读书笔记 effective c++ Item 47 使用traits class表示类型信息
    读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数
    读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”
    读书笔记 effective c++ Item 44 将与模板参数无关的代码抽离出来
  • 原文地址:https://www.cnblogs.com/huizhipeng/p/12050264.html
Copyright © 2020-2023  润新知