zoukankan      html  css  js  c++  java
  • java多线程编程核心技术

    一、java多线程基础

    1.1 进程和线程

      进程是一次程序的执行,线程是进程中一个个独立运行的子任务。一个进程包含多个线程。

    1.2 线程的实现方式

      (1)继承Thread类

      (2)实现Runnable接口

    1.3 成员变量和局部变量

      成员变量,存在于堆中,多线程共享,存在安全问题。局部变量,存在于工作空间也可理解为栈中,栈是私有的,每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。

    1.4 常见方法

      isAlive() : 判断当前线程是否处于活动状态

      sleep(long ms) : 使线程处于休眠状态(暂停执行),线程不会释放对象锁

      (对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的,wait会释放锁)

    1.5 停止正在运行的线程

      1)、 使用退出标志,使线程运行完run方法,正常结束

      2)、 使用stop方法强制终止线程(不推荐,已标记为废弃的方法,可能会产生不可预料的结果)

      3)、使用interrupt方法终止线程

      Interrupt(),线程不会马上停止,只是给线程打了一个停止的标记,并不是真的停止线程。

           Interrupted():测试线程是否已经中断,具有清除功能,第二次调用返回false

      isInterrupted():测试线程是否已经中断

      Interrupted+return也可中断线程(建议使用异常的方式)

      另参考----尊重别人的劳动成果:http://www.cnblogs.com/w-wfy/p/6415005.html

    1.6 暂停线程

      1)、Suspend与resume

      Suspend暂停线程与resume方法恢复线程

      注:因为暂停时未释放锁,容易造成公共的同步对象的独占,使其他线程无法访问公共同步对象,也容易因暂停而导致数据不同步

      2)、yield

      它的作用是放弃cpu资源,让其他任务去占用cpu资源。但放弃的时间是不确定的,有可能刚刚放弃,又马上又获得了cpu资源

    1.7 线程优先级

      1)setpriority();优先级较高线程获得cpu资源较多,也就是cpu优先执行优先级较高的线程中的任务(规则性),但优先级较高的线程不一定每次都先执行完(随机性)

      2)线程优先级具有继承性,比如A线程中调用B线程,则AB的优先级是一样的

    二、对象与变量的并发访问

    2.1、synchronized

       1)、锁对象

      1. synchronized加在方法上,锁为对象实例,同一个实例就是同一把锁。不同对象,不同的锁,Synchronized(this),this就代表对象实例。注意,Synchronized(Class)是以字节码作为锁,一个类只存在一份class字节码,另外,synchronized加载static修饰的方法上,锁也是字节码class,分析同步问题时,重点分析锁对象是什么。线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值,可以看到synchronized能够实现可见性。同时,由于synchronized具有同步锁,所以它也具有原子性

      2. 锁也可为任意对象,比如定义一个成员变量string,因为成员变量,同一个对象共享,所以Synchronized(string)可以作为同步锁

      3. synchronized锁重入,当线程获取到锁后,再次请求此对象的锁可以再次获得,即同步方法中(加了Synchronized修饰的方法),可以调用其他的同步方法

      4. 出现异常,锁自动释放

      5. 同步块,可以只同步主要的代码块,可以提高效率

      6. 一半同步,一半异步,即一个线程获取到锁后访问同步方法时,其他线程因无法获取锁,但可以访问其他不是同步的方法(未加Synchronized的方法)

      7. 内部类,或静态内部类,可以理解为成员变量即可。

    2.1、volatile

      1. 关键字volatile的作用是,强制从公共堆中获取变量,而不是私有栈中,可实现变量在多线程间可见

        线程中的三个概念,原子性问题,可见性问题,有序性问题,volatile保证了可见性,但是非原子性,有一定的顺序性

      当且仅当满足以下所有条件时,才应该使用 volatile 变量:

    • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
    • 该变量没有包含在具有其他变量的不变式中。

      2. 关键子synchronized和volatile进行一下比较:

        1)关键字volatile是线程同步的轻量级实现,所以volatile性能背定比synchronized要好,并且volatile只能修饰于变量, isynchronizedi以修饰方法,以及代码块。随着JDK新版本的发布,   synchronized关键7在执行效率上得到很大提升,在开发中使用synchronized关键子的比率还是比较大的。

        2)多线程访问volatile不会发生阻塞, synchronized会出现阻塞。

        3) volatile能保证数据的可见性,但不能保原子性; synchronizedi以保原子性.也可以同接保证可见性,因为它会将松有内存和公共内存中的数据做同步。

        4)重申一下,关键子volatile解决的是变量在多个线程之间的可见性;synchronized解决是多线程访问资源的同步性

        参考:内存模型是理解volatile的关键

              https://www.cnblogs.com/dolphin0520/p/3920373.html#!comments

      3. 例子

    package TestItems.test.core;
    
    public class PrintString implements Runnable{
        
        /*volatile*/ private Boolean isContinue=true;
        
        public Boolean getIsContinue() {
            return isContinue;
        }
    
        public void setIsContinue(Boolean isContinue) {
            this.isContinue = isContinue;
        }
        
        public void printMethod() {
            try {
                while(isContinue) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void run() {
            printMethod();
        }
    
    }
    
    package TestItems.test.core;
    
    public class VolatileTest {
        public static void main(String[] args) throws Exception {
            PrintString p=new PrintString();
            new Thread(p).start();
            //p.printMethod();
            Thread.sleep(3000);
            p.setIsContinue(false);
        }
    }
    View Code

      在win7的jdk64位环境中,运行正常,但是在JVM设置为Server服务器中会出现死循环(一直没有测试成功),解决方法是加volatle关键字

    三、线程间的通信

    3.1 等待/通知机制

      1)、Wait()和notify()都是Object中的方法。wait使线程停止运行,会释放掉锁,notify()使停止的线程继续运行,等notify所在的同步块执行完,才会释放掉锁。但两个方法的使用前提是,都必须先获得对象级别的锁,否则会抛出IllegalMonitorStateException异常,它是RuntimeException的子类。当线程呈wait状态时,调用interrupt()时会出现InterruptedException遗传。

      2)、等待通机制的实现

    package TestItems.test.core;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class MyThreadWait extends Thread {
        private Object lock;
    
        public MyThreadWait(Object lock) {
            super();
            this.lock = lock;
        }
    
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    SimpleDateFormat smft = new SimpleDateFormat("YYYY年MM月dd日  HH:mm:ss");
                    System.out.println("等待开始:" + smft.format(new Date()));
                    lock.wait();
                    System.out.println("等待结束:" + smft.format(new Date()));
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    package TestItems.test.core;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class MyThreadNotify extends Thread {
        private Object lock;
    
        public MyThreadNotify(Object lock) {
            super();
            this.lock = lock;
        }
    
        @Override
        public void run() {
            try {
                synchronized(lock) {
                    SimpleDateFormat smft = new SimpleDateFormat("YYYY年MM月dd日  HH:mm:ss");
                    System.out.println("唤醒开始:" + smft.format(new Date()));
                    lock.notify();
                    System.out.println("唤醒结束:" + smft.format(new Date()));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    package TestItems.test.core;
    
    public class OneToOneWaitNotify {
        public static void main(String[] args) {
            Object lock = new Object();
            try {
                MyThreadWait wait = new MyThreadWait(lock);
                MyThreadNotify notify = new MyThreadNotify(lock);
                wait.start();
                Thread.sleep(3000);
                notify.start();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
    /*            等待开始:2018年08月02日  14:49:41
                唤醒开始:2018年08月02日  14:49:44
                唤醒结束:2018年08月02日  14:49:44
                等待结束:2018年08月02日  14:49:44*/
            }
    
        }
    }
    View Code

      3)、生产消费者模式:多生产和多消费时,容易出现“假死”现象,即notify唤醒的可能是同类比如生产者唤醒的不是消费者而是生产者,导致所有线程都处于等待状态。解决方式使用notifall唤醒所有线程。

      4)、通过管道进行线程间通信

        JDK中提供了退格类来是线程间可以通信

        PipedInputStream和PipedOutputStream

        PipedReader和PipedWriter

      5)、实战:等待通知交叉备份

    package TestItems.test.core;
    
    public class CommonResoure {
        /* volatile */ private int comm = 10;
         volatile  public boolean flag = true;
    
        public void add() {
            try {
                synchronized (CommonResoure.class) {
                    //如果是if,经过单次判断后不会再进行判断,但是在线程唤醒后需要再次判断的
                    while (!flag) {
                        CommonResoure.class.wait();
                    }
                    comm++;
                    System.out.println("comm++:" + comm);
                    flag = false;
                    CommonResoure.class.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void decrease() {
            try {
                synchronized (CommonResoure.class) {
                    while (flag) {
                        CommonResoure.class.wait();
                    }
                    comm--;
                    System.out.println("comm--:" + comm);
                    flag = true;
                    CommonResoure.class.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public int getComm() {
            return comm;
        }
    
        public void setComm(int comm) {
            this.comm = comm;
        }
    
    }
    
    package TestItems.test.core;
    
    public class Comsume implements Runnable{
        
        private CommonResoure res;
        
        public Comsume(CommonResoure res) {
            this.res=res;
        }
        
        public void run() {
                res.decrease();
        }
    }
    
    package TestItems.test.core;
    
    public class Produce implements Runnable{
        
        private CommonResoure res;
        
        public Produce(CommonResoure res) {
            this.res=res;
        }
        
        public void run() {
                res.add();
        }
    }
    
    package TestItems.test.core;
    
    public class ProduceComsumeTest {
        
        public static void main(String[] args) throws InterruptedException {
            CommonResoure comm=new CommonResoure();
            Produce p=new Produce(comm);
            Comsume c=new Comsume(comm);
            
            for(int i=0;i<50;i++) {
                new Thread(p,"name"+i).start();
                new Thread(c,"name"+i).start();
            }
            
            Thread.sleep(2000);
            
        }
        
    
    }
    View Code

    3.2 join()

             在主线程中启用子线线程,有时子线程中需要进行长时间的运算,而通常主线程在子线程运行完之前就就是,但有时需要等到子线程中的结果,那么可以使用join方法,它的作用就是等待线程对象销毁。Join与interrupt相遇也会出现InterruptedException异常。Join内部是使用wait来实现的,所有具有释放锁的特点

    3.3 ThreadLocal

              变量共享一般使用public static的形式,如果想实现每个线程都有自己的共享变量,就可以使用ThreadLocal。它主要就是来解决每个线程绑定自己的值。

          1)、 示例(后续在写)

          2)、解决get获取为null的问题(示例后续写)

          继承threadLocal重写initialValue方法,返回默认值

          3)、InheritableThreadLocal可以在子线程中取到从父线程中继承的值(示例后续在写)

    四、Lock的使用

             ReentrantLock

            ReentrantReadWriteLock

    4.1 ReentrantLock

             1)、ReentrantLock跟synchronizd一样可以实现线程间的同步互斥,但是更加强大。

      Lock lockins=new ReentrantLock();
    // 获取对象锁  
    lockins.lock();
    // 释放锁  
    lockins.unlock();
     

      2)、关键字synchronized与wait()和notify(/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition(condition.await()与condition.singal)对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition (即对象监视器)实例,线程对象可以注册在批定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
    在使用notify()/notifyAllQ)方法进行通知时,被通知的线程却是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知",这个功能是非常重要的,而且在Condition类中是默认提供的。
    synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现非常大的效率问题。

        注意,condition.await()方法在调用前需使用lock.lock();获取对象锁,否则会抛IllegalMonitorStateException异常

        正确使用condition实现等待/通知 示例

      1 package test.lock;
      2 
      3 import java.util.concurrent.locks.Condition;
      4 import java.util.concurrent.locks.Lock;
      5 import java.util.concurrent.locks.ReentrantLock;
      6 
      7 public class CommonResoure {
      8     private int comm = 10;
      9     volatile  public boolean flag = true;
     10 
     11     Lock lock = new ReentrantLock();
     12     Condition conditionA = lock.newCondition();
     13     Condition conditionB = lock.newCondition();
     14 
     15     public void add() {
     16         try {
     17             lock.lock();
     18             while (!flag) {
     19                 // 类似于synchronized中锁的wait方法,会释放锁
     20                 conditionA.await();
     21             }
     22             comm++;
     23             System.out.println("comm++:" + comm);
     24             flag = false;
     25             // 在A中唤醒B,因为使用了不同的condition,所有直接使用对应的condition即可
     26             // 如使用同一个condition,而又不用signalAll,有可能导致唤醒的是同类,最终到导致都进入了wait状态
     27             conditionB.signal();
     28         } catch (InterruptedException e) {
     29             e.printStackTrace();
     30         }finally {
     31             lock.unlock();
     32         }
     33     }
     34 
     35     public void decrease() {
     36         try {
     37             lock.lock();
     38             while (flag) {
     39                 conditionB.await();
     40             }
     41             comm--;
     42             System.out.println("comm--:" + comm);
     43             flag = true;
     44             conditionA.signal();
     45         } catch (InterruptedException e) {
     46             e.printStackTrace();
     47         }finally {
     48             lock.unlock();
     49         }
     50     }
     51     
     52     public void singalall_A() {
     53         try {
     54             lock.lock();
     55             conditionA.signalAll();
     56             try {
     57                 Thread.sleep(3000);
     58             } catch (InterruptedException e) {
     59                 // TODO Auto-generated catch block
     60                 e.printStackTrace();
     61             }
     62         }finally {
     63             lock.unlock();
     64         }
     65     }
     66     
     67     public void singalall_B() {
     68         try {
     69             lock.lock();
     70             conditionB.signalAll();
     71         }finally {
     72             lock.unlock();
     73         }
     74     }
     75 
     76     public int getComm() {
     77         return comm;
     78     }
     79 
     80     public void setComm(int comm) {
     81         this.comm = comm;
     82     }
     83 
     84 }
     85 
     86 package test.lock;
     87 
     88 public class Produce implements Runnable{
     89     
     90     private CommonResoure res;
     91     
     92     public Produce(CommonResoure res) {
     93         this.res=res;
     94     }
     95     
     96     public void run() {
     97             res.add();
     98     }
     99 
    100     public CommonResoure getRes() {
    101         return res;
    102     }
    103 
    104     public void setRes(CommonResoure res) {
    105         this.res = res;
    106     }
    107     
    108     
    109 }
    110 
    111 package test.lock;
    112 
    113 public class Comsume implements Runnable{
    114     
    115     private CommonResoure res;
    116     
    117     public Comsume(CommonResoure res) {
    118         this.res=res;
    119     }
    120     
    121     public void run() {
    122             res.decrease();
    123     }
    124 }
    125 
    126 package test.lock;
    127 /**
    128  * 生成消费模式,交替出现
    129  * @author sjt
    130  *
    131  */
    132 public class ProduceComsumeTest {
    133     
    134     public static void main(String[] args) throws InterruptedException {
    135         CommonResoure comm=new CommonResoure();
    136         Produce p=new Produce(comm);
    137         Comsume c=new Comsume(comm);
    138         for(int i=0;i<50;i++) {
    139             new Thread(p,"name"+i).start();
    140             new Thread(c,"name"+i).start();
    141         }
    142         Thread.sleep(3000);
    143         //p.getRes().singalall_A();
    144         
    145         Thread[] t=new Thread[Thread.currentThread().getThreadGroup().activeCount()];
    146         Thread.currentThread().getThreadGroup().enumerate(t,true);
    147         for(Thread temp:t){
    148             if(temp!=null) {
    149                 System.out.println(temp.getName());
    150             }
    151         }
    152     }
    153     
    154 
    155 }
    View Code

    4.2 ReentrantReadWriteLock

              读写锁,涉及到的写的都是互斥的,读读是可以共存的

           示例:

      用法跟ReentrantLock一样

      ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    五、线程状态和线程组

         5.1 线程状态

             

                参考:https://blog.csdn.net/pange1991/article/details/53860651

         5.2 线程异常处理

     1 package test.lock;
     2 
     3 public class ThreadExceptinSolveRunnable implements Runnable{
     4     
     5     String userName=null;
     6     
     7     public void run() {
     8         System.out.println(userName.hashCode());
     9     }
    10     
    11 }
    12 
    13 package test.lock;
    14 
    15 import java.lang.Thread.UncaughtExceptionHandler;
    16 
    17 public class ThreadExceptTest {
    18     public static void main(String[] args) {
    19     
    20     ThreadExceptinSolveRunnable solve=new ThreadExceptinSolveRunnable();
    21     Thread t=new Thread(solve);
    22     t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
    23         public void uncaughtException(Thread t, Throwable e) {
    24             e.printStackTrace();
    25             System.out.println("线程出现异常:"+t.getName());
    26         }
    27     });
    28     t.start();
    29     
    30     //也可以设置所有线程的默认的处理器,但是需要继承Thread的方式去实现线程
    31     //t.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler);
    32     }
    33 }
    View Code
  • 相关阅读:
    HighTec-Eclipse for Tricore 的安装方法
    http升级https遇到的问题
    symfony中模板生成路径两种方式
    http请求在https中使用
    git tag标签
    Git查看两个版本之间修改了哪些文件
    mysql将语句写入表中
    使用fiddler抓包模拟器及配置fiddler过滤
    Mixed Content: The page at 'xxx' was loaded over HTTPS, but requested an insecure resource 'xxx'.
    nginx 禁止某IP访问
  • 原文地址:https://www.cnblogs.com/laoyin666/p/9413607.html
Copyright © 2011-2022 走看看