zoukankan      html  css  js  c++  java
  • 多线程详解

    多线程详解

    多线程实际上就是一种多个任务同时处理的技术。

    比如一个银行只有一个窗口,所有人到这里来排队取钱,这是我们平时的程序,所有人都要排队进行,线性执行。多线程技术则是开设了多个窗口一同对来的客户进行业务处理,多个人可在同一个时间执行取钱这个操作。

    如果你理解了这个概念。我们就要从微观上面继续讲解。

    在微观上这个例子并不能很好地解释多线程发生时的状况。

    进程与线程

    这里要先讲一个小知识,就是时间片轮转技术。它是多线程在微观上的实现,将CPU的运行时间分为多个小小的时间片,每个任务(线程)会被分配这些时间片,当被分到的时候就意味着CPU已经正在处理这个人任务。当这个时间片(分配的运行时间)运行结束(时间到了)。线程将会让出CPU资源,给其他线程。

    您可能想问,说这个实际上似乎没有什么意义,毕竟所有的任务所需要的时间都是一样的,你分开进行,和一同排队进行CPU所要工作的时间不是一样的吗?

    当然如此,但是你忽略了一个重点.就是任务会有阻塞这个问题.如果任务发成阻塞,再不是多线程的情况下,所有的线程都要等待.CPU不做任何运算,等待阻塞任务重新需要CPU资源.

    但是在多线程中这个被阻塞的线程会让出资源,让其他资源进行执行.效率提高.也就是说,只要有任务,CPU就是在执行的,最大限度的利用了CPU资源.


    有了这个先到知识,我们开始了解进程和线程。

    我更愿意将线程和进程的关系看成这个样子。(也许不大准确)

    我们操作计算机中的程序完成各种各样的任务,而每个程序则是依靠进程来工作,这在任务管理器中有很好的展示。

    我们可以看见Typora这个软件有两个进程,占有了CPU。当然你也可以将运行的所有的exe程序理解为一个进程.

    计算机依靠进程来分配各种计算机资源(内存,空间等....).

    而线程就是依靠计算机分配给进程的这些资源来进行工作的,可以这么说线程是进程的实体化.

    线程实际上是一个由程序入口,一个计算机指令序列和一个程序出口构成的,它依靠在进程中的资源(内存,空间)进行工作,线程本身不占据多少空间,(实际上也是占据一些的.非常少,可以忽略不计.)而CPU资源实际上是直接被分配给线程的,用以运行其中的计算机指令.也就是我们说的多线程,实际上是线程同步运行发生的各种问题.

    线程中有的资源
    一个指向当前指令的指针  --必须要知道要执行什么方法吧?所以要有一个
    一个栈   --虚拟机栈
    一个寄存器值集合  --程序计数器?
    一个私有数据区   
    (我觉得就是JVM线程隔离部分)
    

    所以我们总结一下:

    • 计算机靠进程分配资源(系统进行资源分配的独立单位),线程则是被分配给CPU资源(CPU调度的独立单位).
    • 线程是进程的实体
    • 线程无空间,进程有独立空间概念.多个线程共享
    • 一个程序有多个进程,一个进程有多个线程

    java创建线程

    线程创建有两种方法,分别是继承Thread实现Runnable接口.

    我们在讲解这两种方式之前要了解一部分相关的知识,打开javaApi我们可以发现,Thread类实际上实现了Runnable接口,他们之间具有多太关系.也就是说,Thread可以向上转型位Runnable类.

    两者的关系没有什么本质的差别,实际上都是差不多的.

    那么问题来了,为什么要出现这个Runnable方法呢?这不是多此一举嘛!

    并不是的,我们知道在java中只能单继承,也就是说只能有一个父类.

    在这种情况下,所有子类(Obeject的子类这种不算)都会出现问题,他们根本无法在实现多线程了.

    package TEST;
    
    public class HOMEWORK1 {
    }
    class home extends HOMEWORK1,Thread{//这里无法通过编译,会有红色波浪线
        
    }
    

    所以要有Runnable这种实现多线程的方式.

    1.Thread实现多线程

    方式很简单,只要让多线程的实现类直接继承Thread并实现run()方法就行.

     class Alogin extends Thread{
    public void run(){
      LoginServer.dopost1("a","aa");
    }
     }
    

    启动方式比较简单,直接实例化调用start()方法即可.

    2.Runnable实现多线程

    package TEST;
    //测试类
    public class HOMEWORK1 {
        public static void main(String[] args) {
    
            home home = new home();
            Thread thread = new Thread(home);//必须将Runnable对象传入Thread中才可以
            thread.start();//必须调start()开始,如果调用run()则只会按照一个普通程序执行,不会有多线程的特性
    
        }
    }
    //实现Runnable的类
    class home implements Runnable {
        public void run() {
            System.out.println(Thread.currentThread());
        }
    }
    

    3.Runnable与Thread

    看了上面两个内容我们似乎知道了这两者之间的差别,

    但是我们不要忘了Thread既然实现了Runnable接口就意味着可以向上转型,如下代码是正确的.

    public class HOMEWORK1 {
        public static void main(String[] args) {
            Runnable home = new home();//实现Thread的类可以被向上转型为Runnable对象
            Thread thread = new Thread(home);//依然可以成功实现
            thread.start();
        }
    }
    class home extends Thread {
        public void run() {
            System.out.println(Thread.currentThread());
        }
    }
    

    4.线程在实践中的一些特性

    实际上多线程序在运行中有一些和普通程序也有区别.其比较核心的就是随机性.

    public class HOMEWORK1 {
        public static void main(String[] args) {
    
            Runnable home = new home();
            Thread thread = new Thread(home);
            thread.start();
            System.out.println("他先执行----执行成功");
        }
    }
    

    说白了就是跟线程相关的代码未必会按照顺序执行.

    如下是输出:

    用的还是上一个例子中的那个线程类,我们发现并没有因为 thread.start(); System.out.println("他先执行----执行成功");中先执行,就先动用这个线程.这就是随机性.(这是因为异步执行,main方法代表的是一个main线程,而Thread对象激活的thread线程和main不是一个线程,所以他俩同时执行.)

    他先执行----执行成功
    Thread[Thread-1,5,main]
    

    要想解决这个问题就加一个sleep()就ok!

    如下样子:

    public class HOMEWORK1 {
        public static void main(String[] args) throws InterruptedException {
            Runnable home = new home();
            Thread thread = new Thread(home);
            thread.start();
            Thread.sleep(3000);
            System.out.println("他先执行----执行成功");
        }
    }
    Thread[Thread-1,5,main]
    他先执行----执行成功
    

    附加内容--停止线程

    一个线程既然能启动,就能终止。那么在多线程中如何终止线程。

    实际上多线程中并不能主动终止线程,这里的主动终止是指调用方法去终止

    不能说完全没有,实际上也有一个方法

    stop()

    但因为容易导致问题,所以这个代码也就不再使用了.

    所以停止线程一般使用一些复杂一些的方式,主要有如下几个:

    return停止  //实际上是让run()方法终止,线程自动死亡
    interrupt停止//这个操作实际上要复杂很多,要混合其他方法进行停止
    沉睡中停止,sleep() //实际上也是利用异常停止
    异常法停止
        
    

    1.interrupt停止

    interrupt实际上不能停止线程,只会给线程一个停止标志,可以被专门的方法检测到.你必须用相关的方法才能让相关的线程停止.

    public class MyThread extends Thread {
        public void run(){
            for (int i = 0; i <100 ; i++) {
                System.out.println(i);
            }
        }
    }
    class RUN{
        /**
         * 完整的执行了程序,说明
         * interrupt并没有中断程序,正常来讲是要用5000000这种大数据的,但是电脑不好就用100代替.
         * 所以这里要假设一下,主线程停止大的两秒中,myThread线程不会消失.
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();
            myThread.start();
            Thread.sleep(2000);
            myThread.interrupt();//中断信号
        }
    
    

    而这个让线程停止的方法则是

    isterrupted
    isInterrupted
    

    两者的作用都是测试线程是否已经被中断

    但是两者还是有差别的.

    public class MyThread extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i <500000 ; i++) {
                System.out.println(i);
            }
        }
    }
    class Run{
        public static void main(String[] args) throws InterruptedException {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
            System.out.println("是否停止"+thread.interrupted());//false
            System.out.println("是否停止:"+thread.interrupted());//false
            //这是因为isInterrupted判断的是当前线程,这个当前线程就是 main main一直没有给过中断
            System.out.println("end!");
        }
    }
    class Run2{
        public static void main(String[] args) {
            Thread.currentThread().interrupt();
            System.out.println(Thread.currentThread().interrupted());
            System.out.println(Thread.currentThread().interrupted());
            /**
             * true
             * false
             * interrupted(会发生置位
             */
        }
    }
    //如何正确使用isInterrupted
    class RUN3{
        public static void main(String[] args) throws InterruptedException {
            MyThread thread=new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
            System.out.println("是否停止1?"+thread.isInterrupted());//true
            System.out.println("是否停止2?"+thread.isInterrupted());//true
        }
    }
    

    从上面的代码中我们可以看到两者的差别isterrupted方法会导致停止标志置位,即第一次调用如果答案是true,则将停止标志重新置为false.

    interrupt方法如果在线程处于sleep()时挂起调用那么就会出现抛出一个异常

    isInterrupted则是只判断线程是否停止,而不对其进行任何操作.

    2.return停止

    return停止实际上我会在之后讲,即如果run()方法执行完毕,线程自动死亡.所以return可以结束run()方法,run()方法结束则线程死亡,线程死亡了自然线程就结束了.

    这就是return结束线程的逻辑.


    3.沉睡中停止,sleep() ,异常法停止

    这两个问题其实也是一个问题.其核心在于如果在线程中出现异常,线程会自动停止执行,并释放锁.

    而**沉睡中停止,sleep() **方法就是依靠这个

    Thread.sleep();
    Thread.interrupt;
    

    这样程序就会报错,自然程序就停止了.

    5.同步

    a.多线程问题的由来

    在讲解之后的同步问题之前,我们还要讲解一下为什么要有同步的方法的存在.

    出现这种问题的原因就在于多个线程使用同一个资源.

    这种对同一个资源的操作容易导致了问题.

    如果一个资源没用这种共享发生,就不会有问题.

    例子如下:

    package TEST;
    
    public class HOMEWORK1 {
        public static void main(String[] args) throws InterruptedException {
    
            Runnable home = new home();
            System.out.println("线程不共享资源");
            home1 home1 = new home1();
            home1 home2 = new home1();
            home1.start();
            home2.start();
            Thread.sleep(1000);
            System.out.println("线程共享资源");
            Thread thread = new Thread(home,"一号线程");
            Thread thread1 = new Thread(home,"二号线程");
            Thread thread3 = new Thread(home,"三号线程");
            thread.start();
            thread1.start();
            thread3.start();
        }
    }
    class home extends Thread {
        private int count=5;
        public void run() {
            count--;//共享资源每次线程都会减1,值会变化(而且还不是正确的变化,这就是所谓的脏读)
            System.out.println(Thread.currentThread()+"    的值为"+count);
        }
    }
    class home1 extends Thread {
        private int count=5;
        public void run() {
            count--;//不共享资源.每个都是独立的值不会变化
            System.out.println(Thread.currentThread()+"    的值为"+count);
        }
    }
    

    输出的值

    Thread[Thread-1,5,main]    的值为4
    Thread[Thread-2,5,main]    的值为4
    线程共享资源
    Thread[三号线程,5,main]    的值为3
    Thread[二号线程,5,main]    的值为2
    Thread[一号线程,5,main]    的值为3
    

    其实从这个就可以看出来,到底是什么情况了.按照我们的想法count被执行递减.即4,3,2.

    可是问题来了,并没有这样发生--这就是共享资源的多线程导致的问题,叫做脏读,同一时间中多个线程的操作出现了问题.

    解决办法

    要想解决这个问题,有两种主要的办法.

    SynchronizedReentrant类两种方式.

    Synchronized方法

    这种方法实际上就是将加上它的方法,变成一个临界区,类似于打印机,只能有一个人同时使用打印机,而其他人在这个时候只能等待.

    也就说如果一个方法加上了Synchronized方法,那么其他线程就只能排队运行性这个方法.一个线程遇见这个关键字的时候就会试图去获取这个锁,可是如果获取失败,他会等待,并且不断地尝试直到成功.如果有多个线程,那么就会发生争抢.

    将上面代码home类更改一下:

    class home extends Thread {
        private int count=5;
        synchronized public void  run() {//加入了同步关键字
            count--;
            System.out.println(Thread.currentThread()+"    的值为"+count);
        }
    }
    

    我们发现这回输出的内容就正确了

    Thread[一号线程,5,main]    的值为4
    Thread[三号线程,5,main]    的值为3
    Thread[二号线程,5,main]    的值为2
    

    这里有个要注意的事情就是synchronized方法必须用在有共享的对象上.他必须被很多个线程一同使用,才有加入同步的意义,要不然加入同步就是行为艺术,没什么大用.

    而且synchronized对象并不会影响对于非synchronized方法的调用.

    对于这个例子:

    例子  1-1
    class method{
        private int count=5;
        public void str(){
            System.out.println(Thread.currentThread()+"执行");
        }
        synchronized public void gong(){
            //死循环
            while(count==5){
    //            System.out.println("chenggong");
            }
        }
    }
    

    当一个线程调用gong()方法的时候,他永远也无法退出.但是另一个线程调用str()方法却依然可以执行.

    package TEST;
    
    public class test2 {
        public static void main(String[] args) throws InterruptedException {
            method method = new method();
            thread thread = new thread("一号线程",method);
            thread.start();
            Thread.sleep(1000);
            thread2 thread2 = new thread2("二号线程",method);
            thread2.start();
        }
    }
    class thread extends Thread{
      private   method method;
        public thread(String name,  method method){
            super(name);
            this.method=method;
        }
        @Override
        public void run() {
            super.run();
            method.str();
            method.gong();
        }
    }
    class thread2 extends Thread{
        private method method;
        public thread2(String name,  method method){
            super(name);
            this.method=method;
        }
        @Override
        public void run() {
            super.run();
            method.str();
            method.gong();
    //        method.str();
        }
    }
    

    输出结果如下:

    Thread[一号线程,5,main]执行5
    Thread[二号线程,5,main]执行5
    

    实际上加上synchronized关键字的方法并不是单纯的锁上了方法,而是对对象的锁.

    像一个场景:

    共享的类中有两个同步方法,一个写方法一个读方法.

    线程1调用了写方法.而同时线程2调用了读方法,那么这最终的结果肯定不是我们想要的.

    所以我们要调用

    怎么解释这个问题呢

    那就要更改一下方法了

    class method{
        private int count=5;
        public void str(){
            count--;
            System.out.println(Thread.currentThread()+"执行"+count);//如果另一个线程调用count--会让死循环停止,可是这一切并没有发生.死循环依然存在.这可能就是证明了什么叫给对象加锁了吧.
        }
         public void gong(){
            while(count==5){
            }
        }
    }
    

    还有一点要注意就是如果出现异常,自动放弃锁.即一个线程调用方法运行导致抛出了错误,那这个锁会自动被释放.

    为了证明这一点,我还是换一下方法

    class method{
        private int count=5;
        public void str(){
    //        count--;
            System.out.println(Thread.currentThread()+"执行"+count);
        }
         public void gong() throws Exception {
            int i=0;
            while(count==5){
              i++;
                if(i==10){//如果i==10的时候会抛出一个异常
                    throw new Exception("infiniteLoop StopsExecution!");
                }
            }
        }
    }
    

    返回的结果是

    java.lang.Exception: infiniteLoop StopsExecution!
    	at TEST.method.gong(test2.java:59)
    	at TEST.thread.run(test2.java:24)
    Thread[二号线程,5,main]执行5
    java.lang.Exception: infiniteLoop StopsExecution!
    	at TEST.method.gong(test2.java:59)
    	at TEST.thread2.run(test2.java:41)
    

    正常情况下(例子1-1的情况)第二个线程是无法执行的,但是现在抛出两个错误.说明第二个线程执行了.那么这个问题也很好的说明了,出现异常,自动放弃锁..

    有一下性质要记住:

    • 同步不具有继承性
    • 出现异常锁自动释放
    • 在日常操作中单纯给写方法加入同步并不会让最终的值没有问题,实际上如果读方法不加锁,最重的结果还是一样的

    synchronized同步语句块

    如果一个方法中操作过于复杂,但是用到同步的部分又少之又少,那么我们就可以采用同步语句块技术.

    以前是将整个方法都给封锁住,现在可以只封锁一个方法中的一部分,可以说很好的节省了时间.

    做一个小栗子:

    class method{
        private int count=0;
        public void method(){
            for (int i = 0; i <3 ; i++) {//每个线程的第一段0-2数字不一定是连续的
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
            synchronized (this){//每个线程的第二段0-2数字一定是连续的
                for (int i = 0; i <3; i++) {
                    System.out.println(Thread.currentThread().getName()+"   "+i);
                }
            }
        }
    }
    

    输出结果

    线程A   0  <--线程A 第一段0-2开始
    线程B   0  <--线程B 第一段0-2开始
    线程B   1
    线程B   2
    线程B   0  <---线程B 第二段0-2开始
    线程B   1
    线程B   2  <---线程B 第二段0-2结束
    线程A   1   <--线程A 第一段0-2结束
    线程A   2
    线程A   0    <--线程A 第二段0-2开始
    线程A   1
    线程A   2   <--线程A 第二段0-2结束
    

    我们发现第二段永远时连在一起的,但是第一段却是交叉打印的.

    这也就是说:当一个线程访问object对象的synchronized代码块的时候,另一个线程依然可以访问非synchronized代码块.

    这也意味着synchronized代码块有了更小的封锁位置,也意味着更高的效率.

    当然我们举得例子使用的this其他的对象其实也可以但是千万要记得,要使用一个对象才好使,要不然就没有同步效果了.

    例子:

    class method{
        private int count=0;
        public void method(){
            String string=new String();
            for (int i = 0; i <3 ; i++) {
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
            synchronized (string){//修改的地方.每个调用的线程实际上都是用一个全新的对象,因为每当调用的时候才会产生一个全新的string对象
                for (int i = 0; i <3; i++) {
                    System.out.println(Thread.currentThread().getName()+"   "+i);
                }
            }
        }
    }
    

    结果如下

    线程A   0
    线程A   1
    线程A   2
    线程A   0
    线程B   0
    线程B   1
    线程A   1
    线程B   2
    线程B   0
    线程B   1
    线程B   2
    线程A   2
    

    我们可以发现,这里完全是混乱的,意味着并没有实现同步.

    同步的特例

    静态同步synchronized方法与synchronized(class)

    这两种方式分别是个static方法加入一个同步关键词以及让同步代码块中放入一个.class

    这些方式实际上是对类加锁,所有的对象都将排队执行.

    例子如下:

    class method{
        private int count=0;
        public void method(){
            String string=new String();
            for (int i = 0; i <3 ; i++) {
                System.out.println(Thread.currentThread().getName()+"   "+i);
            }
            synchronized (method.class){//给整个对象加锁
                for (int i = 0; i <3; i++) {
                    System.out.println(Thread.currentThread().getName()+"   "+i);
                }
            }
        }
    }
    //测试类也有了变化
    public class test3 {
        public static void main(String[] args) {
            method method = new method();
            method method1=new method();
            thread1 thread1 = new thread1("线程A",method);//传入了不同的method对象
            thread3 thread3 = new thread3("线程B",method1);//传入了不同的method对象
            thread1.start();
            thread3.start();
        }
    }
    

    记不记得我们刚开始讲的,如果传入不同的对象,结果会是两个线程交替执行.

    结果如下.你会发现他们是同步的,同步的令人惊压.这其实就是锁住了整个类的结果.所有的对象都会被视作同一个对象(在同步代码块眼中),他们会同步进行.

    线程A   0
    线程A   1
    线程A   2
    线程B   0
    线程B   1
    线程B   2
    

    ReentrantLock类

    这个类是新增的一个类,其本质来讲似乎和synchronized方法是一样的.调用lock的线程自动持有对象监视器,其他线程只有等待被锁释放时再次争抢.使用和效果synchronized关键字一样,几乎没有什么区别.

    这个方法也是锁住整个对象中所有使用了锁的部分.

    小栗子:

    
    package Thread.ConditionTestMoreMethod;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MyService {
        private Lock lock = new ReentrantLock();//声明锁对象
    
        public void methodA() throws InterruptedException {
            lock.lock();//所住
            System.out.println("MethodA    :" + Thread.currentThread().getName() + "time=" + System.currentTimeMillis() + "start");
            Thread.sleep(5000);//停止一段时间看看会不会被劫走
            System.out.println("MethodA    :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "end");
            lock.unlock();//解除锁定
        }
    
        public void methodB() throws InterruptedException {
            lock.lock();
            System.out.println("MethodA    :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "start");
            Thread.sleep(5000);
            System.out.println("MethodB    :" + Thread.currentThread().getName() + "time" + System.currentTimeMillis() + "end");
            lock.unlock();
        }
    }
    
    class ThreadA extends Thread {
        private MyService service;
    
        public ThreadA(MyService service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                service.methodA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class ThreadAA extends Thread {
        private MyService service;
    
        public ThreadAA(MyService service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                service.methodA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class ThreadB extends Thread {
        private MyService service;
    
        public ThreadB(MyService service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                service.methodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class ThreadBB extends Thread {
        private MyService service;
    
        public ThreadBB(MyService service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                service.methodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class Test {
        public static void main(String[] args) {
            MyService myService = new MyService();
            ThreadA threadA = new ThreadA(myService);
            threadA.setName("A");//这是Thread自带的方法
            threadA.start();
            ThreadAA threadAA = new ThreadAA(myService);
            threadAA.setName("AA");
            threadAA.start();
            ThreadB threadB = new ThreadB(myService);
            threadB.setName("B");
            threadB.start();
            ThreadBB threadBB = new ThreadBB(myService);
            threadBB.setName("BB");
            threadBB.start();
        }
    }
    
    

    结果

    MethodA    :A time=1589378808963start
    MethodA    :A time1589378808963end
    MethodA    :AA time=1589378808964start
    MethodA    :AA time1589378808964end
    MethodA    :B time1589378808965start
    MethodB    :B time1589378808965end
    MethodA    :BB time1589378808966start
    MethodB    :BB time1589378808966end
    

    这是一个对象锁,就是将整个对象全部锁住.就如同上面的结果一样.在访问一个线程MethodA的时候其他线程是连B都碰不了的.即使MethodA和MethodB毫无关联.


    在lock对象中也存在notify()wait()这种方法,只不过使用Condition对象来调用的.Condition提供的方法最主要的好处在于他提供了一种叫做选择性通知的方式.这个功能是很重要的.像是notify()wait()这种方式是由Object提供的.而对他们的操作也是由JVM随机操作的.要想唤醒其他线程就必须将所有的剩余线程都唤醒.无疑这将导致严重的效率问题.

    你可能对此有些疑惑,但是不必担心,请在下面的例子中仔细注意Condition对象的数量.

    为此我们准备了一个小例子:

    package Thread.MustUseConditon;
    
    import Thread.ConditionTestMoreMethod.MyService;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MustUseCondition {
        Lock lock = new ReentrantLock();//创建锁
        public Condition a = lock.newCondition();//每个条件对象发出的指令只跟自己这个对象的有关系.
        public Condition b = lock.newCondition();//而wait( 这种方法则是对所有都要上锁.所以也正是因此
    
        public void awaitA() throws InterruptedException {
            lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
            System.out.println("begin await时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
            a.await();//等待,看看什么时候回复.
            System.out.println("end await时间为" + System.currentTimeMillis() + "              " + Thread.currentThread().getName());
            lock.unlock();//千万记得解锁,要不然就死锁了
        }
    
        public void awaitb() throws InterruptedException {
            lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
            System.out.println("begin awaitB时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
            b.await();//等待,看看什么时候回复.
            System.out.println("end awaitB时间为" + System.currentTimeMillis() + "              " + Thread.currentThread().getName());
            lock.unlock();//千万记得解锁,要不然就死锁了
        }
    
        public void signallA() throws InterruptedException {
            lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
            System.out.println("signall时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
            a.signalAll();//释放全部锁
            lock.unlock();
        }
    //下面的方法并不会被调用,实际上一直到结束,这个都不会被调用.
        public void signallB() throws InterruptedException {
            lock.lock();//不加锁,condtion对象中的await这些方法是无法使用的.很糟糕
            System.out.println("signall时间为" + System.currentTimeMillis() + "           " + Thread.currentThread().getName());
            b.signalAll();//释放全部锁
            lock.unlock();
        }
    }
    
    class ThreadA extends Thread {
        private MustUseCondition service;
    
        public ThreadA(MustUseCondition service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                service.awaitA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class ThreadB extends Thread {
        private MustUseCondition service;
    
        public ThreadB(MustUseCondition service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                service.awaitb();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class test {
        public static void main(String[] args) throws InterruptedException {
            MustUseCondition mustUseCondition = new MustUseCondition();
            ThreadA threadA = new ThreadA(mustUseCondition);
            threadA.setName("A");
            threadA.start();
            ThreadB threadB = new ThreadB(mustUseCondition);
            threadB.setName("B");
            threadB.start();
            Thread.sleep(3000);//确保A和B都停止
            mustUseCondition.signallA();
        }
    }
    

    可能并不小,但是我们可以用比较简单的语言来解释这个程序所要表达的意思.

    Synchronized方法中,如果使用wait()方法,这个线程会等待.要想再启动,必须等待signal()方法.跟所有的线程一同启动.

    但是实际上很多线程是没有必要启动的.condition对象为我们提供了一个新的方式,我们这回可以声明不同的condition对象,让他们为我们有等待需求的condition对象进行分类,让有关联的使用同一个condition对象(调用await()方法).

    这样就可以大大的增加效率.


    生产者消费者模式

    生产者消费者模式,实际上就是一个类中的两个不同的方法有这一个公用的信号量.经过设计,这个信号量可以锁住所有使用本方法的线程.只有调用另一个方法(一共两个方法)才能解锁.

    package Thread.ConditionTest;
    
    import java.util.concurrent.locks.*;
    
    public class ConditionTest {
        private ReentrantLock lock1 = new ReentrantLock();
        private Condition condition = lock1.newCondition();
        private boolean hasValue = false;
      //生产者
        public void set() throws InterruptedException {
            lock1.lock();//先锁住,这样所有访问这个线程的内容就全都卡住了.
            while (hasValue) {
                condition.await();
            }//用循环才能卡住线程,要不然一会一格Signall又全起来了
            hasValue = true;
            System.out.println("打印********");
            condition.signal();
            lock1.unlock();
        }
    //消费者
        public void get() throws InterruptedException {
            lock1.lock();//先锁住
            while (!hasValue) {
                condition.await();
            }//用循环才能卡住线程,要不然一会一格Signall又全起来了
            hasValue = false;
            System.out.println("打印!!!!!!!!!!!");
            condition.signal();
            lock1.unlock();
        }
    }
    //测试部分
    class ThreadA extends Thread {
        private ConditionTest service;
    
        public ThreadA(ConditionTest service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                for (int i = 0; i < 4; i++) {
                    service.get();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class ThreadB extends Thread {
        private ConditionTest service;
    
        public ThreadB(ConditionTest service) {
            super();
            this.service = service;
        }
    
        @Override
        public void run() {
            try {
                for (int i = 0; i < 4; i++) {
                    service.set();//开4个线程
                }
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class test {
        public static void main(String[] args) {
            ConditionTest conditionTest = new ConditionTest();
            ThreadA threadA = new ThreadA(conditionTest);
            threadA.setName("A");
            threadA.start();
            ThreadB threadB = new ThreadB(conditionTest);
            threadB.start();
        }
    }
    
    
    打印********
    打印!!!!!!!!!!!
    打印********
    打印!!!!!!!!!!!
    打印********
    打印!!!!!!!!!!!
    打印********
    打印!!!!!!!!!!!
    

    结果可以很清楚的看到,内容是交替打印的,但是我们一次开启了8个线程也就是说.有相当一部分线程会发生阻塞(这是用到了线程的随机性).这就是生产者消费者模型的问题.


    公平锁非公平锁

    公平锁:表示线程获取所得顺序是按照线程加锁的顺序来分配的,即先来先得到的FIFO先进先出顺序.而非公平锁:就是一种获取锁的抢先机制,是随机获得锁的,和非公平锁不一样的是先来不一定先得到.

    (这个不要跟线程的随机性搞混.这个是在线程的随机性基础之上的行为.而非导致随机性的原因)

    这里就只讲一下使用的方法

    lock=new ReentrantLock(isFair);//公平锁,打印出来的内容不是乱序
    lock=new ReentrantLock(false);//非公平锁.打印出来的内容会是乱序
    

    一些特殊的方法

    int getHoldCount()的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数.

    我们将生产者消费者的代码进行一点小的修改

    public class ConditionTest {
        private ReentrantLock lock1 = new ReentrantLock();
        private Condition condition = lock1.newCondition();
        private boolean hasValue = false;
    
        public void set() throws InterruptedException {
            lock1.lock();.
            while (hasValue) {
                condition.await();
            }
            hasValue = true;
            System.out.println("set调用了几次lock"+lock1.getHoldCount());//这里修改了
            condition.signal();
            get();
            lock1.unlock();
        }
    
        public void get() throws InterruptedException {
            lock1.lock();
            while (!hasValue) {
                condition.await();
            }
            hasValue = false;
            System.out.println("get锁住了多少个"+lock1.getHoldCount());//这里修改了
            condition.signal();
            lock1.unlock();
        }
    }
    

    结果如下

    set调用了几次lock1
    get锁住了多少个2
    set调用了几次lock1
    get锁住了多少个2
    set调用了几次lock1
    get锁住了多少个2
    set调用了几次lock1
    get锁住了多少个2
    这个代码中所有对于调用get的线程都被锁住了.
    

    int getQueueLength()返回正等待获取此锁定的线程估计数,比如5个线程,1一个线程执行,那么调用方法后返回4.

    为了方便,我们更改了一些生产者消费者的代码.并且只运行这个代码.

    public void set() throws InterruptedException {
            lock1.lock();//先锁住,这样所有访问这个线程的内容就全都卡住了.
    //        while (hasValue) {
    //            condition.await();
    //        }//用循环才能卡住线程,要不然一会一格Signall又全起来了
            hasValue = true;
            Thread.sleep(1000);
            System.out.println("set调用了几次lock"+lock1.getQueueLength());
            condition.signal();
            get();
            lock1.unlock();
        }
    
       public static void main(String[] args) {
            ConditionTest conditionTest = new ConditionTest();
            for (int i = 0; i < 4; i++) {
                ThreadA threadA = new ThreadA(conditionTest);
                threadA.start();
            }
    //如下代码将开启4个再run中调用set方法的线程.
    
    set调用了几次lock3
    set调用了几次lock3
    set调用了几次lock3
    set调用了几次lock3
    set调用了几次lock2
    set调用了几次lock2
    

    这是一部分结果,我们可以看到这个基本陈工.

  • 相关阅读:
    2018个人面试记录
    如何用纯代码实现图片CSS3
    JS数组删除
    JS数组去重
    HTML--使用提交按钮,提交数据
    HTML--使用下拉列表框进行多选
    HTML--使用下拉列表框,节省空间
    HTML--使用单选框、复选框,让用户选择
    HTML--文本域,支持多行文本输入
    HTML--文本输入框、密码输入框
  • 原文地址:https://www.cnblogs.com/yanzezhong/p/12887896.html
Copyright © 2011-2022 走看看