java多线程_java多线程学习 - CSDN
精华内容
参与话题
  • Java多线程精讲上

    2020-08-18 08:47:18
    通过本门课程的学习你可以深刻理解Java多线程的原理及实现运行机制,深刻学习多线程的生命周期、调度、控制等内容。 本门课程(多线程精讲上)涵盖内容如下:线程概述l  多线程的实现方案(2种)l  线程的...
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2016-06-15 15:16:01
    目录(?)[-] ...一扩展javalangThread类二实现javalangRunnable接口三Thread和Runnable的区别四线程状态转换五线程调度六常用函数说明 ...七常见线程名词解释八线程同步九线程数据传递  本文主要

    转自:http://www.mamicode.com/info-detail-517008.html

    目录(?)[-]

    1. 一扩展javalangThread类
    2. 二实现javalangRunnable接口
    3. 三Thread和Runnable的区别
    4. 四线程状态转换
    5. 五线程调度
    6. 六常用函数说明
      1. 使用方式
      2. 为什么要用join方法
    7. 七常见线程名词解释
    8. 八线程同步
    9. 九线程数据传递

         本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。

    首先讲一下进程和线程的区别:

      进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。

      线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

      线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

      多进程是指操作系统能同时运行多个任务(程序)。

      多线程是指在同一程序中有多个顺序流在执行。

    java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。

    一、扩展java.lang.Thread类

    package com.multithread.learning;
    /**
     *@functon 多线程学习
     *@author 林炳文
     *@time 2015.3.9
     */
    class Thread1 extends Thread{
    	private String name;
        public Thread1(String name) {
           this.name=name;
        }
    	public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + "运行  :  " + i);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
    	}
    }
    public class Main {
    
    	public static void main(String[] args) {
    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=new Thread1("B");
    		mTh1.start();
    		mTh2.start();
    
    	}
    
    }
    
    输出:

    A运行  :  0
    B运行  :  0
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4
    B运行  :  1
    B运行  :  2
    B运行  :  3
    B运行  :  4

    再运行一下:

    A运行  :  0
    B运行  :  0
    B运行  :  1
    B运行  :  2
    B运行  :  3
    B运行  :  4
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4
    说明:
    程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
     
    注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
    从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
    Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
    实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

    但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=mTh1;
    		mTh1.start();
    		mTh2.start();

    输出:

    Exception in thread "main" java.lang.IllegalThreadStateException
        at java.lang.Thread.start(Unknown Source)
        at com.multithread.learning.Main.main(Main.java:31)

    A运行  :  0
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4

    二、实现java.lang.Runnable接口

    /**
     *@functon 多线程学习
     *@author 林炳文
     *@time 2015.3.9
     */
    package com.multithread.runnable;
    class Thread2 implements Runnable{
    	private String name;
    
    	public Thread2(String name) {
    		this.name=name;
    	}
    
    	@Override
    	public void run() {
    		  for (int i = 0; i < 5; i++) {
    	            System.out.println(name + "运行  :  " + i);
    	            try {
    	            	Thread.sleep((int) Math.random() * 10);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    		
    	}
    	
    }
    public class Main {
    
    	public static void main(String[] args) {
    		new Thread(new Thread2("C")).start();
    		new Thread(new Thread2("D")).start();
    	}
    
    }
    
    输出:

    C运行  :  0
    D运行  :  0
    D运行  :  1
    C运行  :  1
    D运行  :  2
    C运行  :  2
    D运行  :  3
    C运行  :  3
    D运行  :  4
    C运行  :  4

    说明:
    Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
    在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
    实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

    三、Thread和Runnable的区别

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

    package com.multithread.learning;
    /**
     *@functon 多线程学习,继承Thread,资源不能共享
     *@author 林炳文
     *@time 2015.3.9
     */
    class Thread1 extends Thread{
    	private int count=5;
    	private String name;
        public Thread1(String name) {
           this.name=name;
        }
    	public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + "运行  count= " + count--);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
    	}
    }
    
    public class Main {
    
    	public static void main(String[] args) {
    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=new Thread1("B");
    		mTh1.start();
    		mTh2.start();
    
    	}
    
    }
    
    
    输出:

    B运行  count= 5
    A运行  count= 5
    B运行  count= 4
    B运行  count= 3
    B运行  count= 2
    B运行  count= 1
    A运行  count= 4
    A运行  count= 3
    A运行  count= 2
    A运行  count= 1

    从上面可以看出,不同的线程之间count是不同的,这对于卖票系统来说就会有很大的问题,当然,这里可以用同步来作。这里我们用Runnable来做下看看

    /**
     *@functon 多线程学习 继承runnable,资源能共享
     *@author 林炳文
     *@time 2015.3.9
     */
    package com.multithread.runnable;
    class Thread2 implements Runnable{
        private int count=15;
    	@Override
    	public void run() {
    		  for (int i = 0; i < 5; i++) {
    			  System.out.println(Thread.currentThread().getName() + "运行  count= " + count--);
    	            try {
    	            	Thread.sleep((int) Math.random() * 10);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    		
    	}
    	
    }
    public class Main {
    
    	public static void main(String[] args) {
    		
    		Thread2 my = new Thread2();
    	        new Thread(my, "C").start();//同一个mt,但是在Thread中就不可以,如果用同一个实例化对象mt,就会出现异常   
    	        new Thread(my, "D").start();
    	        new Thread(my, "E").start();
    	}
    
    }
    

    输出:

    C运行  count= 15
    D运行  count= 14
    E运行  count= 13
    D运行  count= 12
    D运行  count= 10
    D运行  count= 9
    D运行  count= 8
    C运行  count= 11
    E运行  count= 12
    C运行  count= 7
    E运行  count= 6
    C运行  count= 5
    E运行  count= 4
    C运行  count= 3
    E运行  count= 2

    这里要注意每个线程都是用同一个实例化对象,如果不是同一个,效果就和上面的一样了!

    总结:

    实现Runnable接口比继承Thread类所具有的优势:

    1):适合多个相同的程序代码的线程去处理同一个资源

    2):可以避免java中的单继承的限制

    3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立



    提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

     

    java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

    四、线程状态转换

    技术分享

    1、新建状态(New):新创建了一个线程对象。
    2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    五、线程调度

    线程的调度

    1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
     
    Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
    static int MAX_PRIORITY
              线程可以具有的最高优先级,取值为10。
    static int MIN_PRIORITY
              线程可以具有的最低优先级,取值为1。
    static int NORM_PRIORITY
              分配给线程的默认优先级,取值为5。
     
    Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
     
    每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
    线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
    JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
     
    2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
     
    3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
     
    4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
     
    5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
     
    6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
     注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

    六、常用函数说明

    ①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

    ②join():指等待t线程终止。

    使用方式。

    join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

    Thread t = new AThread(); t.start(); t.join();

    为什么要用join()方法

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

    不加join。
    /**
     *@functon 多线程学习,join
     *@author 林炳文
     *@time 2015.3.9
     */
    package com.multithread.join;
    class Thread1 extends Thread{
    	private String name;
        public Thread1(String name) {
        	super(name);
           this.name=name;
        }
    	public void run() {
    		System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程"+name + "运行 : " + i);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
    	}
    }
    
    public class Main {
    
    	public static void main(String[] args) {
    		System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=new Thread1("B");
    		mTh1.start();
    		mTh2.start();
    		System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
    
    	}
    
    }
    
    
    
    
    输出结果:
    main主线程运行开始!
    main主线程运行结束!
    B 线程运行开始!
    子线程B运行 : 0
    A 线程运行开始!
    子线程A运行 : 0
    子线程B运行 : 1
    子线程A运行 : 1
    子线程A运行 : 2
    子线程A运行 : 3
    子线程A运行 : 4
    A 线程运行结束!
    子线程B运行 : 2
    子线程B运行 : 3
    子线程B运行 : 4
    B 线程运行结束!
    发现主线程比子线程早结束

    加join
    public class Main {
    
    	public static void main(String[] args) {
    		System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=new Thread1("B");
    		mTh1.start();
    		mTh2.start();
    		try {
    			mTh1.join();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		try {
    			mTh2.join();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
    
    	}
    
    }

    运行结果:
    main主线程运行开始!
    A 线程运行开始!
    子线程A运行 : 0
    B 线程运行开始!
    子线程B运行 : 0
    子线程A运行 : 1
    子线程B运行 : 1
    子线程A运行 : 2
    子线程B运行 : 2
    子线程A运行 : 3
    子线程B运行 : 3
    子线程A运行 : 4
    子线程B运行 : 4
    A 线程运行结束!
    主线程一定会等子线程都结束了才结束

    ③yield():暂停当前正在执行的线程对象,并执行其他线程。
            Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
             yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
     
    结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。
    /**
     *@functon 多线程学习 yield
     *@author 林炳文
     *@time 2015.3.9
     */
    package com.multithread.yield;
    class ThreadYield extends Thread{
        public ThreadYield(String name) {
            super(name);
        }
     
        @Override
        public void run() {
            for (int i = 1; i <= 50; i++) {
                System.out.println("" + this.getName() + "-----" + i);
                // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
                if (i ==30) {
                    this.yield();
                }
            }
    	
    }
    }
    
    public class Main {
    
    	public static void main(String[] args) {
    		
    		ThreadYield yt1 = new ThreadYield("张三");
        	ThreadYield yt2 = new ThreadYield("李四");
            yt1.start();
            yt2.start();
    	}
    
    }
    

    运行结果:

    第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。

    第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。

    sleep()和yield()的区别
            sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
            sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU  的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
           另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield()  方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。 

    ④setPriority(): 更改线程的优先级。

        MIN_PRIORITY = 1
           NORM_PRIORITY = 5
               MAX_PRIORITY = 10

    用法:
    Thread4 t1 = new Thread4("t1");
    Thread4 t2 = new Thread4("t2");
    t1.setPriority(Thread.MAX_PRIORITY);
    t2.setPriority(Thread.MIN_PRIORITY);

    ⑤interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭

      要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。

    ⑥wait()

    Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

        单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:

        建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

    /**
     * wait用法
     * @author DreamSea 
     * @time 2015.3.9 
     */
    package com.multithread.wait;
    public class MyThreadPrinter2 implements Runnable {   
    	  
        private String name;   
        private Object prev;   
        private Object self;   
      
        private MyThreadPrinter2(String name, Object prev, Object self) {   
            this.name = name;   
            this.prev = prev;   
            this.self = self;   
        }   
      
        @Override  
        public void run() {   
            int count = 10;   
            while (count > 0) {   
                synchronized (prev) {   
                    synchronized (self) {   
                        System.out.print(name);   
                        count--;  
                        
                        self.notify();   
                    }   
                    try {   
                        prev.wait();   
                    } catch (InterruptedException e) {   
                        e.printStackTrace();   
                    }   
                }   
      
            }   
        }   
      
        public static void main(String[] args) throws Exception {   
            Object a = new Object();   
            Object b = new Object();   
            Object c = new Object();   
            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   
            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   
            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);   
               
               
            new Thread(pa).start();
            Thread.sleep(100);  //确保按顺序A、B、C执行
            new Thread(pb).start();
            Thread.sleep(100);  
            new Thread(pc).start();   
            Thread.sleep(100);  
            }   
    }  
    
    

    输出结果:

    ABCABCABCABCABCABCABCABCABCABC

         先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。
        wait和sleep区别
    共同点: 

    1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。 
    2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 
       如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 
       需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。 
    不同点: 
    1. Thread类的方法:sleep(),yield()等 
       Object的方法:wait()和notify()等 
    2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 
       sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 
    3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
    4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
    所以sleep()和wait()方法的最大区别是:
        sleep()睡眠时,保持对象锁,仍然占有该锁;
        而wait()睡眠时,释放对象锁。
      但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

    sleep()方法
    sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
       sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
      在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 
    wait()方法
    wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
      wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
      wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

    七、常见线程名词解释

    主线程:JVM调用程序main()所产生的线程。
    当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
    后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
    前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。
    线程类的一些常用方法: 

      sleep(): 强迫一个线程睡眠N毫秒。 
      isAlive(): 判断一个线程是否存活。 
      join(): 等待线程终止。 
      activeCount(): 程序中活跃的线程数。 
      enumerate(): 枚举程序中的线程。 
        currentThread(): 得到当前线程。 
      isDaemon(): 一个线程是否为守护线程。 
      setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
      setName(): 为线程设置一个名称。 
      wait(): 强迫一个线程等待。 
      notify(): 通知一个线程继续运行。 
      setPriority(): 设置一个线程的优先级。

    八、线程同步

    1、synchronized关键字的作用域有二种: 
    1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 
    2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 

    2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象; 

    3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法; 

    Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

    总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

    在进一步阐述之前,我们需要明确几点:

    A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

    B.每个对象只有一个锁(lock)与之相关联。

    C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

    接着来讨论synchronized用到不同地方对代码产生的影响:

     

    假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

     

    1.  把synchronized当作函数修饰符时,示例代码如下:

    Public synchronized void methodAAA()

    {

    //….

    }

    这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

    上边的示例代码等同于如下代码:

    public void methodAAA()

    {

    synchronized (this)      //  (1)

    {

           //…..

    }

    }

     (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

    2.同步块,示例代码如下:

                public void method3(SomeObject so)

                  {

                         synchronized(so)

    {

           //…..

    }

    }

    这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

    class Foo implements Runnable

    {

           private byte[] lock = new byte[0];  // 特殊的instance变量

        Public void methodA()

    {

           synchronized(lock) { //… }

    }

    //…..

    }

    注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

    3.将synchronized作用于static 函数,示例代码如下:

          Class Foo

    {

    public synchronized static void methodAAA()   // 同步的static 函数

    {

    //….

    }

    public void methodBBB()

    {

           synchronized(Foo.class)   //  class literal(类名称字面常量)

    }

           }

       代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

    记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

    可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。


    1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
    2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
    3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
    4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
    5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
    6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
    7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

    九、线程数据传递

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。

    9.1、通过构造方法传递数据 
    在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据: 

     
    package mythread; 
    public class MyThread1 extends Thread 
    { 
    private String name; 
    public MyThread1(String name) 
    { 
    this.name = name; 
    } 
    public void run() 
    { 
    System.out.println("hello " + name); 
    } 
    public static void main(String[] args) 
    { 
    Thread thread = new MyThread1("world"); 
    thread.start(); 
    } 
    } 
    
    由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。 

    9.2、通过变量和方法传递数据 
    向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置 name变量: 

     
    package mythread; 
    public class MyThread2 implements Runnable 
    { 
    private String name; 
    public void setName(String name) 
    { 
    this.name = name; 
    } 
    public void run() 
    { 
    System.out.println("hello " + name); 
    } 
    public static void main(String[] args) 
    { 
    MyThread2 myThread = new MyThread2(); 
    myThread.setName("world"); 
    Thread thread = new Thread(myThread); 
    thread.start(); 
    } 
    } 
    
    9.3、通过回调函数传递数据 

    上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value是无法事先就传入线程类的。 

     
    package mythread; 
    class Data 
    { 
    public int value = 0; 
    } 
    class Work 
    { 
    public void process(Data data, Integer numbers) 
    { 
    for (int n : numbers) 
    { 
    data.value += n; 
    } 
    } 
    } 
    public class MyThread3 extends Thread 
    { 
    private Work work; 
    public MyThread3(Work work) 
    { 
    this.work = work; 
    } 
    public void run() 
    { 
    java.util.Random random = new java.util.Random(); 
    Data data = new Data(); 
    int n1 = random.nextInt(1000); 
    int n2 = random.nextInt(2000); 
    int n3 = random.nextInt(3000); 
    work.process(data, n1, n2, n3); // 使用回调函数 
    System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" 
    + String.valueOf(n3) + "=" + data.value); 
    } 
    public static void main(String[] args) 
    { 
    Thread thread = new MyThread3(new Work()); 
    thread.start(); 
    } 
    } 

             林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka


    展开全文
  • java多线程编程详细入门教程

    万次阅读 多人点赞 2018-10-31 15:10:49
    1、概念 &amp;amp;amp;amp;nbsp;...线程是jvm调度的最小单元,也叫做轻量级进程,进程是由...这里提出一个问题,为什么要用多线程?有一下几点,首先,随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,

    ##1、概念
         线程是jvm调度的最小单元,也叫做轻量级进程,进程是由线程组成,线程拥有私有的程序技术器以及栈,并且能够访问堆中的共享资源。这里提出一个问题,为什么要用多线程?有一下几点,首先,随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,而同一个时刻一个线程只能运行在一个cpu上,那么计算机的资源被浪费了,所以需要使用多线程。其次,也是为了提高系统的响应速度,如果系统只有一个线程可以执行,那么当不同用户有不同的请求时,由于上一个请求没处理完,那么其他的用户必定需要在一个队列中等待,大大降低响应速度,所以需要多线程。这里继续介绍多线程的几种状态:
    这里写图片描述
    这里可以看到多线程有六种状态,分别是就绪态,运行态,死亡态,阻塞态,等待态,和超时等待态,各种状态之间的切换如上图所示。这里的状态切换是通过synchronized锁下的方法实现,对应的Lock锁下的方法同样可以实现这些切换。

    ##2、线程的创建
         线程的创建有两种方式,第一种是继承Thread类,第二种是实现Runnable接口。第一个代码:

    class MyThread extends Thread{
    	int j=20;
    	public void run(){
    		for (int i = 0; i < 20; i++) {
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println(this.getName()+",i="+j--);
    		}
    	}
    }
    

    然后main函数中创建:

    class MyThread extends Thread{
    	int j=20;
    	public void run(){
    		for (int i = 0; i < 20; i++) {
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println(this.getName()+",i="+j--);
    		}
    	}
    }
    

    第二种方法:

    class MyRunnable implements Runnable{
    	int j=20;
    	@Override
    	public void run() {
    		for (int i = 0; i < 20; i++) {
    			System.out.println(Thread.currentThread().getName()+",j="+this.j--);
    		}
    	}
    	
    }
    

    main函数中:

    		MyRunnable myRunnable = new MyRunnable();
    		Thread t1 = new Thread(myRunnable);
    		Thread t2 = new Thread(myRunnable);
    		t1.start();
    		t2.start();
    

    这就是两种创建线程的方法,在这两种方法中第二种方法时一般情况下的用法,因为继承只能继承一个类,但是可以实现多个接口,这样拓展性更好。

    ##3、线程安全测试
         线程安全是多线程编程中经常需要考虑的一个问题,线程安全是指多线程环境下多个线程可能会同时对同一段代码或者共享变量进行执行,如果每次运行的结果和单线程下的结果是一致的,那么就是线程安全的,如果每次运行的结果不一致,那么就是线程不安全的。这里对线程安全做一个测试:

    class MyRunnable implements Runnable{
    	static int j=10000;
    	@Override
    	public void run() {
    		for (int i = 0; i < 5000; i++) {
    			System.out.println(j--);
    		}
    	}
    	
    }
    

    main函数中:

    		MyRunnable myRunnable = new MyRunnable();
    		Thread t1 = new Thread(myRunnable);
    		Thread t2 = new Thread(myRunnable);
    		t1.start();
    		t2.start();
    

    可以看到,这里同时两个线程同时对共享变量j进行访问,并且减1,但最后的输出结果却是:

    48
    47
    46
    

    并且多次执行程序的结果还不一致,这就是线程不安全的情况,通过加锁可以保证线程安全。

    ##4、锁
         java中有两种锁,一种是重量级锁synchronized,jdk1.6经过锁优化加入了偏向锁和轻量级锁,一种是JUC并发包下的Lock锁,synchronized锁也称对象锁,每个对象都有一个对象锁。这里通过加锁的方式实现线程安全:
    代码:

    class MyRunnable implements Runnable{
    	static int j=10000;
    	@Override
    	public synchronized void run() {
    		for (int i = 0; i < 5000; i++) {
    			System.out.println(j--);
    		}
    	}
    	
    }
    

    main中创建两个线程,测试多次的结果都是:

    3
    2
    1
    

    说明实现的线程安全,因为当加锁过后,每次只能有一个线程访问被加锁的代码,这样就不会出现线程安全了。

    ##5、sleep
         sleep是让当前线程睡眠,睡眠一段时间后重新获取到cpu的使用权。
    代码如下:

    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    

    这里表示线程会睡眠100ms后再次到就绪状态中,这里为什么sleep是Thread的类方法而不是线程的方法,因为,能调用sleep的线程肯定是运行着的,而其他线程也是未运行的,所以调用其他线程的sleep是没有意义的。

    ##6、wait、notify
         wait表示当前线程释放掉锁进入等待状态,所以调用wait和notify的前提是已经获取到对象的锁,如果没有获取到锁就使用wait那么会出异常信息。而进入等待状态的线程需要通过notify或者通过中断来唤醒进入到阻塞状态来等待锁的获取。这里对这种情况进行测试,使用notify唤醒一个等待状态的线程:

    class MyThreadd extends Thread{
    	public MyThreadd(String name) {
    		// TODO Auto-generated constructor stub
    		super(name);
    	}
    	public void run(){
    		synchronized (this) {
    			System.out.println(Thread.currentThread().getName()+" notify a thread");
    			notify();
    		}
    		while(true);
    	}
    

    main中:

    	MyThreadd t1 = new MyThreadd("t1");
    	synchronized (t1) {
    		System.out.println(Thread.currentThread().getName()+" start t1");
    		t1.start();
    		System.out.println(Thread.currentThread().getName()+" wait");
    		t1.wait();
    		System.out.println(Thread.currentThread().getName()+" waked up");
    	}
    

    这里可以看到,在main函数中,主线程将创建一个线程t1然后进入t1的锁的同步块中启动线程t1,然后调用wait进入等待状态,这个时候线程t1也进入到同步块中,调用notify后释放掉锁,可以看到主线程后续的东西继续被输出。当有多个线程调用了wait之后如果采用notify只会随机的唤醒其中的一个线程进入阻塞态,而采用notifyall会将所有的线程给唤醒。在线程运行结束后会调用notifyall将所有等待状态的线程唤醒。

    ##7、join
         join的作用是让父线程等待子线程运行结束后在运行,通过查看源码可以知道:
    这里写图片描述
    其实也是调用了先获取到子线程的锁然后调用wait方法来实现的,因为当子线程运行结束后会调用notifyall所以主线程会被唤醒并且再次获取到子线程的锁继续运行。

    class MyRuu extends Thread{
    	public MyRuu(String name) {
    		super(name);
    	}
    	public void run() {
    		System.out.println(Thread.currentThread().getName());
    		//while(true);
    	}
    }
    

    main函数中:

    		MyRuu myRuu = new MyRuu("t1");
    		System.out.println(Thread.currentThread().getName()+" start t1");
    		myRuu.start();
    		System.out.println(Thread.currentThread().getName() +" join");
    		myRuu.join();
    		System.out.println(Thread.currentThread().getName() +" waked up");
    

    运行结果:

    main start t1
    main join
    t1
    main waked up
    

    可以看到,当主线程调用join后子线程开始运行,等子线程运行结束后主线程被唤醒。

    ##8、yeild
         yeild的作用是线程让步,当前线调用yeild方法后线程从运行态进入到就绪态重新进入到CPU资源的竞争中。这里进行测试:

    class MyRun extends Thread{
    	Object obj;
    	public MyRun(String name,Object obj) {
    		// TODO Auto-generated constructor stub
    		super(name);
    		this.obj = obj;
    	}
    	public void run(){
    //		synchronized (obj) {
    			for (int i = 0; i < 10; i++) {
    				System.out.println(Thread.currentThread().getName()+ " i="+i);
    				if(i%2 == 0)
    					Thread.yield();
    			}
    //		}
    	}
    }
    

    main函数中:

    		Object obj = new Object();
    		// TODO Auto-generated method stub
    		MyRun t1 = new MyRun("t1",obj);
    		MyRun t2 = new MyRun("t2",obj);
    		t1.start();
    		t2.start();
    

    结果:

    t1 i=0
    t2 i=0
    t1 i=1
    t2 i=1
    t2 i=2
    t1 i=2
    t2 i=3
    t2 i=4
    t1 i=3
    t1 i=4
    t2 i=5
    t2 i=6
    t1 i=5
    t1 i=6
    t2 i=7
    t2 i=8
    t1 i=7
    t1 i=8
    t2 i=9
    t1 i=9
    

    可以看到他们两个基本上是交替运行,而不用yeild让步的话大概率一个线程执行完成了另一个线程才会执行。

    ##9、priority
         priority代表线程的优先级,在JVM中优先级高的线程不一定会先运行,只是先运行的概率会比低优先级的线程大。

    ##10、中断
         对于一个正常运行的线程中,中断基本是没有作用的,只是作为一个标志位来查询。而线程的其他几种状态下如sleep、join、wait状态下是可以被中断,并且通过中断来跳出当前状态的。

    class RunInt extends Thread{
    	public void run() {
    		while(!this.isInterrupted()){
    			synchronized (this) {
    				for (int i = 0; i < 10; i++) {
    					System.out.println(Thread.currentThread().getName()+" i="+i);
    				}
    				try {
    					Thread.sleep(3000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					System.out.println(Thread.currentThread().getName()+" interrupted!");
    					break;
    				}
    			}
    		}
    		System.out.println(Thread.currentThread().getName()+" dead");
    	}
    }
    

    main中:

    		RunInt r1 = new RunInt();
    		r1.start();
    		Thread.yield();
    		synchronized (r1) {
    			System.out.println(Thread.currentThread().getName()+" intertupt r1");
    			r1.interrupt();
    		}
    

    结果

    main intertupt r1
    Thread-0 i=0
    Thread-0 i=1
    Thread-0 i=2
    Thread-0 i=3
    Thread-0 i=4
    Thread-0 i=5
    Thread-0 i=6
    Thread-0 i=7
    Thread-0 i=8
    Thread-0 i=9
    Thread-0 interrupted!
    Thread-0 dead
    

    可以看到,当主线程启动子线程后,子线程会进入到循环中并且进入到睡眠状态,然后主线程通过调用中断让子线程唤醒并且推出循环后死亡。

    ##11、死锁
         死锁指的是,两个线程互相等待对方释放资源导致卡死。例子:

    		Thread t1 = new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				synchronized (A) {
    					try {
    						Thread.sleep(1000);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					synchronized (B) {
    						System.out.println("haha");
    					}
    				}
    			}
    		});
    		Thread t2 = new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				synchronized (B) {
    					synchronized (A) {
    						System.out.println("xixi");
    					}
    				}
    			}
    		});
    		t1.start();
    		t2.start();
    

    可以看到t1线程获得A的锁然后睡眠,然后t2线程获得B的锁然后再等待A释放锁,而线程t1睡眠完成后在等待t2释放B的锁,导致程序卡死。

    ##12、生产者与消费者
         生产者和消费者是多线程中一个很常见的应用场景,这里首先用一个共享变量实现生产者和消费者,接着再使用阻塞队列实现。首先实现第一种:
    仓库代码:

    class Depot{
    	private int capacity;
    	private int size=0;
    	public Depot(int c) {
    		// TODO Auto-generated constructor stub
    		this.capacity = c;
    	}
    	public synchronized void product(int count) throws InterruptedException{
    		while(count>0){
    			if(size >= capacity)
    				wait();
    			//真实生产数量
    			int realcount = (capacity-size)>=count?count:(capacity-size);
    			System.out.print(Thread.currentThread().getName()+"--本次想要生产:"+count+",本次实际生产:"+realcount);
    			//下次生产数量
    			count = count - realcount;
    			//仓库剩余
    			size += realcount;
    			System.out.println(",下次想要生产:"+count+",仓库真实容量:"+size);
    			notifyAll();
    		}
    	}
    	public synchronized void comsume(int count) throws InterruptedException {
    		while(count>0){
    			if(size <= 0)
    				wait();
    			//真实消费数量
    			int realcount = (size>=count)?count:size;
    			System.out.print(Thread.currentThread().getName()+"--本次想要消费:"+count+",本次真实消费:"+realcount);
    			//下次消费数量
    			count = count - realcount;
    			//仓库剩余
    			size -= realcount;
    			System.out.println("下次想要消费:"+count+",仓库剩余:"+size);
    			notify();
    		}
    	}
    }
    

    生产者代码:

    class Producer {
    	Depot depot;
    	public Producer(Depot depot) {
    		// TODO Auto-generated constructor stub
    		this.depot = depot;
    	}
    	public void produce(final int count) {
    		new Thread(){
    			public void run() {
    				try {
    					depot.product(count);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		}.start();
    	}
    }
    

    消费者代码:

    class Consumer{
    	Depot depot;
    	public Consumer(Depot depot) {
    		// TODO Auto-generated constructor stub
    		this.depot = depot;
    	}
    	public void consume(final int count) {
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				try {
    					depot.comsume(count);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		}).start();
    	}
    }
    

    main中:

    		Depot depot = new Depot(100);
    		Producer producer = new Producer(depot);
    		Consumer consumer = new Consumer(depot);
    		producer.produce(60);
    		producer.produce(50);
    		producer.produce(30);
    		consumer.consume(50);
    		consumer.consume(110);
    		producer.produce(40);
    

    结果:

    Thread-0--本次想要生产:60,本次实际生产:60,下次想要生产:0,仓库真实容量:60
    Thread-1--本次想要生产:50,本次实际生产:40,下次想要生产:10,仓库真实容量:100
    Thread-4--本次想要消费:110,本次真实消费:100下次想要消费:10,仓库剩余:0
    Thread-1--本次想要生产:10,本次实际生产:10,下次想要生产:0,仓库真实容量:10
    Thread-4--本次想要消费:10,本次真实消费:10下次想要消费:0,仓库剩余:0
    Thread-5--本次想要生产:40,本次实际生产:40,下次想要生产:0,仓库真实容量:40
    Thread-2--本次想要生产:30,本次实际生产:30,下次想要生产:0,仓库真实容量:70
    Thread-3--本次想要消费:50,本次真实消费:50下次想要消费:0,仓库剩余:20
    

    可以看到实现了生产者消费者模型。
    第二种利用阻塞队列实现。直接利用阻塞队列当做仓库,生产者:

    class Pro1{
    	private BlockingQueue<Integer> blockingQueue1;
    	public Pro1(BlockingQueue<Integer> blockingQueue) {
    		// TODO Auto-generated constructor stub
    		this.blockingQueue1 = blockingQueue;
    	}
    	public void produce(final int count) {
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				for (int i = 0; i < count; i++) {
    					try {
    						Thread.sleep(100);
    						blockingQueue1.put(100);
    						System.out.println("生产者,仓库剩余容量"+blockingQueue1.size());
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    			}
    		}).start();
    
    	}
    }
    

    消费者:

    class Con1{
    	private BlockingQueue<Integer> blockingQueue;
    	public Con1(BlockingQueue<Integer> blockingQueue) {
    		// TODO Auto-generated constructor stub
    		this.blockingQueue = blockingQueue;
    	}
    	public void consume(final int count) {
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				for (int i = 0; i < count; i++) {
    					try {
    						Thread.sleep(100);
    						blockingQueue.take();
    						System.out.println("消费者,本次仓库剩余:"+blockingQueue.size());
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    			}
    		}).start();
    	}
    }
    

    main函数:

    		BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(5);
    		Pro1 pro1 = new Pro1(blockingQueue);
    		Con1 con1 = new Con1(blockingQueue);
    		pro1.produce(10);
    		con1.consume(7);
    

    结果:

    消费者,本次仓库剩余:0
    生产者,仓库剩余容量0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量0
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    生产者,仓库剩余容量2
    生产者,仓库剩余容量3
    
    

    这里阻塞队列的作用是,当容量不足的消费者进入等待队列,而当容量有剩余的时候消费者被唤醒,当容量已满的时候生产者进入等待队列,当容量被消费后生产者被唤醒。

    展开全文
  • java多线程理解总结

    千次阅读 2020-10-09 17:03:16
    1.理解多线程首先我们要了解什么是串行、并发、并行串行:一个线程执行到底,相当于单线程。并发:多个线程交替执行,抢占cpu的时间片,但是速度很快,在外人看来就像是多个线程同时执行。并行:多个线程在不同的cpu...

    1.理解多线程

    首先我们要了解什么是串行并发并行

    串行:一个线程执行到底,相当于单线程。

    并发:多个线程交替执行,抢占cpu的时间片,但是速度很快,在宏观角度看来就像是多个线程同时执行。

    并行:多个线程在不同的cpu核心中同时执行。

     

    并发与并行的区别:

    并发严格的说不是同时执行多个线程,只是线程交替执行且速度很快,相当于同时执行。

    而并行是同时执行多个线程,也就是多个cpu核心同时执行多个线程。

     

    在实际开发中,我们不需要关心是否是并发还是并行,因为cpu会帮我们处理多线程,开发中可以认为多线程就是同时执行多个线程。

    2.进一步理解java中的多线程

    在java中,多个线程同时执行可能会造成线程安全问题(线程之间同时拥有一个变量,且发生了修改),为了避免这个问题,需要同步锁来使一个线程执行时,其他线程会等待这个线程执行完毕才执行,而不是”同时“执行。

     

    public static void main(String[] args) throws Exception {  
    	cachedThreadPool();
    }
    public static void cachedThreadPool() {
    	ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    	for (int i = 0; i < 5; i++) {
                MyThread mt = new MyThread();
    	    cachedThreadPool.execute(mt);//同时开启5个MyThread线程
    	}
    }
    class MyThread extends Thread{
    	@Override
    	public void run() {
    		TestThread.testMethod();
    	}
    }
    class TestThread{
    	private static Lock lock = new ReentrantLock();
    	private static Condition c = lock.newCondition();
    	static int i = 0;
    	static TestThread mt = null;
        public static int testMethod() {
        	try {
        	lock.lock();//同步锁
        	System.out.println(Thread.currentThread().getName() + "进来了!");
        	signal();//唤醒线程
        	 while(i<100){
        		 if(i == 2){
        			 await();//当前线程等待
        			 Thread.sleep(1000);
        			 i+=5;
        		 }
        		 i++;
        		 System.out.println(i);
        		 System.out.println(Thread.currentThread().getName());
        		 if(i==28){
        			// signal();
        		 }
        	 }
            } catch (Exception e) {
    	        e.printStackTrace();
    	    } finally {
    	    	lock.unlock();
    		}
        	return i;
        }
        public static void await(){
        	try {
    			System.out.println("线程等待:" + Thread.currentThread().getName());
    			c.await();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
        }
        public static void signal(){
        	try
            {
                lock.lock();
                System.out.println("唤醒线程:" + Thread.currentThread().getName());
                c.signal();
            }
            finally
            {
                lock.unlock();
            }
        }
    }

    结果:

    如果这个结果大家可以看懂,说明对于多线程已经理解的差不多了。

    分析:

    此时线程4依然还是等待状态,没有其他线程可以唤醒线程4

    如果另i=28时,唤醒线程,则线程2会唤醒线程4,使线程4执行后续操作。

    while(i<10){
        	if(i == 2){
        		await();
        		Thread.sleep(1000);
        		i+=5;
        	}
        	i++;
        	System.out.println(i);
        	System.out.println(Thread.currentThread().getName());
        	if(i==28){
        	    signal();//唤醒最后一个等待的线程
            }
    }
    
     

    结果:

     

    展开全文
  • 多线程面试题(2020)

    2020-04-12 13:43:28
    多线程 1.并行和并发有什么区别? 并行:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑 上来看那些任务是同时执行。 并发:多个处理器或多核处理器同时处理多个任务。 如下图: 并发和并行 并发...

    多线程

    1.并行和并发有什么区别?
    并行:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑 上来看那些任务是同时执行。 并发:多个处理器或多核处理器同时处理多个任务。 如下图: 并发和并行 并发 = 两个队列和一台咖啡机。 并行 = 两个队列和两台咖啡机。

    2.线程和进程的区别?
    一个程序下至少有一个进程,一个进程下至少有一个线程,一个进程下也可以有 多个线程来增加程序的执行速度。

    3.守护线程是什么?
    守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某 种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线 程。

    4.创建线程有哪几种方式?
    创建线程有三种方式: 继承 Thread 重新 run 方法; 实现 Runnable 接口; 实现 Callable 接口。
    5. 说一下 runnable 和 callable 有什么区别?
    runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

    6.线程有哪些状态?
    线程的状态: NEW 尚未启动
    RUNNABLE 正在执行中 BLOCKED 阻塞的(被同步锁或者 IO 锁阻塞) WAITING 永久等待状态 TIMED_WAITING 等待指定的时间重新被唤醒的状态 TERMINATED 执行完成
    7. sleep() 和 wait() 有什么区别? 类的不同:sleep() 来自 Thread,wait() 来自 Object。 释放锁:sleep() 不释放锁;wait() 释放锁。 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤 醒。
    8. notify()和 notifyAll()有什么区别? notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会 将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果 不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程, 具体唤醒哪一个线程由虚拟机控制。

    9.线程的 run() 和 start() 有什么区别? start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重 复调用,而 start() 只能调用一次。

    10.创建线程池有哪几种方式?
    线程池创建有七种方式,最核心的是最后一种: newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个 无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务 处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数 目;newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有 几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的 工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置 时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作 队列; newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使 用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意 味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现; 如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
    newSingleThreadScheduledExecutor() : 创 建 单 线 程 池 , 返 回 ScheduledExecutorService,可以进行定时或周期性的工作调度; newScheduledThreadPool(int corePoolSize):和 newSingleThreadScheduledExecutor() 类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调 度,区别在于单一工作线程还是多个工作线程; newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才 加入这个创建方法,其内部会构建 ForkJoinPool,利用 Work-Stealing 算法,并行 地处理任务,不保证处理顺序; ThreadPoolExecutor():是最原始的线程池创建,上面 1-3 创建方式都是对 ThreadPoolExecutor 的封装。

    11.线程池都有哪些状态?
    RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
    SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
    STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的 线程。
    TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
    TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。 线程池中 submit() 和 execute() 方法有什么区别? execute():只能执行 Runnable 类型的任务。 submit():可以执行 Runnable 和 Callable 类型的任务。 Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返回值。

    12.在 Java 程序中怎么保证多线程的运行安全?
    方法一:使用安全类,比如 Java. util. concurrent 下的类。 方法二:使用自动锁 synchronized。 方法三:使用手动锁 Lock。 手动锁 Java 示例代码如下: Lock lock = new ReentrantLock(); lock. lock(); try { System. out. println(“获得锁”); } catch (Exception e) { // TODO: handle exception } finally { System. out. println(“释放锁”); lock. unlock();
    }

    1. 多线程中 synchronized 锁升级的原理是什么?
      synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第 一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其 线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致 则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环 一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象, 此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升 级。锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方 式,从而减低了锁带来的性能消耗。

    14.什么是死锁?
    当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方 需要的锁,而发生的阻塞现象,我们称为死锁。

    15.怎么防止死锁?
    尽 量 使 用 tryLock(long timeout, TimeUnit unit) 的 方 法 (ReentrantLock 、 ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。 尽量使用 Java. util. concurrent 并发类代替自己手写锁。 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。 尽量减少同步的代码块。

    16.ThreadLocal 是什么?有哪些使用场景?
    ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都 可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 ThreadLocal 的经典使用场景是数据库连接和 session 管理等。

    17.说一下 synchronized 底层实现原理?
    synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是 同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内 部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别 的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大
    刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁: 偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

    18.synchronized 和 volatile 的区别是什么?
    volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。 volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以 保证变量的修改可见性和原子性。 volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

    19.synchronized 和 Lock 有什么区别?
    synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。 synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁, 不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去 释放锁就会造成死锁。 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

    20.synchronized 和 ReentrantLock 区别是什么?
    synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相 差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。 主要区别如下: ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作; ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启 锁;ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。 volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器 优化。 21. 说一下 atomic 的原理? atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原 子操作,从而避免 synchronized 的高开销,执行效率大为提升。

    展开全文
  • Java并发编程75道面试题及答案——稳了

    万次阅读 多人点赞 2017-11-16 14:46:09
    Java并发编程75道面试题及答案,看了就是稳。
  • 接上一篇文章:java多线程&并发面试108问(中) 文章目录接上一篇文章:[java多线程&并发面试108问(中)](https://blog.csdn.net/weixin_44395707/article/details/106097656)80、PriorityBlockingQueue...
  • 1、Java中实现多线程有几种方法 继承Thread类; 实现Runnable接口; 实现Callable接口通过FutureTask包装器来创建Thread线程; 使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了...
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • 深入理解Java多线程(一)

    万次阅读 多人点赞 2018-09-09 15:52:27
    关于java多线程的概念以及基本用法:java多线程基础 1,停止线程 停止线程意味着在线程执行完之前停止正在做的操作,即立刻放弃当前的操作,这并不容易。停止线程可以用Thread.stop()方法,但是这个方法不安全...
  • Java多线程:彻底搞懂线程池

    万次阅读 多人点赞 2020-08-06 10:22:59
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...
  • java多线程编程】三种多线程的实现方式

    万次阅读 多人点赞 2019-01-02 08:49:46
    文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?基于Runnable接口实现多线程Thread 与 Runnable 的关系Callable实现多线程线程...
  • Java多线程之进阶篇(一)

    万次阅读 多人点赞 2018-07-30 14:50:03
    在学习完Java多线程之基础篇(一)和Java多线程之基础篇(二)后接下来开始学习Java多线程之进阶篇的内容。 Java 5 添加了一个新的包到Java平台,这个包是java.util.concurrent包(简称JUC)。这个包包含了有一系列...
  • 万字图解Java多线程

    万次阅读 多人点赞 2020-09-20 16:29:45
    java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等,...
  • java多线程编程实例

    万次阅读 多人点赞 2018-05-25 10:01:22
    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下。1.相关知识:Java多线程程序设计到的知识:(1)对同一个数量...
  • JAVA多线程中join()方法的详细分析

    万次阅读 多人点赞 2020-07-07 11:57:21
    虽然关于讨论线程join()方法的博客已经非常极其特别多了,...当然,这也是因为我对多线程中的各种方法和同步的概念都理解的不是很透彻。通过看别人的分析和自己的实践之后终于想明白了,详细解释一下希望能帮助到...
  • Java多线程实现异步调用

    万次阅读 2018-07-20 14:13:12
    首先我们来一个实际的应用场景:用户请求一些报表数据,但是这些数据需要实时计算,那么用户要等待的时间就会很久,这时候我们就可以用异步的方式来处理,更通俗的场景就是生活中烧开水的时候,你可以去包饺子。...
  • Java多线程常用面试题(含答案,精心总结整理)

    万次阅读 多人点赞 2017-11-23 15:42:43
    Java并发编程问题是面试过程中很容易遇到的问题,提前准备是解决问题的最好办法,将试题总结起来,时常查看会有奇效。 ...这个多线程问题比较简单,可以用join方法实现。 核心: thread.Jo
  • 史上最全Java多线程面试题及答案

    万次阅读 多人点赞 2018-08-20 11:17:08
    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。 这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都...
  • 如何学习Java多线程

    千次阅读 2018-04-09 08:22:26
    最近一段时间,我对《Java并发编程实践》这本经典而又有些难懂的书籍,尝试用了一些简单有趣、通俗易懂的方式进行解读,现整理成GitBook(文末有链接),方便大家阅读。
1 2 3 4 5 ... 20
收藏数 1,020,374
精华内容 408,149
关键字:

java多线程