Java基础(多线程)

多线程概述

多线程是Java的特点之一, 掌握多线程编程技术, 可以充分利用CPU的资源,更容易解决实际中的问题,多线程技术广泛应用于和网络有关的程序设计中,因此掌握多线程技术,对于学习网络是至关重要的。

什么是进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。在一个操作系统中,每个独立执行的程序都可称为一个进程,也就是 “正在运行的程序”。

什么是线程

一个程序至少有一个进程,一个进程可以包含多个线程。(相当于航母和舰载机),同一进程中的所有线程共享该进程的资源。

总结

  • 多线程是指一个应用程序中有多个线程并发执行。
  • 并发是指通过CPU的调度算法,使用户感觉像是同时处理多个任务,但同一时刻只有一个执行流占用CPU执行。即使多核多CPU环境还是会使用并发,以提高处理效率。(切换执行)
  • 多线程技术并不能直接提高程序的运行效率,而是通过提高CPU的使用率的方式来达到提高效率的目的。

Demo 多线程初体验:同一个Java程序执行多个无限循环

1.package com.java.demo;
2.
3.public class Demo {
4.  public static void main(String[] args) {
5.      // 线程初体验 : 同一个Java程序执行多个无限循环
6.      HelloThread hello = new HelloThread();
7.      WorldThread world = new WorldThread();
8.      
9.      // 执行两个线程类
10.     hello.start();
11.     world.start();
12.     
13.     while (true) {
14.         System.out.println("main -> run ...");
15.     }
16. }
17.}
18.
19.// 定义了一个 `Hello` 线程类
20.class HelloThread extends Thread {
21. @Override
22. public void run() {
23.     while (true) {
24.         System.out.println("hello");
25.     }
26. }
27.}
28.
29.// 定义一个 `World` 线程类
30.class WorldThread extends Thread {
31. @Override
32. public void run() {
33.     while (true) {
34.         System.out.println("world");
35.     }
36. }
37.}
多线程执行图.jpg

线程的创建

Java提供了两种多线程实现的方式.

  1. 继承java.lang包下的Thread类, 重写Thread类的run方法.在run()方法中实现运行在线程上的代码.
  2. 实现java.lang.Runnable接口, 同样是在run()方法中实现运行在线程上的代码.

方式一:继承Thread

并发地运行多个执行线程第一种方法是:将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
start方法和run方法的区别?
run:封装线程任务, 不调用系统资源,开辟新线程。
start:先调用系统资源,启动线程,再执行run方法。

多个线程之间的独立性

  1. 如果java程序中有多条线程, 如果一条线程发生了异常, 不会影响其它线程执行。
  2. 如果主线程发生了异常,程序不会终止, 程序会继续执行其它的子线程, 直到所有子线程的代码都执行完毕后才会结束程序。

继承Thread方式优缺点

  • 优点是: 可以在子类中增加新的成员变量, 使线程具有某种属性,也可以在子类中增加新的方法,使线程具有某种功能。
  • 缺点是: 由于Java不支持类的多继承,Thread 类的子类不能在扩展其他的类。

方式二:实现Runnable接口

Thread类中的run方法就是实现自Runnable接口。run方法是用来封装线程任务的。Runnable接口中,只有一个run方法,因此,这个接口就是专门用来封装线程任务的接口。因此,实现该接口的类,称为线程任务类。
自定义类,实现Runnable接口,把任务对象传给Thread对象。调用Thread对象的start方法,执行Thread的run。

为什么最后执行的是任务类中的run呢?

  1. 在我们创建Thread 类对象时,我们已经将task 任务类的对象作为参数传递给了线程类对象,在其内容就会将task 赋值给内部属性target 进行存储。

  2. 当我们调用Thread 对象的start 方法启动线程时,肯定会执行Thread 类的run 方法。

  3. 在Thread 的run 方法中, 会先判断target 是否为null。 这个target就是我们创建Thread 对象时传入的任务类对象,所以target 不为null,因此就会执行target 的run 方法,也就是任务类的run 方法。

使用接口完成多线程的好处

  1. 避免了Java单继承带来的局限性
  2. 适合多个相同程序代码的线程去处理同一个资源的情况, 更灵活的实现数据的共享。

线程同步

说明 : 多线程的并发执行可以提高程序的效率, 但是,当多个线程去访问同一个资源时,也会引发一些安全问题。我们必须注意这样一个问题。当两个或多个线程同时访问同一个变量, 并且一些线程需要修改这个变量,程序应对这样的问题作出处理, 否则可能发生混乱。
总结多线程的安全问题发生的原因

  1. 首先必须有多线程。
  2. 多个线程在操作共享的数据,并且对共享数据有修改。
  3. 本质原因是CPU在处理多个线程的时候,在操作共享数据的多条代码之间进行切换导致的。

多线程安全问题解决

我们了解到线程的安全问题其实就是由多个线程同时处理共享资源所导致的,要想解决线程的安全问题,必须保证下面用于处理共享资源的代码在任何时刻只能被一个线程访问。

同步代码块

为了实现这种限制, Java中提供了同步机制, 当多个线程使用同一个共享资源时, 可以将处理共享资源的代码放置在一个代码块中, 使用Synchronized关键字来修饰。 被称作同步代码块。

1.synchronized(锁对象) {
2.    // 操作共享资源代码块
3.}
1.package com.java.multithread;
2.
3.public class Demo {
4.  public static void main(String[] args) {
5.      // 1. 创建一个 `任务类` 的对象
6.      TicketWindow tw = new TicketWindow();
7.      
8.      // 2. 创建线程, 并将任务类对象作为参数传入
9.      Thread t1 = new Thread(tw, "窗口一");
10.     Thread t2 = new Thread(tw, "窗口二");
11.     Thread t3 = new Thread(tw, "窗口三");
12.     
13.     // 3. 启动线程
14.     t1.start();
15.     t2.start();
16.     t3.start();
17. }
18.}
19.
20.class TicketWindow implements Runnable {
21. // 属性
22. private int tickets = 100;
23. // 定义任意的一个 `锁对象`, 用于同步代码块
24. Object lock = new Object();
25. 
26. //行为
27. @Override
28. public void run() {
29.     // 实现循环模拟不断售票的过程
30.     while (true) {
31.         synchronized (lock) {
32.             // 睡 1 毫秒
33.             try {
34.                 Thread.sleep(1);
35.             } catch (InterruptedException e) {
36.                 e.printStackTrace();
37.             }
38.             // 判断
39.             if (tickets > 0) {
40.                 System.out.println(Thread.currentThread().getName() + "正在发售第 " + tickets-- +"张票.");
41.             } else {
42.                 break;  // 完成后结束任务代码
43.             }
44.         }
45.     }
46. }
47.}

同步方法

同步方法 : 在方法前面同样可以使Synchronized关键字来修饰,被修饰的方法称为同步方法,它能实现和同步代码块同样的功能。
修饰符 synchronized 返回值类型方法名 (参数列表){}

1.package cn.java.demo;
2.
3.// 同步方法
4.
5.public class Demo {
6.  public static void main(String[] args) {
7.      // 创建一个 `任务类` 对象
8.      TicketWindow task = new TicketWindow();
9.      
10.     // 创建3个线程对象, 将任务类对象作为参数传入
11.     Thread t1 = new Thread(task, "窗口一");
12.     Thread t2 = new Thread(task, "窗口二");
13.     Thread t3 = new Thread(task, "窗口三");
14.     
15.     // 启动线程
16.     t1.start();
17.     t2.start();
18.     t3.start();
19. }
20.}
21.
22.// 售票案例 : 使用接口方式实现
23.// 定义一个 `售票窗口` 类  (任务类)
24.class TicketWindow implements Runnable {
25. // 属性
26. private int tickets = 100;
27. 
28. // 行为
29. @Override
30. public void run() {
31.     // 使用循环模拟不断售票过程
32.     while (true) {
33.         // 调用售票的方法
34.         this.sellTicket();
35.         
36.         if (tickets <= 0) {
37.             break;
38.         }
39.     }
40. }
41. 
42. // 如果一个方法中所有的代码都需要同步, 就可以直接使用 `同步方法` 实现
43. // 问题一 : 同步方法有没有锁? 有 
44. // 问题二 : 同步方法的锁是谁? this 当前调用这个方法的对象.
45. public synchronized void sellTicket() {
46.     // 判断是否还有余票
47.     if (tickets > 0) {
48.         try {
49.             Thread.sleep(10);
50.         } catch (InterruptedException e) {
51.             e.printStackTrace();
52.         }
53.         System.out.println(Thread.currentThread().getName() + "正在出售第 " + tickets-- + " 张票.");
54.     } 
55. }
56.}

思考 : 大家可能会有这样的疑问 : 同步代码块的锁是自己定义的任意类型的对象, 那么同步方法是否也存在锁? 如果有, 它的锁是什么呢?
答案是肯定的, 同步方法也有锁, 它的锁就是当前调用该方法的对象, 也就是this指向的对象.
这样做的好处是, 同步方法被所有线程所共享, 方法所在的对象相对于所有线程来说是唯一的, 从而保证了锁的唯一性. 当一个线程执行该方法时, 其它的线程就不能进入到该方法中, 直到这个线程执行完该方法为止, 从而达到了线程同步的效果.

Lock

JDK5提供的Lock接口比JDK5之前的同步更好使用。Lock接口代替JDK5之前的同步代码块. 更加灵活.
注意 : Lock接口的实现类对象不能和 Object类中的 wait(), notify(), notifyAll(), 共同使用, 因为Object类中的 wait(), notify(), notifyAll(), 方法必须要和 同步锁 synchronized 共同使用, 否则报异常!

使用Lock演示卖票案例Demo

1.package com.java.demo;
2.
3.import java.util.concurrent.locks.Lock;
4.import java.util.concurrent.locks.ReentrantLock;
5.
6.// 演示 : Lock 接口 & ReentrantLock实现类
7.
8.public class Demo {
9.  public static void main(String[] args) {
10.     // 创建一个 `任务类` 对象
11.     TicketWindow task = new TicketWindow();
12.     
13.     // 创建3个线程对象, 将任务类对象作为参数传入
14.     Thread t1 = new Thread(task, "窗口一");
15.     Thread t2 = new Thread(task, "窗口二");
16.     Thread t3 = new Thread(task, "窗口三");
17.     
18.     // 启动线程
19.     t1.start();
20.     t2.start();
21.     t3.start();
22. }
23.}
24.
25.// 售票案例 : 使用接口方式实现
26.// 定义一个 `售票窗口` 类  (任务类)
27.class TicketWindow implements Runnable {
28. // 属性
29. private int tickets = 100;
30. // 定义一个锁属性
31. Lock lock = new ReentrantLock();
32. 
33. // 行为
34. @Override
35. public void run() {
36.     // 使用循环模拟不断售票过程
37.     while (true) {
38.         // 调用售票的方法
39.         this.sellTicket();
40.         
41.         if (tickets <= 0) {
42.             break;
43.         }
44.     }
45. }
46. 
47. public void sellTicket() {
48.     // 获取锁
49.     lock.lock();
50.     // 判断是否还有余票
51.     if (tickets > 0) {
52.         try {
53.             Thread.sleep(10);
54.         } catch (InterruptedException e) {
55.             e.printStackTrace();
56.         }
57.         System.out.println(Thread.currentThread().getName() + "正在出售第 " + tickets-- + " 张票.");
58.     } 
59.     // 释放锁
60.     lock.unlock();
61. }
62.}

线程的运行状态图(生命周期)

线程的运行状态图.jpg

面试题

1./*
2. * 1. 多线程有几种实现方案, 分别是哪几种 ?
3. * 
4. * 第一种 : 继承 Thread 类
5. *        a. 自定义类, 继承Thread
6. *        b. 重写 run() 方法
7. *        c. 创建线程类对象
8. *        d. 启动线程
9. * 
10. * 第二种 : 实现 Runnable 接口
11. *       a. 自定义类, 实现Runnable 接口
12. *       b. 实现 run() 方法
13. *       c. 创建线程任务类对象
14. *       d. 创建线程对象, 将任务类对象作为参数传入.
15. *       e. 启动线程
16. */
17.
18./*
19. * 2. 同步有几种方式, 分别是什么 ?
20. * 
21. * 第一种 : 同步代码块. 锁是任意对象, 但是必须保证唯一性.
22. * 
23. * 第二种 : 同步方法. 锁是 this 当前对象.
24. * 
25. * 其实 : JDK5.0 之后提供了新的一个同步机制 : 
26. *       Lock接口
27. *       获取锁 : lock.lock();
28. *       释放锁 : lock.unlock();
29. */
30.
31./*
32. * 3. run() 和 start() 的区别 ?
33. * 
34. * run : 仅仅是封装线程任务执行代码, 不调用系统资源开辟新线程.
35. * start : 先调用系统资源,开辟新线程, 在新线程中执行 run()方法的任务代码.
36. */
37.
38./*
39. * 4. 线程的生命周期.
40. * 
41. *               Block阻塞状态
42. * 
43. * New新建状态       Runnable就绪状态    Running运行状态 Terminated死亡状态
44. */
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容

  • 1. 多线程概述 1.1 多线程引入 由上图中程序的调用流程可知,这个程序只有一个执行流程,所以这样的程序就是单线...
    JackChen1024阅读 403评论 0 1
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,812评论 3 53
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,792评论 0 10
  • 一阿姨牵着孙子,招孩子,径直蹒跚走来,躺式换坐式,孩子刹住小蹄子,双目发直,阿姨,帮腔,叫叔叔,叔叔酷不酷?不好意...
    纵情嬉戏天地间阅读 258评论 0 0
  • 今天的文章对很多国内开发者没有什么价值,但对于像我一样做海外项目的还是需要知道的。 糟糕的app使用体验相信很多人...
    轻云时解被占用了阅读 2,897评论 0 3