Java多线程基本使用一

1      多线程--初步

1进程

进程就是在系统中正在运行的程序

比如:QQ、迅雷、360、飞秋...

注意:操作系统中安装了很多应用程序,只有当开启之后,系统才会为这个程序分配系统资源(CPU执行权、内存...)让其运行在系统中

2线程的概念

线程是进程中的一个执行路径

3线程的意义:

每当一个进程开启之后,系统会为这个一个进程分配系统资源,而线程是进程中的一部分,当开启一个线程之后,系统不会单独为其分配内存,而是一个进程中的多个线程共享它们所在的进程的资源,所以线程也可以理解为是轻量级的进程。

4.Java程序的运行原理

代码是运行在线程中的,如果一个进程没有线程,那么进程就结束了,也就是说一个进程至少要有一个线程

当开启一个Java程序之后,在进程中至少会自动创建两个线程:主线程、垃圾回收线程

实际上,使用多线程并不是为了提高程序运行的速度,而是为了提高CPU的使用率

多进程:在系统中同时运行着多个应用程序,并且多个进程(应用程序)之间互不干扰

多线程:在进程中同时执行着多个执行路径,并且多个线程(执行路径)之间互不干扰

使用场景:

迅雷下载、360多个任务的执行、QQ视屏和文件传输………

1.1    基本概念

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

System.out.println("洗衣服,进度"+i+"%");

Thread.sleep(500);

}

for(int i=0;i<=100;i+=10){

System.out.println("做饭,进度"+i+"%");

Thread.sleep(500);

}

//创建

WashThread wash = newWashThread();

CookThread cook = newCookThread();

//启动

wash.start();

cook.start();

for(inti=10;i<=100;i+=10){

System.out.println("微波炉加热,进度"+i+"%");

try {

Thread.sleep(500);

} catch(InterruptedException e) {

// TODOAuto-generated catch block

e.printStackTrace();

}

}

//每隔10秒打印Hello,同时可以接受用户输入,并打印出来

Scanner s = newScanner(System.in);

PrintThread t = newPrintThread();

t.start();

while(true){

String line =s.nextLine();

System.out.println(line);

}

//选择性学习

LagRunnable r = newLagRunnable();

ExecutorService service =Executors.newCachedThreadPool();

service.execute(r);

service.execute(r);

service.execute(r);

service.shutdown();

//**************************************

FileReader fr = newFileReader("g:/config.txt");

BufferedReader br = newBufferedReader(fr);

//               ReverseRunnable r =newReverseRunnable();

String name = br.readLine();

Class clz =Class.forName(name);

Runnable r=(Runnable)clz.newInstance();

//**************************************

//以上部分选择性学习,扩展内容

Thread t = new Thread(r);

t.start();

进程运行起来的程序

线程可以理解为一个子进程

进程间进行切换的代价较大,不能共享资源

线程间切换代价小,可以共享数据和逻辑

Java进程每运行一个java应用程序,就会启动一个虚拟机进程,我们的程序就在其中运行

Java线程每个java进程中都默认有一个主线程,名字为main

(我们之前的程序都是在主线程中执行)

Thread类java中的线程,在一个线程中的代码逻辑就像被一根线串起来一样,从前往后顺序执行的

1.2    多线程

多线程,创建主线程以外的子线程,让多段逻辑"同时"执行

,节省时间、避免程序阻塞带来的问题

分时复用多个线程不停地快速切换使用CPU,表面上看是同时执行,其实也是有先后顺序的,同一时间只有一个线程占有CPU。

1.3    线程的状态

线程的状态:

创建-->就绪-->运行-->结束

运行-->阻塞--〉就绪

1.4    线程调度

假如当前计算机只有一个CPU且只有一个核心,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令,那么Java是如何对线程进行调用的呢?

1.4.1  线程有两种调度模型

分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

抢占调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取CPU时间片相对多一些

Java使用的是抢占式调度模型

1.4.2  调整线程的优先级

优先级:获取CPU资源的几率

优先级越高的线程会获取越多的运行机会

优先级的取值范围是1-10之间的整数

Java将差别最大的三个优先级定义成了Thread类中的静态常量

MAX_PRIORITY:最高优先级(10)

MIN_PRIORITY:最低优先级(1)

NORM_PRIORITY:默认优先级(5)

可以通过setPriority()和getPriority()方法进行设置和获取

1.5    使用Thread继承创建

线程的创建和使用

在Java中一切皆对象,线程这一类事物也用了一个类来进行了描述,这个类叫做Thread类

线程的创建

1、继承Thread类

2、实现Runnable接口

两种方式有什么区别?

1、 通过继承Thread类会出现单继承的问题,通过实现Runnable接口可以避免单继承的不足

2、 通过实现Runnable接口可以将线程的任务从线程的子类中分离出来,进行单独的封装,按照面向对象的思想将任务封装成了对象,并且可以方便实现多个线程的数据共享

一、使用Thread继承创建

1、定义一个类继承Thread,重写父类的run()方法,在run()中写上要在子线程中执行的逻辑

2、创建定义好的类,生成一个实例

3、调用这个实例的start(),启动线程(不要调用run(),这个run()是一个回调方法)

1.6    Runnable(接口)方式

二、Runnable(接口)方式

1、定义一个类实现Runnable接口,实现抽象方法run(),在run()中写上要在子线程中执行的逻辑

2、创建一个Thread实例,从构造方法参数传入1中定义类的实例

3、调用start()

Runnable方式从设计上耦合度更低,更灵活

1.7    数据共享

@Override

public void run() {

int progress = 0;  //初始化为0

while(true){

System.out.println(Thread.currentThread().getName()

+"烧饭,进度"+progress+"%");

if(progress ==100){//进度到100停止循环

break;

}

try {

Thread.sleep(100);

} catch(InterruptedException e) {

// TODOAuto-generated catch block

e.printStackTrace();

}

progress++;   //每次进度+1

}

}

Threadt1 = new Thread(new CookRunnable());

t1.start();

Thread t2 = new Thread(newCookRunnable());

t2.start();

CookRunnabler=newCookRunnable();

Threadt1=newThread(r);//两个线程使用同一个Runnable对象

Threadt2=newThread(r);

//以下方式不能共享,因为是分别使用了两个Runnable对象的两个progress属性

/* Thread t1 = new Thread(newCookRunnable());

Thread t2 = new Thread(newCookRunnable());*/

t1.start();//启动

try{

Thread.sleep(50);//错开50毫秒

}catch(InterruptedExceptione) {

//TODOAuto-generated catch block

e.printStackTrace();

}

t2.start();//启动另一个线程

}

数据共享:

1、局部变量不能被多个线程共享

2、成员变量可以被线程共享

1.8    常用方法

interrupt()方法:

Thread t = new Thread(new Runnable() {

@Override

publicvoid run() {

System.out.println("开始睡眠");

try{

Thread.sleep(20000);//睡眠20秒

}catch (InterruptedException e) {

//TODO Auto-generated catch block

System.out.println("睡到一半被断了");

}

System.out.println("结束睡眠");

}

});

t.start();

try{

Thread.sleep(5000);

}catch (InterruptedException e) {

//TODO Auto-generated catch block

e.printStackTrace();

}

t.interrupt();//启动子线程5秒后打断它的睡眠

}

线程的优先级:

t1.setPriority(1);

t2.setPriority(10);

t1.start();

t2.start();

Stop方法让线程停下:

public class CycleRunnable implements Runnable{

privateboolean control = true;

publicvoid setControl(boolean control) {

this.control= control;

}

@Override

publicvoid run() {

while(control){

try{

Thread.sleep(3000);

}catch (InterruptedException e) {

//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

publicstatic void main(String[] args) {

CycleRunnabler =new CycleRunnable();

Threadt = new Thread(r);

t.setName("停不下来的线程");//设置线程名字

t.start();

Scanners = new Scanner(System.in);

while(true){

Stringline = s.nextLine();

System.out.println("line="+line);

if(line.equals("stop")){

r.setControl(false);

break;

}

}

}

礼让Yield():

Thread t2 = new Thread(new Runnable() {

@Override

publicvoid run() {

for(inti=1;i<=1000;i++){

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

Thread.yield();//让给别的线程使用CPU

//让当前正在执行的线程释放CPU资源,让其他线程有机会去抢占CPU资源

//有可能在释放CPU资源之后,立马自己又再抢到了CPU资源

}

}

});

Thread.currentThread()通过Thread的静态方法获得当前线程的对象,还可以调用这个对象的getName()方法获取其名字

Thread.sleep(longm)通过这个静态方法,可以睡眠m毫秒,当前线程睡眠

setName(Stringn)设置线程名

intgetPriority()获取优先级,数字越大优先级越高,优先级高则抢占CPU成功的概率高一些,优先级范围1-10,默认5

setPriority(intp)设置优先级

yield()让出CPU使用权,但可以马上再抢回来

interrupt()方法,能打断一个线程sleep()、wait()、join()等方法,能让这些方法抛出InterruptedException

join()会合,等待子线程直到它结束,加入当前正在运行的线程,等待加入的线程执行完,再继续执行被加入的线程

setDaemon(boolean)方法将指定的线程设置为后台线程,设置守护线程,所有前台线程都结束后,后台线程(守护线程)会自动结束,该方法要在start()方法之前调用

线程加入:

public class ThreadJoinDemo{

public static void main(String[] args) throwsInterruptedException {

//线程加入

ThreadJoin t1 = new ThreadJoin("线程A");

ThreadJoin t2 = new ThreadJoin("线程B");

ThreadJoin t3 = new ThreadJoin("线程C");

t1.start();

/**

* 加入线程,让指定的线程(t1)加入到当前正在运行的线程(主线程)中

* 并且保证指定的线程执行完之后,在继续执行当前线程

*/

t2.start();

t1.join();

t3.start();

}

}

class ThreadJoin extends Thread{

public ThreadJoin(String name){

super(name);

}

public void run(){

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

try {

Thread.sleep(500);

System.out.println(Thread.currentThread().getName()+"......."+i);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

守护线程(后台线程):

public classThreadDaemonDemo {

public static void main(String[] args) throwsInterruptedException {

// 设置守护线程

ThreadDaemon t1 = new ThreadDaemon("线程A");

ThreadDaemon t2 = new ThreadDaemon("线程B");

ThreadDaemon t3 = new ThreadDaemon("线程C");

t1.start();

Thread.sleep(5000);

/*

* 设置守护线程(后台线程)

* 开启的线程默认都是前台线程,可以通过setDaemon(boolean)方法将指定线程设置为后台线程,

* 当所有前台线程都结束之后,后台线程会自动结束(无论有没有执行完)

*

* 现在,t1和主线程是前台线程,而t2和t3被设置为后台线程,也就是说当t1和主线程都结束之后,

* 也就代表前台线程全部结束了,那么两个后台线程(t2和t3)会自动结束

*/

t2.setDaemon(true);

t3.setDaemon(true);

t2.start();

t3.start();

}

}

class ThreadDaemon extendsThread {

public ThreadDaemon(String name) {

super(name);

}

@Override

public void run() {

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

try {

Thread.sleep(500);

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

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

线程停止:

public class ThreadStop {

public static void main(String[] args) throwsInterruptedException {

Stops t1 = new Stops("线程A");

t1.start();

// 3秒之后停止子线程运行

Thread.sleep(3000);

// 1)使用stop方法

// t1.stop();

// 2)设置标记

// 停止线程最本质的原理其实就是让run()方法执行完

// t1.setFlag(false);

// 3)使用interrupt方法

t1.interrupt();

// System.out.println("结束线程");

}

}

class Stops extends Thread{

private boolean runFlag = true;

public Stops(String name){

super(name);

}

@Override

public void run() {

// while(flag) {

while(!isInterrupted()) { // 判断中断标记是否为true

System.out.println("子线程...");

try {

/*

* 阻塞状态会被中断标记终止,如果是被强制终止阻塞状态的话会抛出中断异常(InterruptedException)

* 并且在结束阻塞状态之后,会清除中断标记(也就是说isInterrupted()返回值为false)

*/

Thread.sleep(6000);

} catch (InterruptedException e) {

//打印异常信息

// e.printStackTrace();

break;

}

}

System.out.println("线程已经运行完毕...");

}

public  voidsetFlag(boolean flag){

this.runFlag = flag;

}

}

1.8.1  线程停止

1.stop()方法,不建议使用,要让线程结束,就让它执行完;可以使用一个条件变量作为线程中循环条件,然后在外界通过控制这个变量,来使得线程结束(终止线程,已被废弃,不推荐使用)

2、设置标记

3.interrupt():中断线程的阻塞状态,调用该方法之后,线程会标记一个中断标记,当线程处于阻塞(比如休眠) 状态时,如果线程具备中断状态,就会直接中断当前的阻塞状态,并去除中断标记

(isInterrupted():测试线程是否已经中断)

PS:停止线程最本质的原理其实就是让run()方法执行完

1.9    线程的生命周期

生命周期:生命周期:从创建到结束的整个过程,在这期间会包含很多阶段(状态)

新建状态(New):MyThread  t1 = new MyThread();

就绪状态(Runnable):调用start()方法,使线程具有了运行资格,但是没有CPU的执行权(就绪队列:CPU在分配执行权的时候是在就绪队列中随机和挑选一个线程并分配其执行权(CPU调度)),等待CPU调度

运行状态(Running):获取了CPU的执行权,只有在运行状态下的线程才具备CPU的执行权

阻塞状态(Blocked):在运行状态下调用了sleep、wait或其他的一切方法使线程进入阻塞状态(睡眠池、等待池)

【sleep(毫秒值)《睡眠池:一块内存池,集合对象或数组》   wait()《等待池》】

《睡眠结束(时间结束或被中断)被唤醒(notify) -à>>>会再次进入就绪状态》

死亡状态(Dead):stop()    run()执行完《线程一旦结束进入死亡之后,就没有用了,该状态是不可逆的》

推荐阅读更多精彩内容