zoukankan      html  css  js  c++  java
  • 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具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
  • 相关阅读:
    「UVA12293」 Box Game
    「CF803C」 Maximal GCD
    「CF525D」Arthur and Walls
    「CF442C」 Artem and Array
    LeetCode lcci 16.03 交点
    LeetCode 1305 两棵二叉搜索树中的所有元素
    LeetCode 1040 移动石子直到连续 II
    LeetCode 664 奇怪的打印机
    iOS UIPageViewController系统方法崩溃修复
    LeetCode 334 递增的三元子序列
  • 原文地址:https://www.cnblogs.com/huizhipeng/p/12050264.html
Copyright © 2011-2022 走看看