• 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) ...Java多线程学习(二)synchronized关键字...Java多线程学习)volatile关键字 Java多线程学习(...

    部分图片无法访问请跳转自:https://juejin.im/post/5ab116875188255561411b8a 阅读。

    该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb/JavaGuide.

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79640870

    Java 并发的基础知识,可能会在笔试中遇到,技术面试中也可能以并发知识环节提问的第一个问题出现。比如面试官可能会问你:“谈谈自己对于进程和线程的理解,两者的区别是什么?”

    一 进程和多线程简介

    1.1 进程和线程

    进程和线程的对比这一知识点由于过于基础,所以在面试中很少碰到,但是极有可能会在笔试题中碰到。常见的提问形式是这样的:“什么是线程和进程?,请简要描述线程与进程的关系、区别及优缺点? ”。

    1.2 何为进程?

    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe文件的运行)。

    进程

    1.3 何为线程?

    线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

    1.4 何为多线程?

    多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。

    1.5 为什么多线程是必要的?

    个人觉得可以用一句话概括:开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

    1.6 为什么提倡多线程而不是多进程?

    线程就是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。

    二 几个重要的概念

    2.1 同步和异步

    同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。

    关于异步目前比较经典以及常用的实现方式就是消息队列:在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。

    2.2 并发(Concurrency)和并行(Parallelism)

    并发和并行是两个非常容易被混淆的概念。它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行”。

    多线程在单核CPU的话是顺序执行,也就是交替运行(并发)。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行(并行)。

    2.3 高并发

    高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

    高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

    2.4 临界区

    临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。在并行程序中,临界区资源是保护的对象。

    2.5 阻塞和非阻塞

    非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回,而阻塞与之相反。

    三 使用多线程常见的三种方式

    前两种实际上很少使用,一般都是用线程池的方式比较多一点。

    ①继承Thread类

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		System.out.println("MyThread");
    	}
    }
    

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		MyThread mythread = new MyThread();
    		mythread.start();
    		System.out.println("运行结束");
    	}
    
    }
    
    

    运行结果:

    运行结束
    MyThread
    

    从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。

    ②实现Runnable接口

    推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。

    MyRunnable.java

    public class MyRunnable implements Runnable {
    	@Override
    	public void run() {
    		System.out.println("MyRunnable");
    	}
    }
    

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		Runnable runnable=new MyRunnable();
    		Thread thread=new Thread(runnable);
    		thread.start();
    		System.out.println("运行结束!");
    	}
    
    }
    

    运行结果:

    运行结束
    MyThread
    

    ③使用线程池

    使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。这里就不给大家演示代码了,线程池这一节会详细介绍到这部分内容。

    四 实例变量和线程安全

    线程类中的实例变量针对其他线程可以有共享和不共享之分。下面通过两个简单的例子来说明!

    4.1 不共享数据的情况

    
    /**
     * 
     * @author SnailClimb
     * @date 2018年10月30日
     * @Description: 多个线程之间不共享变量线程安全的情况
     */
    public class MyThread extends Thread {
    
    	private int count = 5;
    
    	public MyThread(String name) {
    		super();
    		this.setName(name);
    	}
    
    	@Override
    	public void run() {
    		super.run();
    		while (count > 0) {
    			count--;
    			System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count);
    		}
    	}
    
    	public static void main(String[] args) {
    		MyThread a = new MyThread("A");
    		MyThread b = new MyThread("B");
    		MyThread c = new MyThread("C");
    		a.start();
    		b.start();
    		c.start();
    	}
    }
    

    运行结果:
    在这里插入图片描述
    可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另一种情况。

    4.2 共享数据的情况

    /**
     * 
     * @author SnailClimb
     * @date 2018年10月30日
     * @Description: 多个线程之间共享变量线程不安全的情况
     */
    public class SharedVariableThread extends Thread {
    	private int count = 5;
    
    	@Override
    	public void run() {
    		super.run();
    		count--;
    		System.out.println("由 " + SharedVariableThread.currentThread().getName() + " 计算,count=" + count);
    	}
    
    	public static void main(String[] args) {
    
    		SharedVariableThread mythread = new SharedVariableThread();
    		// 下列线程都是通过mythread对象创建的
    		Thread a = new Thread(mythread, "A");
    		Thread b = new Thread(mythread, "B");
    		Thread c = new Thread(mythread, "C");
    		Thread d = new Thread(mythread, "D");
    		Thread e = new Thread(mythread, "E");
    		a.start();
    		b.start();
    		c.start();
    		d.start();
    		e.start();
    	}
    }
    

    运行结果:

    在这里插入图片描述

    可以看出这里已经出现了错误,我们想要的是依次递减的结果。为什么呢??

    因为在大多数jvm中,count–的操作分为如下下三步:

    1. 取得原有count值
    2. 计算i -1
    3. 对i进行赋值

    所以多个线程同时访问时出现问题就是难以避免的了。

    那么有没有什么解决办法呢?

    答案是:当然有,而且很简单。给大家提供两种解决办法:一种是利用 synchronized 关键字(保证任意时刻只能有一个线程执行该方法),一种是利用 AtomicInteger 类(JUC 中的 Atomic 原子类)。大家如果之前没有接触 Java 多线程的话,可能对这两个概念不太熟悉,不过不要担心我后面会一一向你介绍到!这里不能用 volatile 关键字,因为 volatile 关键字不能保证复合操作的原子性。

    五 一些常用方法

    5.1 currentThread()

    返回对当前正在执行的线程对象的引用。

    5.2 getId()

    返回此线程的标识符

    5.3 getName()

    返回此线程的名称

    5.4 getPriority()

    返回此线程的优先级

    5.5 isAlive()

    测试这个线程是否还处于活动状态。

    什么是活动状态呢?

    活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。

    5.6 sleep(long millis)

    使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

    5.7 interrupt()

    中断这个线程。

    5.8 interrupted() 和isInterrupted()

    interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能

    isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志

    5.9 setName(String name)

    将此线程的名称更改为等于参数 name 。

    5.10 isDaemon()

    测试这个线程是否是守护线程。

    5.11 setDaemon(boolean on)

    将此线程标记为 daemon线程或用户线程。

    5.12 join()

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

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

    5.13 yield()

    yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。

    5.14 setPriority(int newPriority)

    更改此线程的优先级

    六 如何停止一个线程呢?

    stop(),suspend(),resume()(仅用于与suspend()一起使用)这些方法已被弃用,所以我这里不予讲解。

    在这里插入图片描述

    6.1 使用interrupt()方法

    我们上面提到了interrupt()方法,先来试一下interrupt()方法能不能停止线程

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 5000000; i++) {
    			System.out.println("i=" + (i + 1));
    		}
    	}
    		public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    	}
    }
    

    运行上诉代码你会发现,线程并不会终止

    针对上面代码的一个改进:

    interrupted()方法判断线程是否停止,如果是停止状态则break

    
    /**
     * 
     * @author SnailClimb
     * @date 2018年10月30日
     * @Description: 使用interrupt()方法终止线程
     */
    public class InterruptThread2 extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 500000; i++) {
    			if (this.interrupted()) {
    				System.out.println("已经是停止状态了!我要退出了!");
    				break;
    			}
    			System.out.println("i=" + (i + 1));
    		}
    		System.out.println("看到这句话说明线程并未终止------");
    	}
    
    	public static void main(String[] args) {
    		try {
    			InterruptThread2 thread = new InterruptThread2();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    	}
    }
    

    运行结果:
    运行结果
    for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。

    6.2 使用return停止线程

    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    			while (true) {
    				if (this.isInterrupted()) {
    					System.out.println("ֹͣ停止了!");
    					return;
    				}
    				System.out.println("timer=" + System.currentTimeMillis());
    			}
    	}
    	public static void main(String[] args) throws InterruptedException {
    		MyThread t=new MyThread();
    		t.start();
    		Thread.sleep(2000);
    		t.interrupt();
    	}
    
    }
    

    运行结果:

    在这里插入图片描述

    当然还有其他停止线程的方法,后面再做介绍。

    七 线程的优先级

    每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低
    优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。

    线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。

    线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。

    Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1)Thread.NORM_PRIORITY(常数5),
    Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)

    学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。

    线程优先级具有继承特性测试代码:

    MyThread1.java:

    public class MyThread1 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread1 run priority=" + this.getPriority());
    		MyThread2 thread2 = new MyThread2();
    		thread2.start();
    	}
    }
    
    

    MyThread2.java:

    public class MyThread2 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread2 run priority=" + this.getPriority());
    	}
    }
    

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		System.out.println("main thread begin priority="
    				+ Thread.currentThread().getPriority());
    		Thread.currentThread().setPriority(6);
    		System.out.println("main thread end   priority="
    				+ Thread.currentThread().getPriority());
    		MyThread1 thread1 = new MyThread1();
    		thread1.start();
    	}
    }
    
    

    运行结果:
    运行结果

    八 Java多线程分类

    8.1 多线程分类

    用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

    守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”

    特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

    应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程

    最常见的守护线程:垃圾回收线程

    8.2 如何设置守护线程?

    可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程

    注意事项:

    1.  setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
    2. 在守护线程中产生的新线程也是守护线程
    3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
    

    MyThread.java:

    public class MyThread extends Thread {
    	private int i = 0;
    
    	@Override
    	public void run() {
    		try {
    			while (true) {
    				i++;
    				System.out.println("i=" + (i));
    				Thread.sleep(100);
    			}
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.setDaemon(true);
    			thread.start();
    			Thread.sleep(5000);
    			System.out.println("我离开thread对象也不再打印了,也就是停止了!");
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    

    运行结果:
    守护线程

    展开全文
  • 本文主要讲了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

    展开全文
  • 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) ...Java多线程学习)volatile关键字 Java多线程学习(...

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693

    系列文章传送门:
    Java多线程学习(一)Java多线程入门

    Java多线程学习(二)synchronized关键字(1)

    Java多线程学习(二)synchronized关键字(2)

    Java多线程学习(三)volatile关键字

    Java多线程学习(四)等待/通知(wait/notify)机制

    系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。

    本节思维导图:

    volatile关键字

    思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册”回复关键字:“Java多线程”免费领取。

    一 简介

    先来看看维基百科对“volatile关键字”的定义:

    在程序设计中,尤其是在C语言、C++、C#和Java语言中,使用volatile关键字声明的变量或对象通常具有与优化、多线程相关的特殊属性。通常,volatile关键字用来阻止(伪)编译器认为的无法“被代码本身”改变的代码(变量/对象)进行优化。如在C语言中,volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。据

    在C环境中,volatile关键字的真实定义和适用范围经常被误解。虽然C++、C#和Java都保留了C中的volatile关键字,但在这些编程语言中volatile的用法和语义却大相径庭。

    Java中的“volatile关键字”关键字:

    在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致
    数据的不一致
    要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
    volatile关键字的可见性

    二 volatile关键字的可见性

    volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性

    RunThread.java

     private boolean isRunning = true;
     int m;
        public boolean isRunning() {
            return isRunning;
        }
        public void setRunning(boolean isRunning) {
            this.isRunning = isRunning;
        }
        @Override
        public void run() {
            System.out.println("进入run了");
            while (isRunning == true) {
                int a=2;
                int b=3;
                int c=a+b;
                m=c;
            }
            System.out.println(m);
            System.out.println("线程被停止了!");
        }
    }

    Run.java

    public class Run {
        public static void main(String[] args) throws InterruptedException {
            RunThread thread = new RunThread();
    
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
    
            System.out.println("已经赋值为false");
        }
    }

    运行结果:
    死循环

    RunThread类中的isRunning变量没有加上volatile关键字时,运行以上代码会出现死循环,这是因为isRunning变量虽然被修改但是没有被写到主存中,这也就导致该线程在本地内存中的值一直为true,这样就导致了死循环的产生。

    解决办法也很简单:isRunning变量前加上volatile关键字即可。
    isRunning变量前加上volatile关键字
    这样运行就不会出现死循环了。
    加上volatile关键字后的运行结果:
    加上volatile关键字后的运行结果

    你是不是以为到这就完了?

    不存在的!!!(这里还有一点需要强调,下面的内容一定要看,不然你在用volatile关键字时会很迷糊,因为书籍几乎都没有提这个问题)

    假如你把while循环代码里加上任意一个输出语句或者sleep方法你会发现死循环也会停止,不管isRunning变量是否被加上了上volatile关键字。

    加上输出语句:

        while (isRunning == true) {
                int a=2;
                int b=3;
                int c=a+b;
                m=c;
                System.out.println(m);
            }

    加上sleep方法:

            while (isRunning == true) {
                int a=2;
                int b=3;
                int c=a+b;
                m=c;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

    这是为什么呢?

    因为:JVM会尽力保证内存的可见性,即便这个变量没有加同步关键字。换句话说,只要CPU有时间,JVM会尽力去保证变量值的更新。这种与volatile关键字的不同在于,volatile关键字会强制的保证线程的可见性。而不加这个关键字,JVM也会尽力去保证可见性,但是如果CPU一直有其他的事情在处理,它也没办法。最开始的代码,一直处于死循环中,CPU处于一直占用的状态,这个时候CPU没有时间,JVM也不能强制要求CPU分点时间去取最新的变量值。而加了输出或者sleep语句之后,CPU就有可能有时间去保证内存的可见性,于是while循环可以被终止

    三 volatile关键字能保证原子性吗?

    《Java并发编程艺术》这本书上说保证但是在自增操作(非原子操作)上不保证,《Java多线程编程核心艺术》这本书说不保证。

    我个人更倾向于这种说法:volatile无法保证对变量原子性的。我个人感觉《Java并发编程艺术》这本书上说volatile关键字保证原子性吗但是在自增操作(非原子操作)上不保证这种说法是有问题的。只是个人看法,希望不要被喷。可以看下面测试代码:

    MyThread.java

    public class MyThread extends Thread {
        volatile public static int count;
    
        private static void addCount() {
            for (int i = 0; i < 100; i++) {
                count=i;
            }
            System.out.println("count=" + count);
    
        }
        @Override
        public void run() {
            addCount();
        }
    }

    Run.java

    public class Run {
        public static void main(String[] args) {
            MyThread[] mythreadArray = new MyThread[100];
            for (int i = 0; i < 100; i++) {
                mythreadArray[i] = new MyThread();
            }
    
            for (int i = 0; i < 100; i++) {
                mythreadArray[i].start();
            }
        }
    
    }

    运行结果:

    上面的“count=i;”是一个原子操作,但是运行结果大部分都是正确结果99,但是也有部分不是99的结果。
    运行结果
    解决办法

    使用synchronized关键字加锁。(这只是一种方法,Lock和AtomicInteger原子类都可以,因为之前学过synchronized关键字,所以我们使用synchronized关键字的方法)

    修改MyThread.java如下:

    public class MyThread extends Thread {
        public static int count;
    
        synchronized private static void addCount() {
            for (int i = 0; i < 100; i++) {
                count=i;
            }
            System.out.println("count=" + count);
        }
        @Override
        public void run() {
            addCount();
        }
    }

    这样运行输出的count就都为99了,所以要保证数据的原子性还是要使用synchronized关键字

    四 synchronized关键字和volatile关键字比较

    • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用synchronized关键字还是更多一些
    • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
    • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
    • volatile关键字用于解决变量在多个线程之间的可见性,而ynchronized关键字解决的是多个线程之间访问资源的同步性。

    参考:
    《Java多线程编程核心技术》

    《Java并发编程的艺术》

    极客学院Java并发编程wiki: http://wiki.jikexueyuan.com/project/java-concurrency/volatile1.html

    如果你觉得博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。

    欢迎关注我的微信公众号:”Java面试通关手册“(一个有温度的微信公众号,无广告,单纯技术分享,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源。你想关注便关注,公众号只是我记录文字和生活的地方,无所谓利益,请不要一棒子打死一群做“自媒体”的人。)

    最后,就是使用阿里云服务器一段时间后,感觉阿里云真的很不错,就申请做了阿里云大使,然后这是我的优惠券地址.

    我的公众号

    展开全文
  • 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2...Java多线程学习)volatile关键字 系列文章将被优先更新...

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79670775

    系列文章传送门:

    Java多线程学习(一)Java多线程入门

    Java多线程学习(二)synchronized关键字(1)

    Java多线程学习(二)synchronized关键字(2)

    Java多线程学习(三)volatile关键字

    系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。

    (2) synchronized同步语句块

    本节思维导图:
    思维导图

    思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册”回复关键字:“Java多线程”免费领取。

    一 synchronized方法的缺点

    使用synchronized关键字声明方法有些时候是有很大的弊端的,比如我们有两个线程一个线程A调用同步方法后获得锁,那么另一个线程B就需要等待A执行完,但是如果说A执行的是一个很费时间的任务的话这样就会很耗时。

    先来看一个暴露synchronized方法的缺点实例,然后在看看如何通过synchronized同步语句块解决这个问题。

    Task.java

    public class Task {
    
        private String getData1;
        private String getData2;
    
        public synchronized void doLongTimeTask() {
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
                getData1 = "长时间处理任务后从远程返回的值1 threadName="
                        + Thread.currentThread().getName();
                getData2 = "长时间处理任务后从远程返回的值2 threadName="
                        + Thread.currentThread().getName();
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    CommonUtils.java

    public class CommonUtils {
    
        public static long beginTime1;
        public static long endTime1;
    
        public static long beginTime2;
        public static long endTime2;
    }
    

    MyThread1.java

    public class MyThread1 extends Thread {
        private Task task;
        public MyThread1(Task task) {
            super();
            this.task = task;
        }
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime1 = System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime1 = System.currentTimeMillis();
        }
    }
    

    MyThread2.java

    public class MyThread2 extends Thread {
        private Task task;
        public MyThread2(Task task) {
            super();
            this.task = task;
        }
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime2 = System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime2 = System.currentTimeMillis();
        }
    }

    Run.java

    public class Run {
    
        public static void main(String[] args) {
            Task task = new Task();
    
            MyThread1 thread1 = new MyThread1(task);
            thread1.start();
    
            MyThread2 thread2 = new MyThread2(task);
            thread2.start();
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            long beginTime = CommonUtils.beginTime1;
            if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {
                beginTime = CommonUtils.beginTime2;
            }
    
            long endTime = CommonUtils.endTime1;
            if (CommonUtils.endTime2 > CommonUtils.endTime1) {
                endTime = CommonUtils.endTime2;
            }
    
            System.out.println("耗时:" + ((endTime - beginTime) / 1000));
        }
    }

    运行结果:
    运行结果
    从运行时间上来看,synchronized方法的问题很明显。可以使用synchronized同步块来解决这个问题。但是要注意synchronized同步块的使用方式,如果synchronized同步块使用不好的话并不会带来效率的提升。

    二 synchronized(this)同步代码块的使用

    修改上例中的Task.java如下:

    public class Task {
    
        private String getData1;
        private String getData2;
    
        public void doLongTimeTask() {
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
    
                String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName="
                        + Thread.currentThread().getName();
                String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName="
                        + Thread.currentThread().getName();
    
                synchronized (this) {
                    getData1 = privateGetData1;
                    getData2 = privateGetData2;
                }
    
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    运行结果:
    运行结果
    从上面代码可以看出当一个线程访问一个对象的synchronized同步代码块时,另一个线程任然可以访问该对象非synchronized同步代码块

    时间虽然缩短了,但是大家考虑一下synchronized代码块真的是同步的吗?它真的持有当前调用对象的锁吗?

    是的。不在synchronized代码块中就异步执行,在synchronized代码块中就是同步执行。

    验证代码:synchronizedDemo1包下

    三 synchronized(object)代码块间使用

    MyObject.java

    public class MyObject {
    }

    Service.java

    public class Service {
    
        public void testMethod1(MyObject object) {
            synchronized (object) {
                try {
                    System.out.println("testMethod1 ____getLock time="
                            + System.currentTimeMillis() + " run ThreadName="
                            + Thread.currentThread().getName());
                    Thread.sleep(2000);
                    System.out.println("testMethod1 releaseLock time="
                            + System.currentTimeMillis() + " run ThreadName="
                            + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    ThreadA.java

    public class ThreadA extends Thread {
    
        private Service service;
        private MyObject object;
    
        public ThreadA(Service service, MyObject object) {
            super();
            this.service = service;
            this.object = object;
        }
    
        @Override
        public void run() {
            super.run();
            service.testMethod1(object);
        }
    }

    ThreadB.java

    public class ThreadB extends Thread {
        private Service service;
        private MyObject object;
    
        public ThreadB(Service service, MyObject object) {
            super();
            this.service = service;
            this.object = object;
        }
    
        @Override
        public void run() {
            super.run();
            service.testMethod1(object);
        }
    
    }

    Run1_1.java

    public class Run1_1 {
    
        public static void main(String[] args) {
            Service service = new Service();
            MyObject object = new MyObject();
    
            ThreadA a = new ThreadA(service, object);
            a.setName("a");
            a.start();
    
            ThreadB b = new ThreadB(service, object);
            b.setName("b");
            b.start();
        }
    }

    运行结果:
    运行结果
    可以看出如下图所示,两个线程使用了同一个“对象监视器”,所以运行结果是同步的。
    同一个对象监视器
    那么,如果使用不同的对象监视器会出现什么效果呢?

    修改Run1_1.java如下:

    public class Run1_2 {
    
        public static void main(String[] args) {
            Service service = new Service();
            MyObject object1 = new MyObject();
            MyObject object2 = new MyObject();
    
            ThreadA a = new ThreadA(service, object1);
            a.setName("a");
            a.start();
    
            ThreadB b = new ThreadB(service, object2);
            b.setName("b");
            b.start();
        }
    }

    运行结果:
    运行结果:
    可以看出如下图所示,两个线程使用了不同的“对象监视器”,所以运行结果不是同步的了。
    不同的对象监视器

    四 synchronized代码块间的同步性

    当一个对象访问synchronized(this)代码块时,其他线程对同一个对象中所有其他synchronized(this)代码块代码块的访问将被阻塞,这说明synchronized(this)代码块使用的“对象监视器”是一个。
    也就是说和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

    另外通过上面的学习我们可以得出两个结论

    1. 其他线程执行对象中synchronized同步方法(上一节我们介绍过,需要回顾的可以看上一节的文章)和synchronized(this)代码块时呈现同步效果;
    2. 如果两个线程使用了同一个“对象监视器”,运行结果同步,否则不同步.

    五 静态同步synchronized方法与synchronized(class)代码块

    synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

    Service.java

    package ceshi;
    
    public class Service {
    
        public static void printA() {
            synchronized (Service.class) {
                try {
                    System.out.println(
                            "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
                    Thread.sleep(3000);
                    System.out.println(
                            "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        synchronized public static void printB() {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
        }
    
        synchronized public void printC() {
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");
            System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");
        }
    
    }

    ThreadA.java

    public class ThreadA extends Thread {
        private Service service;
        public ThreadA(Service service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.printA();
        }
    }

    ThreadB.java

    public class ThreadB extends Thread {
        private Service service;
        public ThreadB(Service service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.printB();
        }
    }

    ThreadC.java

    public class ThreadC extends Thread {
        private Service service;
        public ThreadC(Service service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.printC();
        }
    }
    

    Run.java

    public class Run {
        public static void main(String[] args) {
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            a.setName("A");
            a.start();
    
            ThreadB b = new ThreadB(service);
            b.setName("B");
            b.start();
    
            ThreadC c = new ThreadC(service);
            c.setName("C");
            c.start();
        }
    }

    运行结果:
    运行结果
    从运行结果可以看出:静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。synchronized关键字加到非static静态方法上持有的是对象锁。

    线程A,B和线程C持有的锁不一样,所以A和B运行同步,但是和C运行不同步。
    实例代码:

    六 数据类型String的常量池属性

    在Jvm中具有String常量池缓存的功能

        String s1 = "a";
        String s2="a";
        System.out.println(s1==s2);//true

    上面代码输出为true.这是为什么呢?

    字符串常量池中的字符串只存在一份! 即执行完第一行代码后,常量池中已存在 “a”,那么s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。

    因为数据类型String的常量池属性,所以synchronized(string)在使用时某些情况下会出现一些问题,比如两个线程运行
    synchronized(“abc”){
    }和
    synchronized(“abc”){
    }修饰的方法时,这两个线程就会持有相同的锁,导致某一时刻只有一个线程能运行。所以尽量不要使用synchronized(string)而使用synchronized(object)

    参考:

    《Java多线程编程核心技术》
    《Java并发编程的艺术》

    欢迎关注我的微信公众号:”Java面试通关手册“(一个有温度的微信公众号,无广告,单纯技术分享,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源。)

    最后,就是使用阿里云服务器一段时间后,感觉阿里云真的很不错,就申请做了阿里云大使,然后这是我的优惠券地址.

    我的公众号

    展开全文
  • 学习Java多线程之基础篇(一)和Java多线程之基础篇(二)后接下来开始学习Java多线程之进阶篇的内容。 Java 5 添加了一个新的包到Java平台,这个包是java.util.concurrent包(简称JUC)。这个包包含了有一系列...

    在学习完Java多线程之基础篇(一)Java多线程之基础篇(二)后接下来开始学习Java多线程之进阶篇的内容。
    Java 5 添加了一个新的包到Java平台,这个包是java.util.concurrent包(简称JUC)。这个包包含了有一系列能够让Java的并发编程更加轻松的类。本文使用的Java 7 版本的JUC,下面让我们继续来学习吧!

    一、线程池

    提到线程线程池我们先来说一下线程池的好处,线程池的有点大概可以概括三点:
    (1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
    (2)能有效控制线程池的最大并发数,避免大量线程之间因互相抢夺系统资源而导致的阻塞现象。
    (3)能够对线程进行简单的管理,并提供定时执行以及指向间隔循环执行等功能。

    1.1 线程池的创建

      Java SE 5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Executor允许你管理异步任务的执行,而无须显式的管理线程的生命周期。Executor在Java中启动任务的优选方法。

    public class CachedThreadPool {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
    
            class MyRunnable implements Runnable{
                private int a = 5;
                @Override
                public void run() {
                    synchronized(this){
                        for(int i=0;i<10;i++){
                            if(this.a>0){
                                System.out.println(Thread.currentThread().getName()+" a的值:"+this.a--);
                            }
    
                        }
                    }
                }
    
            }
            ExecutorService exec = Executors.newCachedThreadPool();
            for(int i=0;i<5;i++)
                exec.execute(new MyRunnable());
            exec.shutdown();
    
        }
    
    }
    

    运行结果:

    pool-1-thread-2 a的值:5
    pool-1-thread-1 a的值:5
    pool-1-thread-1 a的值:4
    pool-1-thread-1 a的值:3
    pool-1-thread-3 a的值:5
    pool-1-thread-2 a的值:4
    pool-1-thread-1 a的值:2
    pool-1-thread-1 a的值:1
    pool-1-thread-2 a的值:3
    pool-1-thread-2 a的值:2
    pool-1-thread-2 a的值:1
    pool-1-thread-3 a的值:4
    pool-1-thread-3 a的值:3
    pool-1-thread-3 a的值:2
    pool-1-thread-3 a的值:1
    pool-1-thread-5 a的值:5
    pool-1-thread-5 a的值:4
    pool-1-thread-5 a的值:3
    pool-1-thread-5 a的值:2
    pool-1-thread-5 a的值:1
    pool-1-thread-4 a的值:5
    pool-1-thread-4 a的值:4
    pool-1-thread-4 a的值:3
    pool-1-thread-4 a的值:2
    pool-1-thread-4 a的值:1
    

    说明:
    这个结果可以和Java多线程之基础篇(一)3.2.1定义任务(Runnable)的例子和结果做对比。发现用Executor来管理时,Runnable中的“资源不在共享”,这个疑问我还没有解决?知道的可以告诉我一声。
    ExecutorService是一个接口,并继承了接口Executor。而Executors是一个工具类,下面来看看它们之间的UML图:
    这里写图片描述
    其中最为主要的是ThreadPoolExecutor类和Executors中的四类方法,下面我们来逐个分析。

    1.1.1 ThreadPoolExecutor

    (1)ThreadPoolExecutor简介
      ThreadPoolExecutor是线程池类。对于线程池,可以通俗的将它理解为“存放一定数量的一个线程集合。线程池允许若个线程同时运行,运行同时运行的线程数量就是线程池的容量。当添加到线程池中的线程超过它的容量时,会有一部分线程阻塞等待,线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理。”
    (2)ThreadPoolExecutor的数据结构
    这里写图片描述
    下面是ThreadPoolExecutor类中比较典型的部分代码:

    public class ThreadPoolExecutor extends AbstractExecutorService {
        // 阻塞队列。
        private final BlockingQueue<Runnable> workQueue;
        // 互斥锁
        private final ReentrantLock mainLock = new ReentrantLock();
        // 线程集合。一个Worker对应一个线程。
        private final HashSet<Worker> workers = new HashSet<Worker>();
        // “终止条件”,与“mainLock”绑定。
        private final Condition termination = mainLock.newCondition();
        // 线程池中线程数量曾经达到过的最大值。
        private int largestPoolSize;
        // 已完成任务数量
        private long completedTaskCount;
        // ThreadFactory对象,用于创建线程。
        private volatile ThreadFactory threadFactory;
        // 拒绝策略的处理句柄。
        private volatile RejectedExecutionHandler handler;
        // 保持线程存活时间。
        private volatile long keepAliveTime;
    
        private volatile boolean allowCoreThreadTimeOut;
        // 核心池大小
        private volatile int corePoolSize;
        // 最大池大小
        private volatile int maximumPoolSize;
    
        //构造方法
       public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    }

    对一些关键的变量进行介绍:

    • workers
      workers是HashSet类型,它是一个Worker集合。而一个Worker对应一个线程,也就是说线程池通过workers包含了“一个线程集合”。当Worker对应的线程池启动时,它会执行线程池中的任务;当执行完一个任务后,它会从线程池的阻塞队列中取出一个阻塞的任务来继续运行。workers的作用是:线程池通过它来实现了“允许多个线程同时运行”。
    • workQueue
      workQueue是BlockingQueue类型,它是一个阻塞队列。当线程池中的线程超过它的容量的时候,线程会进入阻塞队列进行阻塞等待。workQueue的作用是:让线程池实现 了阻塞功能。
    • mainLock
      mainLock是互斥锁,通过mainLock实现了对线程池的互斥访问。
    • corePoolSize和maximumPoolSize
      corePoolSize是“核心池大小”,maximumPoolSize是“最大池大小”。它们的作用是:调整“线程池中实际运行的线程的数量”。
      例如,当新任务提交给线程池时(通过execute方法)。
      ——如果此时,线程池中运行的线程数量 小于 corePoolSize;则仅当阻塞队列满时才创建新线程。
      ——如果此时,线程池中运行的线程数量 大于 corePoolSize,但却是 小于 maximumPoolSize;则仅当阻塞队列慢时才创建新线程。
      ——如果此时,corePoolSize和maximumPoolSize相同,则创建了固定大小的线程池。如果maximumPoolSize设置为基本的无界值(如,Integer.MAX_VALUE),则允许线程池适应任意数量的并发任务。在大多数情况下,核心池大小和最大池大小的值在创建线程池设置的。但是,也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)进行动态更改。
    • poolSize
      poolSize是当前线程池的实际大小,即线程池中任务的数量。
    • allowCoreThreadTimeOut和keepAliveTime
      allowCoreThreadTimeOut表示是否允许“线程在空闲状态时,仍然能够存活”。
      keepAliveTime表示线程池处于空闲状态的时候,超过keepAliveTime时间之后,空闲的线程会被终止。
    • threadFactory
      threadFactory是ThreadFactory对象,它是一个线程工厂类,即“线程池通ThreadFactory创建线程”
    • handler
      handler是RejectedExecutionHandler类型。它是“线程池拒绝策略”的句柄,也就是说“当某任务添加到线程池中,而线程池拒绝任务是,线程池会通过handler进行相应的处理”

    综上所述,线程池通过workers来管理“线程集合”,每个线程在启动后,会执行线程池中的任务;当一个任务执行完后,它会从线程池的阻塞队列中取出任务来继续运行。阻塞队列时管理线程池任务的队列,当添加到线程池中的任务超过线程池的容量时,该任务就会进入阻塞队列进行等候。

    1.1.2 线程池的分类

      ExecutorService是Executor直接的扩展接口,也是最常用的线程池接口,我们通常见到的线程池定时任务线程池都是它的实现类。上面的Executors.newCachedThreadPool();中的Executors还有其他静态方法可以调用,每个方法都有不同特性,它们都是直接或间接的通过配置ThreadPoolExecutor来实现自己的功能特性,这四类线程池分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor。

    (1)FixedThreadPool
      通过Executor的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程池处于空闲状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程线程并且这些核心线程不会被回收,这意味着它能过更加快速的相应外界的请求。newFixedThreadPool方法的实现如下,可以发现FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制的。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

      newFixedThreadPool()在调用ThreadPoolExecutor()时,它传递一个LinkedBlockingQueue()对象,而LinkedBlockingQueue是单向链表实现的阻塞队列。在线程池中,就是通过该阻塞队列来实现“当线程池中任务数量超过允许的任务数量时,部分任务会阻塞等待”。关于LinkedBlockingQueue的实现细节,在后续的文章会继续介绍。
      有了FixedThreadPool,你可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间,因为你不用为每个任务都固定的付出创建线程的开销。在事件驱动的系统中,这种方式较好。

    (2)SingleThreadExecutor
      通过Executor的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得这些任务之间不需要处理线程同步的问题。SingleThreadExecutor方法的实现如下所示:

     public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    

    这对于你希望在另一个线程中连续运行的任何事物(长期存活的任务)来说,这是很有用的,例如监听进入的套接字连接的任务。它对于希望在线程中运行的短任务也是同样方便,例如,更新本地或远程日志的小任务,或者是事件分发线程。

    (3)ScheduledThreadPool
      通过Executors的newScheduledPool方法来创建。它的核心线程数量时固定的,而非核心线程数是没有限制的,并且当非核心线程闲置是会被立即回收。ScheduledThreadPool这类线程主要用于执行定时任务和具有固定周期的重复任务,newScheduledThreadPool方法的实现如下:

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
                return new ScheduledThreadPoolExecutor(corePoolSize);
            }
    
        public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
                  new DelayedWorkQueue());
        }
    

    ScheduledThreadPoolExecutor继承ThreadPoolExecutor,并实现ScheduledExecutorService。

    (4)CachedThreadPool
      通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都是处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时长为60秒,超过60秒闲置线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为在这种场景下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,在很多情况下可以把它简单理解为一个无法存储元素的队列,由于它在实际中较少使用,这里就不探讨了。从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被终止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的,newCachedThreadPool的实现方法如下:

      public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }

    1.2 线程池中任务的添加

    1.2.1 execute()

    execute()定义在ThreadPoolExecutor.java中,源码如下:

    public void execute(Runnable command) {
        // 如果任务为null,则抛出异常。
        if (command == null)
            throw new NullPointerException();
        // 获取ctl对应的int值。该int值保存了"线程池中任务的数量"和"线程池状态"信息
        int c = ctl.get();
        // 当线程池中的任务数量 < "核心池大小"时,即线程池中少于corePoolSize个任务。
        // 则通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 当线程池中的任务数量 >= "核心池大小"时,
        // 而且,"线程池处于允许状态"时,则尝试将任务添加到阻塞队列中。
        if (isRunning(c) && workQueue.offer(command)) {
            // 再次确认“线程池状态”,若线程池异常终止了,则删除任务;然后通过reject()执行相应的拒绝策略的内容。
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 否则,如果"线程池中任务数量"为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        // 如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
        else if (!addWorker(command, false))
            reject(command);
    }

    说明:execute()的作用是将任务添加到线程池中执行。它分为三种情况:
    (1)如果“线程池中任务数量” < “核心池大小” 时,即线程池中少于corePoolSize个任务;此时就新建一个线程,并将该任务添加到线程中进行执行。
    (2)如果“线程池中任务数量” >= “核心池大小” ,并且“线程池是允许状态”;此时,则将任务添加到阻塞队列中阻塞等待。在该情况下,会再次确认“线程状态”,如果“第2次读到的线程池状态”和“第1次读到的线程次状态”不同,则从阻塞队列中删除该任务。
    (3)如果非上述的两种情况,就会尝试新建一个线程,并将该任务添加到线程中进行执行。如果执行失败,则通过reject()拒绝该任务。

    1.2.2 addWorker()

    addWorker()的源码如下:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        // 更新"线程池状态和计数"标记,即更新ctl。
        for (;;) {
            // 获取ctl对应的int值。该int值保存了"线程池中任务的数量"和"线程池状态"信息
            int c = ctl.get();
            // 获取线程池状态。
            int rs = runStateOf(c);
    
            // 有效性检查
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
    
            for (;;) {
                // 获取线程池中任务的数量。
                int wc = workerCountOf(c);
                // 如果"线程池中任务的数量"超过限制,则返回false。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 通过CAS函数将c的值+1。操作失败的话,则退出循环。
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // 检查"线程池状态",如果与之前的状态不同,则从retry重新开始。
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
    
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        // 添加任务到线程池,并启动任务所在的线程。
        try {
            final ReentrantLock mainLock = this.mainLock;
            // 新建Worker,并且指定firstTask为Worker的第一个任务。
            w = new Worker(firstTask);
            // 获取Worker对应的线程。
            final Thread t = w.thread;
            if (t != null) {
                // 获取锁
                mainLock.lock();
                try {
                    int c = ctl.get();
                    int rs = runStateOf(c);
    
                    // 再次确认"线程池状态"
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 将Worker对象(w)添加到"线程池的Worker集合(workers)"中
                        workers.add(w);
                        // 更新largestPoolSize
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                // 如果"成功将任务添加到线程池"中,则启动任务所在的线程。 
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回任务是否启动。
        return workerStarted;
    }

      addWorker()的作用是将firstTask添加到线程池中,并启动该任务。当core为true是,则以corePoolSize为界限,若“线程池中已有任务数量” >= corePoolSize ,那么返回false;当core为false时,则以maximumPoolSize为界限,若“线程池中已有任务数量” >= maximumPoolSize ,则返回false。addWorker()方法会先通过for循环不断尝试更新 ctl状态,ctl 记录了“线程池中任务数量和线程池状态”。更新成功后,在通过try模块来将任务添加到线程池中,并启动任务所在的线程。
      从addWorker()方法中,我们可以发现:线程池在添加任务时,会创建任务对应的Worker对象,而一个Worker对象包含了一个Thread对象。通过将Worker对象添加到“线程的workers集合中”,从而实现将任务添加到线程池中。通过启动Worker对应的Thread线程,则执行该任务。

    1.2.3 submit()

    submit()实际上也是通过调用execute()实现的,源码如下:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    1.3 线程池的关闭

    在ThreadPoolExecutor类中的shutdown()方法源码为:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        // 获取锁
        mainLock.lock();
        try {
            // 检查终止线程池的“线程”是否有权限。
            checkShutdownAccess();
            // 设置线程池的状态为关闭状态。
            advanceRunState(SHUTDOWN);
            // 中断线程池中空闲的线程。
            interruptIdleWorkers();
            // 钩子函数,在ThreadPoolExecutor中没有任何动作。
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            // 释放锁
            mainLock.unlock();
        }
        // 尝试终止线程池
        tryTerminate();
    }

    1.4 使用Callable

      Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE 5 中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它,下面是简单示例:

    public class CallableDemo {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
    
    
            class TaskWithResult implements Callable<String>{
    
                private int id;
                public TaskWithResult(int id){
                    this.id = id;
                }
                @Override
                public String call() throws Exception {
    
                    return "result of Callable "+id;
                }
    
            }
    
            ExecutorService exec = Executors.newCachedThreadPool();
            ArrayList<Future<String>>  results = new ArrayList<Future<String>>();
            for(int i=0;i<5;i++){
                results.add(exec.submit(new TaskWithResult(i)));
            }
    
            for(Future<String> fs:results){
                try {
                    System.out.println(fs.get());
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }finally{
                    exec.shutdown();
                }
            }
        }
    
    }
    

    输出的结果:

    result of Callable 0
    result of Callable 1
    result of Callable 2
    result of Callable 3
    result of Callable 4
    

    submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化。

    二、解决共享资源竞争

      在Java SE5 的java.util.concurrent类库中还包含有定义在java.util.concurrent.locks中的显式的互斥机制。Lcok对象必须被显示的创建、锁定、和释放。因此,它与内间的锁形式相比,代码缺乏优雅性。但是,对于解决某些类型的问题,它更加灵活。下面是用Lock写以解决共享资源的示例:

    public class LockAndUnLock {
    
        static Lock lock = new ReentrantLock();//新建锁
    
        public static void main(String[] args) {
    
            new Thread("A"){
                public void run() {
                    Thread.yield();//当前线程的让步,加快线程切换
                    numPrint();
    
                };
            }.start();
    
            new Thread("B"){
                public void run() {
                    Thread.yield();//当前线程的让步,加快线程切换
                    numPrint();
                };
            }.start();
    
        }
    
        private static void numPrint(){
            lock.lock();
            try{
                for(int i=0;i<10;i++){
                    Thread.sleep(100);
                    System.out.println("当前线程"+Thread.currentThread().getName()+":"+i);
                }
            }catch(Exception e){
    
            }finally{
                lock.unlock();
            }
        }
    
    }
    

    输出结果:

    当前线程A0
    当前线程A1
    当前线程A2
    当前线程A3
    当前线程A4
    当前线程A5
    当前线程A6
    当前线程A7
    当前线程A8
    当前线程A9
    当前线程B:0
    当前线程B:1
    当前线程B:2
    当前线程B:3
    当前线程B:4
    当前线程B:5
    当前线程B:6
    当前线程B:7
    当前线程B:8
    当前线程B:9
    

      可以看出一个被互斥调用的锁,并使用lock()和unlock()方法在numPrint()内创建了临界资源。当你在使用Lock对象时,将这里的所示的惯用法内部化是很重要的:紧接着的对lock()的调用,你必须放再finally子句中带有unlock()的try-finally语句中。尽管try-finally所需的代码比synchronized关键字要多,但是这也代表了显示的Lock对象的优点之一。如果在使用synchronized关键字,某些事务失败了,那么就会抛出一个异常。但是你没有机会去做任何清理工作,以维护系统使其处于良好状态。有了显示的Lock对象,你就可以使用finally子句将系统维护在正确的状态了。
      大体上,当你使用synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显示的Lock对象。

      以上,先对线程池做了大体的介绍,然后我会逐步介绍JUC中的原子类、线程安全的集合、锁以及深层次剖析线程池原理。

    展开全文
  • Java多线程学习)volatile关键字 Java多线程学习(四)等待/通知(wait/notify)机制 Java多线程学习(五)线程间通信知识点补充 系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java...
  • 本思维导图参考高洪岩所著《Java多线程编程核心技术》
  • Java多线程学习

    2016-04-16 11:31:52
    一、线程的理解 线程是一种轻量级的进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。...
  • 声明:原文出处...以下原作者为林炳文Evankaka的原创作品。转载出处:http://blog.csdn.net/evankakahttp://blog.csdn.net/evankaka/article/details/44153709#t1 (java线程学习的入门...
  • 关注微信公众号:“Java面试通关手册” 回复“Java多线程”获取思维导图源文件和思维导图软件。 多线程就一定好吗?快吗?? 并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不...
  • java线程的支持主要体现在Thread类以及Runable接口上,他们都位于java.lang包下,无论是Thread类还是Runable接口,它们都有public void run()方法,这个run方法为我们提供了线程实际工作时的代码,换句话说,我们...
  • 最近,在研究Java多线程的内容目录,将其内容逐步整理并发布。 (一) 基础篇 01. Java多线程系列--“基础篇”01之 基本概念 02. Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式 03. Java...
  • 多线程学习路线图
  • 对于Java程序员来讲,Spring全家桶几乎可以搞定一切,Spring全家桶便是精妙的招式,多线程就是内功心法很重要的一块,线上出现...掌握Java多线程可以:应对面试、优化之路、深入理解。 应对面试 在面试中,求职...
  • 文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?基于Runnable接口实现多线程Thread 与 Runnable 的关系Callable实现多线程线程...
  • 一、概述 在JAVA中,用Thread类代表线程,所有线程对象,都必须是Thread类或者Thread类子类的实例。每个线程的任务就是执行一段顺序执行的代码,JAVA使用线程执行体来...通过继承Thread类来创建并启动多线程的步骤...
  • 学习多线程之前需要先了解以下几个概念。 进程:一般指一个程序运行时的实例,每个进程有自己独立的地址空间。一个进程无法直接访问另一个进程的数据。 线程:是CPU调度的最小单元,一个进程可以有很多线程,每条...
  • 熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...
  • Java多线程学习(二)将分为两篇文章介绍synchronized同步方法另一篇介绍synchronized同步语句块。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) Java...
1 2 3 4 5 ... 20
收藏数 248,533
精华内容 99,413