多线程

1. 多线程

1.1. 本章目标

1. 程序,进程,线程概念

2. java实现多线程(2种)

3. 静态代理设计模式

4. 线程的状态和方法

5. 线程的基本信息和优先级

1.2. 什么是程序?

程序:Program ,是一个静态的概念

只要是计算机中安装的,在控制面板中能够看到的,都是程序

1.3. 什么是进程?

当你双击的Microsoft Word的图标,你就开始运行的Word的进程。在Windows系统中,一个运行的exe就是一个进程

计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。

假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。

进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

进程的概括:Process,是一个动态的概念。

u 进程是程序的一次动态执行过程,占用特定的地址空间。

u 每个进程都是独立的,由3部分组成cpu,data,code

缺点:内存的浪费,cpu的负担(程序开太多,会卡)

1.4. 什么是线程?

一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。

由于一个进程可以由多个线程,线程可以被认为是“轻量级”的进程。因此,一个线程和一个进程之间的本质区别在于,每一个用来完成的工作。线程用于小任务,而进程用于更多的“重量级”的任务。

线程:Thread,是进程中一个“单一的连续控制流程”(a single sequential flow of control)/执行路径

线程又被称为轻量级进程(lightweight process)

Threads run at the same time,independently of one another

一个进程可拥有多个并行的(concurrent)线程

一个进程中的线程共享相同的内存单元/内存地址空间---->可以访问相同的变量和对象,而且它们从同一堆中分配对象---->通信、数据交换、同步操作

由于线程间的通信是在同一块地址空间上进行的,所以不需要额外的通信进制,这就使得通信更简便而且信息传递的速度更快。

1.5. 什么是多线程?

1。单进程单线程:一个人在一个桌子上吃菜。

2。单进程多线程:多个人在同一个桌子上一起吃菜。

3。多进程单线程:多个人每个人在自己的桌子上吃菜。

多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢。对于 Windows 系统来说,“开桌子”的开销很大,因此 Windows 鼓励大家在一个桌子上吃菜。因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。

一个进程中的所有线程可以同时执行,也可以按照要求顺序执行、部分执行、互相等待或完全停止。

1.6. 进程和线程的区别?

l 根本区别。进程是资源分配单位,线程是调度和执行单位。进程能做的所有事情,都是进程中的线程在做,进程是对这些线程总和的称呼。如果一个进程中一个线程都没有,表示这个进程什么都做不了。

l 内存空间。每个进程拥有自己的地址空间,进程中的所有线程共享相同的内存单元,可以访问相同的变量和对象。

l 通信。由于线程间的通信是在同一地址空间上进行,所以不需要额外的通信机制,这就使得通信更简单而且消息传递的速度也更快。进程间由于地址空间不共享,通信效率更低。

l 运行调度。进程中的一个线程崩溃会导致整个进程崩溃,但一个进程崩溃不会导致所有进程崩溃。

软件开发管理。进程可以交给不同的团队开发, 但是多线程必须是同一个团队开发,因为调试时要启动进程,所有线程都会被启动,调试要同步。

 

面试题:什么是进程?什么是线程? 进程和线程有什么区别?

关键点: 每个进程独立的内存空间,互不影响 ,线程:线程是进程的一部分,是进程中的多条执行路径, 区别:表中红色字体

总结:进程是系统运行程序的基本单位,每一个进程都有自己独立的一块内存空间,一组系统资源。每一个进程的内部数据和状态都是完全独立的。

线程:是进程中执行运算的最小单位,是进程内部的一个执行单元。可看成轻量级进程,同一类线程共享代码和数据空间。

区别:一个进程中至少要有一个线程。资源分配给进程,同一进程的所有线程共享该进程的所有资源。处理机分配给线程,即真正在处理机上运行的是线程。

1.7. 多线程实现的两种方法

java中实现多线程(一)

java中负责线程的这个功能的是Java.lang.Thread这个类

可以通过创建Thread的实例来创建新的线程

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。

通过调用Thread类的start()方法来启动一个线程。

举例:龟兔赛跑

package com.njwb.threadmethod;

/**

 * 1.类继承java.lang.Thread +重写run方法

 * 2.创建该类的实例 +start()方法开启多线程

 * 程序中 main 主线程   后台gc 回收堆中不用的变量的内存 自动调用  ,异常 打印堆栈

 * @author Administrator

 *

 */

public class Rabbit extends Thread {

@Override

public void run() {

for(int i=0;i<1000;i++){

System.out.println("兔子跑了"+i+"");

/*try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}*/

}

}

}

class Tortise extends Thread{

@Override

public void run() {

for(int i=0;i<1000;i++){

System.out.println("乌龟跑了"+i+"");

/*try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}*/

}

}

}

package com.threadmethod;

 

public class RabbitApp {

public static void main(String[] args) throws InterruptedException {

Rabbit ra = new Rabbit();

//开启多线程不能调用run() ,如果直接写ra.run()表示调用普通方法run

//ra.run();

//start() 线程处于就绪状态,该线程会被加入线程组中,等待cpu的调度,如果cpu调度到,就可以执行,会自动调用run方法

ra.start();

Tortise tor = new Tortise();

//tor.run();

tor.start();

for(int i=0;i<1000;i++){

System.out.println("main......"+i);

//休眠 暂停线程

//Thread.sleep(200);

}

}

}

练习:在线程中输出1-100的整数。

package com.threadmethod;

public class NumDemo extends Thread{

@Override

public void run() {

for(int i=1;i<=100;i++){

System.out.println(i);

}

}

}

package com.threadmethod;

public class NumDemoApp {

public static void main(String[] args) {

//实例化线程类对象

NumDemo nd = new NumDemo();

nd.start();

}

}

JAVA中实现多线程(二)

继承Thread类方式的缺点:那就是如果我们的类已经从一个类继承(如小程序必须继承自Applet类),则无法再继承Thread类

通过Runnable接口实现多线程

优点:可以同时实现继承(实现接口的方式,该线程类以后可以去继承其他的类,将父类预留出来)。实现Runnable接口方式要通用一些。

1) 避免单继承

2) 方便共享资源,同一份资源,多个代理访问

举例:使用Runnable接口方式

package com.njwb.staticproxy;

/**

 * 1.类 实现Runnable接口 +重写run方法

 * 2.调用的时候,实例化该类,将该类的对象作为参数传入代理Thread的构造方法中,通过代理start()开启多线程

 * @author Administrator

 *

 */

public class ThreadDemo implements Runnable{

@Override

public void run() {

for(int i=0;i<1000;i++){

System.out.println("ThreadDemo....."+i);

}

}

}

package com.staticproxy;

public class ThreadDemoApp {

public static void main(String[] args) {

//创建真实角色类

ThreadDemo td = new ThreadDemo();

//不能td.start(),因为ThreadDemo类没有start()方法,

//td.start();

//创建代理角色,代理持有对真实角色的引用

Thread th = new Thread(td);

//通过代理启动

th.start();

}

}

举例:向线程类传递数据,带参构造

package com.njwb.threadrunnable;

public class SomethingDemo implements Runnable {

private String doSomething;

public SomethingDemo(String doSomething) {

super();

this.doSomething = doSomething;

}

@Override

public void run() {

while(true){

System.out.println("一边"+doSomething);

}

}

}

package com.threadrunnable;

public class TestSomethingDemo {

public static void main(String[] args) {

//创建真实角色

SomethingDemo sd = new SomethingDemo("QQ");

//创建代理角色,代理持有对真实角色的引用

Thread th = new Thread(sd);

//通过代理启动

th.start();

//创建真实角色

SomethingDemo sd2 = new SomethingDemo("打游戏");

//创建代理角色,代理持有对真实角色的引用

Thread th2 = new Thread(sd2);

//通过代理启动

th2.start();

while(true){

System.out.println("一边唱歌...");

}

}

}

两种方法的不同

l 使用直接继承Thread,虽然简单,但是由于java是单继承,有局限。

l 使用Runnable接口,虽然程序复杂了,但是接口能多继承(推荐使用)

总结

当线程目标run()方法结束时该线程完成。

线程的调度是JVM的一部分,实际上一次只能运行一个线程,众多可运行线程中的某一个会被选中做为当前线程,谁先谁后顺序没有保障。

1.8. 静态代理

package com.njwb.staticproxy;

/**

 * 1.真实角色类,代理角色类要实现相同的接口

 * 2.代理角色持有对真实角色的引用 (真实角色在代理类,作为属性存在,后期调用的时候通过带参构造传入)

 * 3.调用方法时,通过代理调用

 * @author Administrator

 *

 */

//公有的接口

interface Marry{

void toMarry();

}

 

//真实角色类

class You implements Marry{

@Override

public void toMarry() {

System.out.println("你和小红结婚了");

}

}

 

//代理角色类

class WeddingCompany implements Marry{

private Marry you;

public WeddingCompany(Marry you) {

super();

this.you = you;

}

 

public void before(){

System.out.println("布置场地....");

}

@Override

public void toMarry() {

before();

this.you.toMarry();

after();

}

public void after(){

System.out.println("收拾残局....");

}

}

public class StaticProxyDemo {

public static void main(String[] args) {

//创建真实角色

Marry you = new You();

//创建代理角色+持有对真实角色的引用

WeddingCompany com = new WeddingCompany(you);

//通过代理调用方法

com.toMarry();

}

}

1.9. 线程状态

新生状态:使用new关键字Thread类或其子类建立一个线程对象后,该线程处于新生状态,有自己的内存空间,当调用start方法,进入就绪状态

l 就绪状态:处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,等待系统为其分配CPU.等待状态并不是执行 状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“CPU调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。 

运行状态: 在运行状态中线程将执行自己run方法,直到调用其他方法而终止、或者等待某些资源而阻塞、或完成任务而死亡,如果在给定的时间片内未执行完,就会被系统换下来进入就绪状态,等待CPU重新分配后方可进入运行状态

等待/阻塞/睡眠状态:处在运行状态的线程在某种情况下,如执行sleep()(睡眠方法),或等待I/O设备资源(input.next()),将让出CPU并暂停停止自己的运行,进入阻塞状态,在阻塞状态的线程不能立即进入就绪队列,只有当引起阻塞的原因消除时,如睡眠时间到了或者I/O设备空闲了,线程就转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行(当获取到CPU时又可以进入运行状态)

l 死亡状态:死亡状态是线程生命周期中的最后一个阶段。死亡状态有两个,一是线程正常执行结束,二是线程被强制终止,如通过执行stop或destroy方法来终止一个线程(不推荐使用这2个方法,前者会产生异常,后者是强制终止,不会释放锁。)使用flag控制

 

Rabbit ra = new Rabbit(); //新生状态

ra.start(); //就绪状态

比方说龟兔赛跑,各跑1000步,1000步跑完,线程正常的终止(执行结束) --à进入死亡状态

比方说:3秒后 ,人为终止线程 ,线程也被终止掉,--à进入死亡状态

Main方法中,如果i等于50的时候,main线程会进入阻塞状态,等待输入,不输入的话,始终处在阻塞状态,如果遇到sleep(),在休眠的时间内,也是出于阻塞状态。

for(int i=0;i<1000;i++){

System.out.println("main......"+i);

//阻塞事件  等待输入

/*if(i==50){

int num = new Scanner(System.in).nextInt();

}*/

//阻塞事件,线程的休眠

//Thread.sleep(200);

}

1.10. 线程名称

运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字,一个是你定的名字。在没有指定线程名字的情况下,虚拟机总会为线程指定名字,并且主线程的名字总是main,非主线程的名字不确定。

举例:设置线程名称,获取名称 ,判断线程的状态(是否alive)

Thread.currentThread 获取的当前的线程对象

getName() 获取的线程名称

setName()  设置线程名称

isAlive() 判断线程的状态  如果线程死亡的话  isAlive() false  如果就绪状态或者运行状态或者阻塞状态的话 ,isAlive() true

boolean

isAlive() 
          测试线程是否处于活动状态。

举例:线程类见1.14终止线程  

测试类1

package com.njwb.threadrunnable;

/**

 * 主线程:main  

 * @author Administrator

 *

 */

public class TestThreadDemo {

public static void main(String[] args) throws InterruptedException {

ThreadDemo t = new  ThreadDemo();

Thread th = new Thread(t);

th.setName("线程A");

System.out.println("当前线程的名字主线程:"+Thread.currentThread().getName());

System.out.println("已开启的线程A的名字:"+th.getName());

th.start();

System.out.println("启动后线程A的状态:"+th.isAlive());

Thread.sleep(10);

//10ms后终止线程

t.stop();

//延时

Thread.sleep(3);

System.out.println("终止后线程A的状态:"+th.isAlive());

}

}

测试类2

package com.njwb.threadrunnable;

/**

 * 主线程:main  

 * 如果线程没有指定名称,将有默认的名称,自动编号 Thread-0,Thread-1,Thread-2....

 * @author Administrator

 *

 */

public class TestThreadDemo02 {

public static void main(String[] args) throws InterruptedException {

ThreadDemo t = new  ThreadDemo();

Thread th = new Thread(t);

ThreadDemo t2 = new  ThreadDemo();

Thread th2 = new Thread(t2);

th.start();

th2.start();

Thread.sleep(2000);

//10ms后终止线程

t.stop();

t2.stop();

}

}

测试类3

package com.njwb.threadrunnable;

/**

 * Thread(Runnable target, String name)  

 * 1个参数是线程类(实现了Runnable接口的实现类),2个参数可以指定线程的名字

    分配新的 Thread 对象。

 * @author Administrator

 *

 */

public class TestThreadDemo03 {

public static void main(String[] args) throws InterruptedException {

ThreadDemo t = new  ThreadDemo();

Thread th = new Thread(t,"线程A");

ThreadDemo t2 = new  ThreadDemo();

Thread th2 = new Thread(t2,"线程B");

th.start();

th2.start();

Thread.sleep(2000);

//10ms后终止线程

t.stop();

t2.stop();

//1个代理,对应1个线程对象 ,不要1个代理对应多个线程对象

/*ThreadDemo t = new  ThreadDemo();

Thread th = new Thread(t,"线程A");

ThreadDemo t2 = new  ThreadDemo();

th = new Thread(t2,"线程B");

th.start();

th.start();

Thread.sleep(2000);

//10ms后终止线程

t.stop();

t2.stop();

t2.stop();*/

}

}

1.11. 资源争抢

web12306订票,

package com.njwb.threadrunnable;

public class Web12306 implements Runnable {

private int ticket = 50;

@Override

public void run() {

while(true){

if(ticket<=0){

break;

}

System.out.println(Thread.currentThread().getName()+"抢到了"+ticket--);

}

}

}

package com.njwb.threadrunnable;

public class Web12306App {

public static void main(String[] args) {

//同一份资源 50张票

Web12306 we = new Web12306();

//创建代理角色

Thread th1 = new Thread(we,"黄牛甲");

Thread th2 = new Thread(we,"黄牛已");

Thread th3 = new Thread(we,"黄牛丙");

//人为的控制

th1.setPriority(10);

th2.setPriority(1);

th3.setPriority(1);

//同时启动

th1.start();

th2.start();

th3.start();

}

}

1.12. 线程睡眠

什么是线程睡眠?

Thread.sleep(long millis)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态(就绪状态)。

为什么要睡眠?

线程执行太快线程睡眠是帮助其他线程获得运行机会的最好方法。一个线程睡眠了,其他线程就有机会了。线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。

注意:

线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

sleep()是静态方法,只能控制当前正在运行的线程。

举例:(1)1秒1个,倒数10个数

10

9

8…..

(2)一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串

1

2

3

10

*********************

11

……

20

*********

(3)倒计时

时:分:秒倒计时  ,假设当前时间是  15:38:27  ,10秒后    15:38:37  ,从10秒后往当前时间去显示

15:38:37

15:38:36

15:38:35

…..

15:38:27

分析: DateFormat df =  new SimpleDateFormat(“HH:mm:ss”)  String str = df.format(endTime );  ,

10秒后时间对象 Date endTime = new Date(System.currenteMillin()+10*1000);

/**

 * 方式1: 次数

 */

public static void calTime(){

//获取10秒后的时间

Date endTime = new Date(System.currentTimeMillis()+10000);

//显示的格式 HH:mm:ss

DateFormat df = new SimpleDateFormat("HH:mm:ss");

for(int i=1;i<=11;i++){

System.out.println(df.format(endTime));

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//构建下一秒的时间

endTime = new Date(endTime.getTime()-1000);

}

}

/**

 * 方式2break控制循环

 */

public static void calTime2(){

//获取10秒后的时间

Date endTime = new Date(System.currentTimeMillis()+10000);

//获取10秒的时间,对应的毫秒数

long end = endTime.getTime();

//显示的格式 HH:mm:ss

DateFormat df = new SimpleDateFormat("HH:mm:ss");

while(true){

System.out.println(df.format(endTime));

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//构建下一秒的时间

endTime = new Date(endTime.getTime()-1000);

//end-10000表示的当前时间 ,endTime不断的构建下一秒的时间,如果不控制,就会一直往下减,

if(end-10000>endTime.getTime()){

break;

}

}

}

1.13. 线程优先级

优先级的定义有部分在不同系统间有差别。1-10,10表示优先级最高,默认值是5

举例: 该例题的线程类见 终止线程中的例题

package com.njwb.threadrunnable;

/**优先级:1-10

 * MAX_PRIORITY  10

 * MIN_PRIORITY   1

 * 如果没有设置优先级,线程的默认级别是5 NORM_PRIORITY

 * @author Administrator

 *

 */

public class TestThreadDemo04 {

public static void main(String[] args) throws InterruptedException {

ThreadDemo t = new  ThreadDemo();

Thread th = new Thread(t,"线程A");

ThreadDemo t2 = new  ThreadDemo();

Thread th2 = new Thread(t2,"线程B");

th.setPriority(Thread.MAX_PRIORITY);

//th2.setPriority(7);

//等价于 th2.setPrority(1);

//th2.setPriority(Thread.MIN_PRIORITY);

//该句相当于设置的是5 ,和不设置是一样的,因为默认值就是5

th2.setPriority(Thread.NORM_PRIORITY);

th.start();

th2.start();

Thread.sleep(200);

//10ms后终止线程

t.stop();

t2.stop();

}

}

注意:不要假定高优先级的线程一定先于低优先级的线程执行,不要有逻辑依赖于线程优先级,否则可能产生意外结果

1.14. 终止线程

l 线程正常执行完毕

l 外部干涉

(1) 线程类中定义一个标志位

(2) 线程体中使用该标志位

(3) 提供一个改变标志位的方法

(4) 外部根据条件调用改变标志的方法

举例:

public class ThreadDemo implements Runnable {

int num = 0;

private boolean flag = true;

@Override

public void run() {

while(flag){

System.out.println(Thread.currentThread().getName()+"...--->跑了"+(num++)+"");

}

}

public void stop(){

flag=!flag;

}

}

1.15. Yield(暂停线程)

package com.njwb.sleep;

public class YieldDemo implements Runnable{

@Override

public void run() {

for(int i=0;i<1000;i++){

System.out.println("YieldDemo...."+i);

/*if(i%20==0){

Thread.yield(); //暂停本线程th

}*/

try {

Thread.sleep(20);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

package com.njwb.sleep;

/**

 * static void yield()  静态方法 ,类名  

          暂停当前正在执行的线程对象,并执行其他线程。   

    yield()方法出现在哪个线程体内,就是暂停的谁

 * @author Administrator

 *

 */

public class YieldDemoApp {

public static void main(String[] args) throws InterruptedException {

//创建真实角色

YieldDemo yd = new YieldDemo();

//创建代理角色,代理持有对真实角色的引用

Thread th = new Thread(yd);

//代理启动

th.start();

for(int i=0;i<1000;i++){

if(i%20==0){

Thread.yield(); //暂停main

}

System.out.println("main....."+i);

Thread.sleep(100);

}

}

}

1.16. Join(合并线程)

package com.njwb.sleep;

public class JoinDemo  implements Runnable{

public void run() {

for(int i=0;i<1000;i++){

System.out.println("JoinDemo...."+i);

}

}

}

package com.njwb.sleep;

/**

 * void join()  写在哪个线程体内,就阻塞的哪个线程

          等待该线程终止。

 * @author Administrator

 *

 */

public class JoinDemoApp {

public static void main(String[] args) {

JoinDemo jd = new JoinDemo();

Thread th = new Thread(jd,"线程Join");

th.start();

for(int i=0;i<1000;i++){

if(i==70){

try {

th.join(); //合并的main线程,阻塞的main

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("main....."+i);

}

}

}

1.17. 作业

1)创建一个集合aList

写一个线程,每隔1秒向集合中添加一个随机数字

写一个线程,每隔1秒检查一下,当数字超过10个时,遍历输出集合中所有元素并清空集合。

思路: 订票 ,2个线程共享同一个ArrayList对象

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值