java多线程 程序_JAVA多线程程序开发基础知识

JAVA多线程基础

概念-程序、进程与多任务

程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。

进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。

多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。

线程

线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。

简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序。

一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。

操作系统给每个线程分配不同的CPU时间片,在某一时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行。

0818b9ca8b590ca3270a3433284dd417.png

创建多线程

每个Java程序启动后,虚拟机将自动创建一个主线程

可以通过以下两种方式自定义线程类:

创建 java.lang.Thread 类的子类,重写该类的 run方 法

创建 java.lang.Runnable接 口的实现类,实现接口中的 run 方法

继承 Thread 类

Thread:代表一个线程类

0818b9ca8b590ca3270a3433284dd417.png

Thread 类

Thread类中的重要方法:

run方法:包括线程运行时执行的代码,通常在子类中重写它。

start方法:启动一个新的线程,然后虚拟机调用新线程的run方法

Thread 类代码示例:

0818b9ca8b590ca3270a3433284dd417.png

线程执行流程

0818b9ca8b590ca3270a3433284dd417.png

创建多线程

0818b9ca8b590ca3270a3433284dd417.png

问题:要定义的线程类已经显式继承了一个其他的类怎么办?

答:实现Runnable接口

Runnable 接口

Runnable 接口中只有一个未实现的 run 方法,实现该接口的类必须重写该方法。

Runnable 接口与 Thread 类之间的区别

Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写

Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类

Runnable 接口适合于资源的共享

0818b9ca8b590ca3270a3433284dd417.png

线程的生命周期

线程的生命周期:

- 指线程从创建到启动,直至运行结束

- 可以通过调用 Thread 类的相关方法影响线程的运行状态

0818b9ca8b590ca3270a3433284dd417.png

线程的运行状态

1、 新建(New)

当创建了一个Thread对象时,该对象就处于“新建状态”

没有启动,因此无法运行

2、 可执行(Runnable)

其他线程调用了处于新建状态线程的start方法,该线程对象将转换到“可执行状态”

线程拥有获得CPU控制权的机会,处在等待调度阶段。

3、 运行(Running)

处在“可执行状态”的线程对象一旦获得了 CPU 控制权,就会转换到“执行状态”

在“执行状态”下,线程状态占用 CPU 时间片段,执行run 方法中的代码

处在“执行状态”下的线程可以调用 yield 方法,该方法用于主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。

0818b9ca8b590ca3270a3433284dd417.png

4、 阻塞(Blocking)

线程在“执行状态”下由于受某种条件的影响会被迫出让CPU控制权,进入“阻塞状态”。

进入阻塞状态的三种情况

- 调用sleep方法

1、 public void sleep(long millis)

2、 Thread类的sleep方法用于让当前线程暂时休眠一段时间

参数 millis 的单位是毫秒

0818b9ca8b590ca3270a3433284dd417.png

- 调用join方法

处在“执行状态”的线程如果调用了其他线程的 join 方法,将被挂起进入“阻塞状态”

目标线程执行完毕后才会解除阻塞,回到 “可执行状态”

0818b9ca8b590ca3270a3433284dd417.png

执行I/O操作

线程在执行过程中如果因为访问外部资源(等待用户键盘输入、访问网络)时发生了阻塞,也会导致当前线程进入“阻塞状态”。

4.1、 解除阻塞

睡眠状态超时

调用 join 后等待其他线程执行完毕

I/O 操作执行完毕

调用阻塞线程的 interrupt 方法(线程睡眠时,调用该线程的interrupt方法会抛出InterruptedException)

0818b9ca8b590ca3270a3433284dd417.png

5、死亡(Dead)

死亡状态(Dead):处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。

已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException

可以使用 Thread 类的 isAlive 方法判断线程是否活着

0818b9ca8b590ca3270a3433284dd417.png

线程调度

线程调度

按照特定机制为线程分配 CPU 时间片段的行为

Java程序运行时,由 Java 虚拟机负责线程的调度

线程调度的实现方式

分时调度模型:让所有线程轮流获得CPU的控制权,并且为每个线程平均分配CPU时间片段

抢占式调度模型:选择优先级相对较高的线程执行,如果所有线程的优先级相同,则随机选择一个线程执行 。Java虚拟机采用此种调度模型。

线程的优先级

Thread类提供了获取和设置线程优先级的方法

getPriority:获取当前线程的优先级

setPriority:设置当前线程的优先级

Java语言为线程类设置了10个优先级,分别使用1~10内的整数表示 ,整数值越大代表优先级越高。每个线程都有一个默认的优先级,主线程的默认优先级是5。

Thread类定义的三个常量分别代表了几个常用的优先级:

MAX_PRIORITY::代表了最高优先级10

MIN_PRIORITY::代表了最低优先级1

NORM_PRIORITY::代表了正常优先级5

setPriority 不一定起作用,在不同的操作系统、不同的 JVM 上,效果也可能不同。操作系统也不能保证设置了优先级的线程就一定会先运行或得到更多的CPU时间。

在实际使用中,不建议使用该方法

0818b9ca8b590ca3270a3433284dd417.png

线程同步

问题:通过多线程解决售票问题。

非线程安全示例:

TicketWindow2.java

复制内容到剪贴板

/**

* 售票窗口

*/

public class TicketWindow2 implements Runnable {

//票数

int ticketNum = 10;

private  boolean isNext(){

//是否售完标识

boolean f = false;

if (ticketNum > 0) {

//每次少一张票

ticketNum--;

f = true;

}

try {

/**

* Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9

* 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出

* 窗口1 剩余 8 张票...

* 窗口2 剩余 8 张票...

*/

Thread.currentThread().sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

if (ticketNum == 0)

f = false;

if (!f)

System.out.println(Thread.currentThread().getName() + " 票已售完 ");

else

System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");

return f;

}

@Override

public void run() {

for (;ticketNum > 0 ;){

//执行售票

isNext();

}

}

}

测试方法

复制内容到剪贴板

public static void main(String[] a ){

TicketWindow2 tw2 = new TicketWindow2();

Thread t1 = new Thread(tw2,"窗口1");

Thread t2 = new Thread(tw2,"窗口2");

t2.start();

t1.start();

}

结果:

窗口1 剩余 8 张票…

窗口2 剩余 8 张票…

窗口1 剩余 6 张票…

窗口2 剩余 6 张票…

窗口1 剩余 4 张票…

窗口2 剩余 3 张票…

窗口1 剩余 2 张票…

窗口2 剩余 1 张票…

窗口1 票已售完

窗口2 票已售完

结果已反映了一切问题。多窗口售票是不安全的有问题的。那如何解决这些问题???

线程安全

多线程应用程序同时访问共享对象时,由于线程间相互抢占CPU的控制权,造成一个线程夹在另一个线程的执行过程中运行,所以可能导致错误的执行结果。

0818b9ca8b590ca3270a3433284dd417.png

Synchronized 关键字

为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。

1、 synchronized关键字

确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念

2、 使用 synchronized 关键字

修饰方法:被“synchronized”关键字修饰的方法称为”同步方法”

当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问

复制内容到剪贴板

//定义同步方法

public synchronized void methd(){

//方法实现

}

0818b9ca8b590ca3270a3433284dd417.png

3、 使用 ”synchronized” 关键字:修饰部分代码,如果只希望同步部分代码行,可以使用“同步块”

复制内容到剪贴板

//同步块

synchronized(obj){

//被同步的代码块

}

同步块的作用与同步方法一样,只是控制范围有所区别

修改示例

重新修改售票的例子,将isNext方法改为同步方法

复制内容到剪贴板

//票数

int ticketNum = 100;

/**

* 同步方法

*/

private synchronized boolean isNext(){

/**

* 同步代码块

*/

synchronized (this) {

boolean f = false;

if (ticketNum > 0) {

ticketNum--;

f = true;

}

try {

/**

* Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9

* 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出

* 窗口1 剩余 8 张票...

* 窗口2 剩余 8 张票...

*/

Thread.currentThread().sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

if (ticketNum == 0)

f = false;

if (!f)

System.out.println(Thread.currentThread().getName() + " 票已售完 ");

else

System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");

return f;

}

}

测试:

为了实现多线程的结果 我模拟了4个售票窗口,100张票

测试方法

复制内容到剪贴板

public static void main(String[] a ){

/**

* 这里可以看出 TicketWindow2 为线程共享参照对象,

* 也就是说,如果需要同步的话,那synchronize(参照对象)中的参照对象即为:TicketWindow2

*/

TicketWindow2 tw2 = new TicketWindow2();

Thread t1 = new Thread(tw2,"窗口1");

Thread t2 = new Thread(tw2,"窗口2");

Thread t3 = new Thread(tw2,"窗口3");

Thread t4 = new Thread(tw2,"窗口4");

t2.start();

t1.start();

t4.start();

t3.start();

}

结果:

窗口2 剩余 99 张票…

窗口2 剩余 98 张票…

窗口2 剩余 84 张票…

窗口3 剩余 83 张票…

窗口3 剩余 82 张票…

窗口3 剩余 20 张票…

窗口4 剩余 19 张票…

窗口4 剩余 1 张票…

窗口4 票已售完

窗口1 票已售完

窗口3 票已售完

窗口2 票已售完

线程通信

wait()方法:

中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。

notify()方法:

唤醒由于使用这个同步方法而处于等待线程的 某一个结束等待

notifyall()方法:

唤醒所有由于使用这个同步方法而处于等待的线程结束等待

练习

通过交叉打印,练习线程通信方法

PrintWords.java

复制内容到剪贴板

public class PrintWords implements Runnable{

private boolean a=true,b=false;

private char abc = 'a';

private synchronized void doPrint(){

if (abc <= 'z') {

String name = Thread.currentThread().getName();

System.out.println("Thread- " + name + "   :  " + abc);

abc++;

//等下个线程进来就可以换新上个等待的线程

notifyAll();

try {

//当前线程等待

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

if (abc >= 'z'){

notifyAll();

}

}

@Override

public void run() {

while (abc <= 'z') {

doPrint();

}

}

public static void main(String[] a){

Runnable p = new PrintWords();

Thread ta = new Thread(p,"a");

Thread tb = new Thread(p,"b");

ta.start();

tb.start();

}

}

测试结果

Thread- a : a  Thread- b : b  Thread- a : c  Thread- b : d  …  Thread- a : k  Thread- b : l  …  Thread- b : z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值