• java线程的基础 线程创建与执行 线程的停止与中断 等待与唤醒

    Java线程思维记录图

    在这里插入图片描述

    java线程的基础

    Thread的几个方法和线程组

    展开全文
  • 多线程学习路线图

    目录

    1. 进程和线程
    2. 创建线程的两个方法
    3. 多线程数据共享
    4. 多线程同步(一)——synchronized
    5. 多线程同步(二)——wait, notify, notifyAll, join以及sleep
    6. 多线程同步(三)——volatile。
    7. 多线程同步(四)——CycliBarriar和CountdownLatch。
    8. 线程控制——start, stop, interrupt, suspend, resume, destroy。
    9. Executor框架。
    10. thread dump方法。
    展开全文
  • 本文主要讲了java多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。

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

            写在前面的话:此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢?如果你觉得此文很简单,那推荐你看看Java并发包的的线程池(Java并发编程与技术内幕:线程池深入理解),或者看这个专栏:Java并发编程与技术内幕。你将会对Java里头的高并发场景下的线程有更加深刻的理解。

    目录(?)[-]

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

            本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区别:

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

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

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

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

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

    java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个,有兴趣看这里Java并发编程与技术内幕:Callable、Future、FutureTask、CompletionService )

    一、扩展java.lang.Thread类

    这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread.(笔者推荐使用Runable,后头会说明为什么)。下面来看一个简单的实例

    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接口

    采用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例。

    /**
     *@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接口的话,则很容易的实现资源共享。

    总结:

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

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

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

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

    4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类


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

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

    四、线程状态转换

    下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!


    1、新建状态(New):新创建了一个线程对象。
    2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
    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():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

    ⑥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可以在任何地方使用
    所以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(); 
    } 
    } 

      好了,Java多线程的基础知识就讲到这里了,有兴趣研究多线程的推荐直接看java的源码,你将会得到很大的提升!

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

    展开全文
  • 声明:原文出处http://www.cnblogs.com/nsw2018/p/5822270.html,本人出于学习,收集...转载出处:http://blog.csdn.net/evankakahttp://blog.csdn.net/evankaka/article/details/44153709#t1 (java线程学习的入门...

    声明:原文出处http://www.cnblogs.com/nsw2018/p/5822270.html,本人出于学习,收集干货,不作商业用途!


    以下原作者为林炳文Evankaka的原创作品。转载出处:http://blog.csdn.net/evankaka

    http://blog.csdn.net/evankaka/article/details/44153709#t1  (java线程学习的入门基础知识)

     

    http://blog.csdn.net/evankaka/article/details/51866242  (java线程并发与技术内幕  上)

     

    http://blog.csdn.net/evankaka/article/details/51705661  (ThreadFactory和ThreadLocal)

     

    http://blog.csdn.net/evankaka/article/details/51627380   (TheadGroup线程组应用)

     

    http://blog.csdn.net/evankaka/article/details/51610635  (Callable、Future、FutureTask、CompletionService这几个类的学习总结)

     

    http://blog.csdn.net/evankaka/article/details/51489322   (线程池的深入理解)

     

    http://blog.csdn.net/evankaka   (博主林炳文Evankaka

     

    http://blog.csdn.net/column/details/javahhighconcurrence.html  (汇总)

    展开全文
  • 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多线程学习

    2019-05-06 15:46:07
    什么是线程 进程代表运行中的程序,一个运行的Java程序就是一个进程。线程是进程中可独立执行的子任务,一个进程可以包含多个线程,同一个进程中线程共享该进程所申请到的资源,从jvm看,线程是...什么是多线程 一条...
  • Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多线程学习(四)等待/通知(wait/notify)机制 Java多线程学习(五)线程...
  • Java多线程编程实战指南(核心篇) 高清pdf带目录 随着现代处理器的生产工艺从提升处理器主频频率转向多核化,即在一块芯片上集成多个处理器内核(Core),多核处理器(Multicore Processor)离我们越来越近了――如今...
  • Java多线程编程实战指南(设计模式篇)》采用Java(JDK1.6)语言和UML 为描述语言,并结合作者多年工作经历的相关实战案例,介绍了多线程环境下常用设计模式的来龙去脉:各个设计模式是什么样的及其典型的实际应用...
  • java电子书目录:部分 多线程编程基础第1章 走近Java世界中的线程 21.1 进程、线程与任务 21.2 多线程编程简介 41.2.1 什么是多线程编程 41.2.2 为什么使用多线程 41.3 Java线程API简介 51.3.1 线程的创建...
  • 关注微信公众号:“Java面试通关手册” 回复“Java多线程”获取思维导图源文件和思维导图软件。 多线程就一定好吗?快吗?? 并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不...
  • Java多线程编程实战指南-核心篇Java多线程编程实战指南-核心篇
  • 下载地址:网盘下载 随着CPU 多核时代的到来,多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。而 解决多线程编程中频繁出现的普遍...《Java多线程编程实战指南(设计模式篇)》采用...
  • Java多线程编程实战指南(核心篇)读书笔记
  • 如何学习Java多线程

    2018-04-09 08:22:26
    最近一段时间,我对《Java并发编程实践》这本经典而又有些难懂的书籍,尝试用了一些简单有趣、通俗易懂的方式进行解读,现整理成GitBook(文末有链接),方便大家阅读。
  • Java多线程超详解

    2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多线程学习(四)等待/通知(wait/notify)机制 系列文章将被优先更新于...
  • Java多线程编程实战指南(设计模式篇)》答疑开展以来,不少网友提出的问题既有与本书有关的话题,也有Java多线程编程基础知识的相关话题。由于时间关系,对于重复的问题我不逐一回复。还请各位网友参考本总结。...
  • 首先,整理出一张图概括了Java多线程的体系: 一、进程与线程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 线程,有时被...
  • Java多线程编程学习总结(一) Java多线程编程学习总结(二) 前序:  在2017年参加的大小校招面试过程中,本人也曾经死啃Java多线程编程,抱着一本书天天背诵各种理论知识,详情请见Jav...
1 2 3 4 5 ... 20
收藏数 54,394
精华内容 21,757