1、进程与线程

1)进程

程序是指令与数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程程序在处理机上的一次执行过程,是一个动态概念。

进程是一个具有一定独立功能的程序,一个实体,每一个进程都有自己的地址空间


2)进程的状态

进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程具有以下三种基本状态 :

1) 就绪状态(Ready)
2) 运行状态(Running)
3) 阻塞状态(Blocked)

10、多线程与并发 - 图1


3)线程

线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干程序又可以划分成若干个线程。

线程: 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程(单线程程序)

一个程序可以同时执行多个任务,来提高效率。
例如:
(1)同时下载多个电影
(2)同时与多人聊天


4)并行与并发

并行: 就是两个任务同时运行(多个CPU)

并发: 是指两个任务同时请求运行,而处理器一次只能接受一个任务,就会把两个任务安排轮流执行,由于CPU时间片运行时间较短,就会感觉两个任务在同时执行


2、线程的基本使用

static Thread currentThread() 返回对当前正在执行的线程对象的引用

  1. Thread.currentThread().getName() //正在执行线程的,名字

(1)继承Thread类,重写run方法

  1. class MyThread extends Thread{
  2. public void run(){
  3. //逻辑处理
  4. }
  5. }
  6. MyThread mt = new MyThread();
  7. mt.start();

(2)实现Runnable接口,重写run方法(推荐使用)

  1. class MyRunnable implements Runnable{
  2. public void run(){
  3. //逻辑处理
  4. }
  5. }
  6. MyRunnable mr = new MyRunnable();
  7. Thread t = new Thread(mr);
  8. t.start();

3、线程休眠

public static void sleep(long millis)

  1. throws InterruptedException使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),释放CPU的时间片,具体取决于系统定时器和调度程序的精度和准确性。线程不会丢失任何显示器的所有权。

参数:
millis - 以毫秒为单位的睡眠时间长度
异常
IllegalArgumentException - 如果 millis值为负数
InterruptedException - 如果任何线程中断当前线程。 当抛出此异常时, 当前线程的中断状态将被清除。

public static void sleep(long millis,int nanos)

  1. throws InterruptedException 毫秒,纳秒
  1. try {
  2. Thread.sleep(500); //线程休眠,在当前线程的执行中,暂停指定的毫秒数,释放CPU的时间片
  3. } catch (InterruptedException e) {
  4. e.printStackTrace();
  5. }

Example:ThreadDemo1

  1. /**
  2. * <p>
  3. * 线程的休眠
  4. * 在当前线程的执行中,暂停指定的毫秒数,释放CPU的时间片
  5. */
  6. public class ThreadDemo1 {
  7. public static void main(String[] args) {
  8. MyThread mt = new MyThread();
  9. //推荐
  10. MyRunnable mr = new MyRunnable();
  11. Thread t2 = new Thread(mr);
  12. mt.start();//启动线程
  13. t2.start();
  14. for (int i = 0; i < 100; i++) {
  15. System.out.println(Thread.currentThread().getName() + "-" + i);
  16. try {
  17. Thread.sleep(500);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }
  24. /**
  25. * 实现线程的第一种方式:继承thread类
  26. */
  27. class MyThread extends Thread {
  28. @Override
  29. public void run() {
  30. for (int i = 0; i < 100; i++) {
  31. if (this.isInterrupted()) {
  32. break;
  33. }
  34. System.out.println(Thread.currentThread().getName() + "-" + i);
  35. try {
  36. Thread.sleep(500);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. this.interrupt();
  40. }
  41. }
  42. }
  43. }
  44. /**
  45. * 实现线程的第二种方式:实现Runnable接口
  46. */
  47. class MyRunnable implements Runnable {
  48. @Override
  49. public void run() {
  50. for (int i = 0; i < 100; i++) {
  51. System.out.println(Thread.currentThread().getName() + "-" + i);
  52. try {
  53. Thread.sleep(500);
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. }

4、join与中断线程

join方法的主要作用是同步,它可以使得线程之间的并行执行变为串行执行,加入线程,让调用的线程先执行指定时间或执行完毕

public final void join() throws InterruptedException
等待这个线程死亡。
调用此方法的行为方式与调用完全相同
join (0)
异常 InterruptedException - 如果任何线程中断当前线程。 当抛出此异常时, 当前线程的中断状态将被清除。

public void interrupt()
中断这个线程。
除非当前线程中断自身, 这是始终允许的

public static boolean interrupted()
测试当前线程是否中断。 该方法可以清除线程的中断状态 。 换句话说, 如果这个方法被连续调用两次, 那么第二个调用将返回false(除非当前线程再次中断, 在第一个调用已经清除其中断状态之后, 在第二个调用之前已经检查过) 。
忽略线程中断, 因为线程在中断时不存在将被该方法返回false所反映。

自定义标记中断线程,推荐使用

Example:ThreadDemo2

  1. /**
  2. * join方法:
  3. * 加入线程,让调用的线程先执行指定时间或执行完毕
  4. * 中断线程:
  5. * (1)使用interrupt方法来中断线程,设置一个中断状态(标记)
  6. * (2)自定义标记的方式(推荐使用)
  7. */
  8. public class ThreadDemo2 {
  9. public static void main(String[] args){
  10. MyRunable2 mr2 = new MyRunable2();
  11. Thread t = new Thread(mr2);
  12. // t.start();
  13. MyRunable3 mr3 = new MyRunable3();
  14. Thread t2 = new Thread(mr3);
  15. t2.start();
  16. for (int i = 0; i < 50; i++) {
  17. System.out.println(Thread.currentThread().getName()+"--"+i);
  18. try {
  19. Thread.sleep(300);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. if(i==20){
  24. // try {
  25. // t.join();//让t线程执行完毕,再执行主线程
  26. // } catch (InterruptedException e) {
  27. // e.printStackTrace();
  28. // }
  29. // t.interrupt();//中断线程,只是作了一个中断标记
  30. mr3.flag = false;
  31. }
  32. }
  33. }
  34. }
  35. class MyRunable2 implements Runnable{
  36. @Override
  37. public void run() {
  38. for (int i = 0; i < 50; i++) {
  39. if(Thread.interrupted()){//测试中断状态,此方法会把中断状态清除
  40. //....保存数据处理
  41. break;
  42. }
  43. System.out.println(Thread.currentThread().getName()+"--"+i);
  44. try {
  45. Thread.sleep(300);
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. Thread.currentThread().interrupt();
  49. }
  50. }
  51. }
  52. }
  53. class MyRunable3 implements Runnable{
  54. public boolean flag = true;
  55. public MyRunable3(){
  56. flag = true;
  57. }
  58. @Override
  59. public void run() {
  60. int i=0;
  61. while(flag){
  62. System.out.println(Thread.currentThread().getName()+"==="+(i++));
  63. try {
  64. Thread.sleep(300);
  65. } catch (InterruptedException e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. }
  70. }

5、守护线程与yield

yield作用是暂停当前正在执行的线程对象(放弃当前的cpu资源),并执行其他线程。

yield是让当前运行线程回到可执行状态,以允许具有相同优先级的其他线程获得运行机会。

public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时, Java虚拟机将退出。

public final boolean isDaemon()
测试这个线程是否是守护线程。

public static void yield()
暂停当前正在执行的线程对象, 并执行其他线程。 (了解)


6、其他方法与优先级

方法名称 作用
long getId() 返回该线程的标识符
String getName() 返回该线程的名称
void setName(String name) 改变线程的名称,使之与参数name相同
boolean isAlive() 测试线程是否处于活动状态
void setPriority(int newPriority) 更改线程的优先级
static int MAX_PRIORITY 线程可以具有更高优先级
static int MIN_PRIORITY 线程可以具有更低优先级
static int NORM_PRIORITY 分配给线程的默认优先级

Example:ThreadDemo3

  1. package com.vince;
  2. import javax.sound.midi.Soundbank;
  3. /**
  4. * Created by vince on 2017/6/2.
  5. */
  6. public class ThreadDemo3 {
  7. public static void main(String[] args){
  8. MyRunnable4 mr4 = new MyRunnable4();
  9. Thread t = new Thread(mr4);
  10. t.setName("Thread-t");
  11. //优先级高可以提高该线程抢点CPU时间片的概率大
  12. t.setPriority(Thread.MAX_PRIORITY);
  13. //线程可以分成守护线程和 用户线程,当进程中没有用户线程时,JVM会退出
  14. t.setDaemon(true);//把线程设置为守护线程
  15. System.out.println(t.isAlive());
  16. t.start();
  17. System.out.println(t.isAlive());
  18. for (int i = 0; i < 50; i++) {
  19. System.out.println("main--"+i);
  20. try {
  21. Thread.sleep(200);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. if (i==5){
  26. Thread.yield();//让出本次CPU执行时间片
  27. }
  28. }
  29. }
  30. }
  31. class MyRunnable4 implements Runnable{
  32. @Override
  33. public void run() {
  34. for (int i = 0; i < 50; i++) {
  35. System.out.println("--"+i);
  36. try {
  37. Thread.sleep(500);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }
  43. }

7、线程同步

线程同步是确保多线程共享数据的安全性,但同时会牺牲性能,同步过多还可能产生死锁,因此务必按需求使用同步。

1、 多线程共享数据
在多线程的操作中, 多个线程有可能同时处理同一个资源, 这就是多线程中的共享数据。

2、 线程同步
解决数据共享问题, 必须使用同步, 所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行。

线程进行同步, 有以下三种方法:
(1) 同步代码块
synchronized(要同步的对象){
要同步的操作 ;
}
(2) 同步方法
public synchronized void method(){
要同步的操作 ;
}
(3) Lock(ReentrantLock)

3、 同步准则
当编写 synchronized 块时, 有几个简单的准则可以遵循, 这些准则在避免死锁和性能危险的风险方面大有帮助:
(1) 使代码块保持简短。 把不随线程变化的预处理和后处理移出synchronized 块。
(2) 不要阻塞。 如InputStream.read()。
(3) 在持有锁的时候, 不要对其它对象调用方法


Example:ThreadDemo4

  1. package com.vince;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. /**
  4. * 1多线程共享数据时,会发生线程不安全的情况
  5. * 2多线程共享数据必须使用同步
  6. * 3实现同步的三种方法:
  7. * (1)使用同步代码块
  8. * (2)使用同步方法
  9. * (3)使用Lock(更灵活的代码控制)
  10. * 多线程共享数据,会有安全问题 ,使用同步可以解决安全问题 ,但同时会牺牲性能,所以同步的代码块要
  11. * 尽量保持简短,把不随数据变化的相关代码移除同步块,不要阻塞
  12. */
  13. public class ThreadDemo4 {
  14. public static void main(String[] args) {
  15. MyRunnable5 mr5 = new MyRunnable5();
  16. Thread t1 = new Thread(mr5);
  17. Thread t2 = new Thread(mr5);
  18. t1.start();
  19. t2.start();
  20. }
  21. }
  22. class MyRunnable5 implements Runnable {
  23. private int ticket = 10;//售票
  24. //private Object obj = new Object();//同步锁
  25. @Override
  26. public void run() {
  27. for (int i = 0; i < 300; i++) {
  28. // if (ticket > 0) {
  29. // synchronized (this) {
  30. // ticket--;
  31. // try {
  32. // Thread.sleep(1000);
  33. // } catch (InterruptedException e) {
  34. // e.printStackTrace();
  35. // }
  36. // System.out.println("您购买的票剩余" + ticket + "张");
  37. // }
  38. // }
  39. method2();
  40. }
  41. }
  42. //同步方法:同步的对象是当前对象(this)
  43. private synchronized void method() {
  44. if (ticket > 0) {
  45. ticket--;
  46. try {
  47. Thread.sleep(1000);
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. System.out.println("您购买的票剩余" + ticket + "张");
  52. }
  53. }
  54. //互斥锁
  55. ReentrantLock lock = new ReentrantLock();
  56. //Lock实现同步
  57. private void method2() {
  58. lock.lock();//锁
  59. try {
  60. if (ticket > 0) {
  61. ticket--;
  62. try {
  63. Thread.sleep(1000);
  64. } catch (InterruptedException e) {
  65. e.printStackTrace();
  66. }
  67. System.out.println("您购买的票剩余" + ticket + "张");
  68. }
  69. }finally {
  70. lock.unlock();//释放锁
  71. }
  72. }
  73. }

8、死锁

过多的同步有可能出现死锁, 死锁的操作一般是在程序运行的时候才有可能出现。

多线程中要进行资源的共享, 就需要同步, 但同步过多, 就可能造成死锁。

  1. package com.vince;
  2. /**
  3. * Created by vince on 2017/6/5.
  4. * 线程死锁:在一个同步方法中调用了另一个对象的同步方法,可能产生死锁
  5. *
  6. */
  7. public class DeadThreadDemo {
  8. public static void main(String[] args) {
  9. new DeadThread();
  10. }
  11. }
  12. //顾客
  13. class Customer{
  14. public synchronized void say(Waiter w){
  15. System.out.println("顾客说:先吃饭再买单!");
  16. w.doService();
  17. }
  18. public synchronized void doService(){
  19. System.out.println("同意了,买完单再吃饭!");
  20. }
  21. }
  22. //服务员
  23. class Waiter{
  24. public synchronized void say(Customer c){
  25. System.out.println("服务员说:先买单再吃饭!");
  26. c.doService();
  27. }
  28. public synchronized void doService(){
  29. System.out.println("同意了,吃完饭再买单!");
  30. }
  31. }
  32. //死锁线程
  33. class DeadThread implements Runnable{
  34. Customer c = new Customer();
  35. Waiter w = new Waiter();
  36. public DeadThread(){
  37. new Thread(this).start();
  38. w.say(c);
  39. }
  40. @Override
  41. public void run() {
  42. c.say(w);
  43. }
  44. }

9、生产者与消费者应用案例

多线程的开发中有一个最经典的操作案例, 就是生产者-消费者, 生产者不断生产产品, 消费者不断取走产品。

例如: 饭店里的有一个厨师和一个服务员, 这个服务员必须等待厨师准备好膳食。 当厨师准备好时, 他会通知服务员, 之后服务员上菜, 然后返回继续等待。 这是一个任务协作的示例, 厨师代表生产者, 而服务员代表消费者。

使用wait与notify

  1. package com.vince;
  2. /**
  3. * 两个线程协同工作,先生产,再消费
  4. * 面试题 :
  5. * sleep 与 wait的区别?
  6. * sleep:让线程进入休眠状态,让出CPU的时间片,不释放对象监视器的所有权(对象锁)
  7. * wait:让线程进入等待状态,让出CPU的时间片,并释放对象监视器的所有权,等待其它线程通过notify方法来唤醒
  8. */
  9. public class ProducterCustomerDemo {
  10. public static void main(String[] args) {
  11. Food food = new Food();
  12. Producter p = new Producter(food);
  13. Customers c = new Customers(food);
  14. Thread t1 = new Thread(p);
  15. Thread t2 = new Thread(c);
  16. t1.start();
  17. t2.start();
  18. }
  19. }
  20. /**
  21. * 消费者
  22. */
  23. class Customers implements Runnable{
  24. private Food food;
  25. public Customers(Food food){
  26. this.food = food;
  27. }
  28. @Override
  29. public void run() {
  30. for (int i = 0; i < 20; i++) {
  31. food.get();
  32. }
  33. }
  34. }
  35. /**
  36. * 生产者
  37. */
  38. class Producter implements Runnable{
  39. private Food food;
  40. public Producter(Food food){
  41. this.food = food;
  42. }
  43. @Override
  44. public void run() {
  45. for (int i = 0; i < 20; i++) {
  46. if(i%2==0){
  47. food.set("锅包肉","酸甜口味,爽");
  48. }else{
  49. food.set("佛跳墙","大补,滋阴补阳");
  50. }
  51. }
  52. }
  53. }
  54. /**
  55. * 食物
  56. */
  57. class Food{
  58. private String name;
  59. private String desc;
  60. private boolean flag = true; //true表示可以生产,false表示可以消费
  61. /**
  62. * 生产产品
  63. */
  64. public synchronized void set(String name,String desc){
  65. //不能生产
  66. if(!flag){
  67. try {
  68. this.wait();//线程进入等待状态,释放监视器的所有权(对象锁)
  69. } catch (InterruptedException e) {
  70. e.printStackTrace();
  71. }
  72. }
  73. this.setName(name);
  74. try {
  75. Thread.sleep(500);
  76. } catch (InterruptedException e) {
  77. e.printStackTrace();
  78. }
  79. this.setDesc(desc);
  80. flag = false;
  81. this.notify();//唤醒等待的线程(随机的其中一个)
  82. }
  83. /**
  84. * 消费产品
  85. */
  86. public synchronized void get(){
  87. //不能消费
  88. if(flag){
  89. try {
  90. this.wait();
  91. } catch (InterruptedException e) {
  92. e.printStackTrace();
  93. }
  94. }
  95. try {
  96. Thread.sleep(500);
  97. } catch (InterruptedException e) {
  98. e.printStackTrace();
  99. }
  100. System.out.println(this.getName()+"->"+this.getDesc());
  101. flag = true;
  102. this.notify();
  103. }
  104. public String getName() {
  105. return name;
  106. }
  107. public void setName(String name) {
  108. this.name = name;
  109. }
  110. public String getDesc() {
  111. return desc;
  112. }
  113. public void setDesc(String desc) {
  114. this.desc = desc;
  115. }
  116. @Override
  117. public String toString() {
  118. return "Food{" +
  119. "name='" + name + '\'' +
  120. ", desc='" + desc + '\'' +
  121. '}';
  122. }
  123. public Food(String name, String desc) {
  124. this.name = name;
  125. this.desc = desc;
  126. }
  127. public Food() {
  128. }
  129. }

10、线程的生命周期

10、多线程与并发 - 图2


11、线程池

线程池是预先创建线程的一种技术。 线程池在还没有任务到来之前, 创建一定数量的线程,放入空闲队列中, 然后对这些资源进行复用。 减少频繁的创建和销毁对象。
jdk1.5版本以上提供了现成的线程池。
Java里面线程池的顶级接口是Executor, 是一个执行线程的工具。
线程池接口是ExecutorService。

java.util.concurrent 包: 并发编程中很常用的实用工具类
Executor 接口:执行已提交的 Runnable 任务的对象。
ExecutorService 接口:Executor 提供了管理终止的方法, 以及可为跟踪一个或多个异步任务执行状况而生成Future 的方法。
Executors 类:此包中所定义的Executor、 ExecutorService等的工厂和实用方法。

在Executors类里面提供了一些静态工厂, 生成一些常用的线程池。

newSingleThreadExecutor:创建一个单线程的线程池。 这个线程池只有一个线程在工作, 也就是相当于单线程串行执行所有任务。 如果这个唯一的线程因为异常结束, 那么会有一个新的线程来替代它。 此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool: 创建固定大小的线程池。 每次提交一个任务就创建一个线程, 直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程

newCachedThreadPool:创建一个可缓存的线程池。 如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(60秒不执行任务) 的线程, 当任务数增加时, 此线程池又可以智能的添加新线程来处理任务。 此线程池不会对线程池大小做限制, 线程池大小完全依赖于操作系统(或者说JVM) 能够创建的最大线程大小。

newScheduledThreadPool:创建一个大小无限的线程池。 此线程池支持定时以及周期性执行任务的需求。

Example:ThreadDemo5

  1. package com.vince;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.ScheduledExecutorService;
  5. import java.util.concurrent.TimeUnit;
  6. /**
  7. * JDK1.5线程池
  8. */
  9. public class ThreadDemo5 {
  10. public static void main(String[] args) {
  11. //创建线程池(4种)
  12. //1、创建一个单线程的线程池
  13. // ExecutorService es = Executors.newSingleThreadExecutor();
  14. //2、创建一个固定大小的线程池
  15. // ExecutorService es = Executors.newFixedThreadPool(1);
  16. //3、创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
  17. // 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,
  18. // 此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,
  19. // 线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  20. // ExecutorService es = Executors.newCachedThreadPool();
  21. //4、创建一个大小无限的线程池。 此线程池支持定时以及周期性执行任务的需求。
  22. ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
  23. // es.execute(new MyRunable6());
  24. // es.execute(new MyRunable6());
  25. es.schedule(new MyRunable6(),3000, TimeUnit.MILLISECONDS);
  26. es.shutdown();
  27. }
  28. }
  29. class MyRunable6 implements Runnable{
  30. @Override
  31. public void run() {
  32. for (int i = 0; i < 10; i++) {
  33. System.out.println(Thread.currentThread().getName()+"---"+i);
  34. try {
  35. Thread.sleep(300);
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. }
  41. }