多线程

  • 多任务
  • 多线程
  • 进程
  • 线程
  • 程序,进程与线程
  • 并发与并行
  • 实现线程(线程的创建)
    • (1)Thread类
    • (2)Runnable接口
      • 1.Runnable接口的实现
      • 2一份资源,多个代理
    • (3)Callable接口
    • (4) 线程创建的时间点
  • 静态代理设计模式
  • 创建线程的简化表达式
    • (1)静态内部类
    • (2)局部内部类
    • (3)匿名内部类
    • (4)Lamda表达式
  • 线程的生命周期( 线程状态)
  • 线程的停止
  • 线程的休眠
  • 线程的礼让
  • 线程的加入
  • 线程的中断
  • 线程状态的观察
  • 线程的优先级
  • 守护线程
  • 线程的信息
  • 多线程并发

多任务

比如一边学习,一边玩手机,好像同时在执行两个事情,但其实是在一个时间点进行了一件事情。

多线程

比如访问网站,就是多个线程访问(也就是多个用户)服务器,各自访问各自的,如果是单线程就等一个线程访问完,下一个线程才能去访问。

普通方法的调用时一条路径,比如main方法调用一个a方法,a方法要调用b方法。
那么main方法就得等待a方法执行完才能继续向下走,a方法执行完就得等待b方法执行完

多线程的方法调用时就是多条路径
mian调用a方法,但不用等待a方法执行完,main方法继续接着往下走,a方法自己走它的路径


两条路径,但是每次实现的具体事情是并发的,即一个时间点要么只执行main路径里的内容,要么只执行a路径里的内容

进程

进程:正在运行的程序的实例。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程一般由程序,数据集合和进程控制块三部分组成。
进程具有的特征:

动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成

Windows操作系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。系统可以分配给每个进程一段有限的使用CPU的时间(CPU时间片),CPU在这段时间中执行某个进程,然后下一个时间片又跳至另一个进程中去执行。因为CPU转换很快,所以使得每个进程好像是同时进行的。

线程

比如你在使用电脑时,你可以同时qq聊天,听音乐,下载文件。这些活动是同时进行的。这种思想被称为并发,并发完成的每一件事情称为线程
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间。
线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。

一个线程则是进程中的执行流程,一个进程可以包含多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。在单线程中,程序代码按调用顺序依次向下执行。如果一个进程同时完成多段代码的操作,就需要多线程。

java提供了并发机制,程序员可以在程序中执行多个线程,每个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程。

程序,进程与线程

在操作系统中运行中的程序就是进程,如看视频。
一个进程可以有多个线程,如视频中同时听声音、看图像、显示字幕

区别 进程 线程
根本区别 作为资源分配的单位 调度和执行的单位
开销 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销 线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换的开销小
所处环境 在操作系统中能同时运行多个任务(程序) 在同一应用程序中有多个顺序流同时执行
分配内存 系统在运行会为每个进程分配不同的内存区域 除了CPU外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源
包含关系 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线,而是多个线程共同完成的 线程是进程的一部分

很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。
如果是模拟出来的多线程,即一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为 切换的很快,所以就有同时执行的错觉,也就是并发

同一个时间完成一个事情就是单线程
同时进行多个事情就是线程机制

并发与并行

并行是指两个或者多个事件在同一时刻发生
并发是指两个或多个事件在同一时间间隔发生。
可以这么看,并行就是真的同时在干多个事情,吃牛排时,你左手拿叉右手拿刀,两个手一块操作牛排,这就是并行
并发可以看做是一个假的同时。就是在一个时间T内,你把这个时间分为很多个小的时间段t1tn,这样有形成了很多个时间间隔ti,在这每一个时间间隔ti内你只能干一件事请的一小部分,所以完成一个事情需要多个时间间隔。但是时间间隔t的切换很快,感觉你就在这个时间T内“同时”干了多个事情。比如你是闪电侠,快如闪电,0.1 0.2内秒你在中国,0.3~ 0.4秒你在法国,0.50.6你在美国,0.6 0.7内秒你在中国,0.8~ 0.9内秒你在法国,0.9~1.0秒内你在美国,那么从这一秒来看。你好像同时在三个国家。

实现线程(线程的创建)

java主要提供三种方式实现线程,分别为继承java.lang.Thread类,实现jjava.lang.Runnable接口,实现Callable接口
Callable接口是juc并发包下的,用的不多,主要还是前两种
一般来说多使用Runnable 接口,因为一个类只能继承一个类,如果你除了要继承Thread类还要继承其他类的话得、就得实现Runnable接口

API文档描述
1.Thread类
public class Thread extends Object implements Runnable
Thread类也是继承了Runnable接口了的
每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守护程序。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护线程。
用户线程:用户线程是程序必须等它执行完才能停下的。
守护线程:程序可以不用等它执行完就停下。守护线程是为用户线程服务的
默认情况下,每个线程都是用户线程,如果把它标记为守护线程,虚拟机不用等它执行完就会停止

 class PrimeThread extends Thread {long minPrime;PrimeThread(long minPrime) {this.minPrime = minPrime;}public void run() {  //run方法是线程的入口点// compute primes larger than minPrime. . .}}

然后,以下代码将创建一个线程并启动它运行:

PrimeThread p = new PrimeThread(143);
p.start();

由Thread类的子类执行start()方法,注意不是run()方法(run()方法只是一个普通方法的调用,调用run()方法只有一条路径,即main路径得等待run()方法执行完毕之后,才能继续执行),而start()方法才能创建一个新的线程。start()方法只是扔给CPU,什么时候执行是CPU说了算,不是人为操作。

2.Runnable接口
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。
run方法是线程的入口点

 class PrimeRun implements Runnable {long minPrime;PrimeRun(long minPrime) {this.minPrime = minPrime;}public void run() {// compute primes larger than minPrime. . .}}

然后,以下代码将创建一个线程并启动它运行:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

实现Runnable接口的类必须创建一个Thread类来调用strat()方法,Runnable接口不具备start()方法,没有交给CPU的能力。所以必须创建一个Thread类

(1)Thread类

这个类的实例化对象为代表一个线程
构造方法

public Thread()       //创建一个新的线程对象
public Thread(String threadName)     //创建一个名称为threadName的线程对象
public Thread(Runnable target);   //通过Runnable 创建
Thread(Runnable target, String name) ;  //  通过Runnable 创建 ,并起个名字
Thread(ThreadGroup group, Runnable target)  ;   //通过ThreadGroup(线程组)创建
public class Test extends Thread{     //创建一个新的继承Thread类的线程
}

完成线程功能放在类的run()方法中。
当一个类继承Thread类后,就可以在该类中覆盖run()方法,将实现该线程功能的代码写入run()方法中。同时调用Thread类中的start()方法执行线程。也就是调用run()方法。如果start()方法调用一个已经启动的线程,将抛出异常

run()方法的语法
public void run(){
}
 public static void main(String[] args) {new Test().start();}

主方法就是一个线程,主方法线程由java虚拟机负责,启动自己写的线程需要strat()方法

public class Test extends Thread {private int count=0;@Overridepublic void run() {while (true){System.out.print((count++)+" ");if(count>10)break;}}public static void main(String[] args) {new Test().start();}
}
输出结果:
0 1 2 3 4 5 6 7 8 9 10

通常在run()方法中使用无限循环的形式,使得线程一直运行下去,所以要有一个退出循环的条件。
在main()方法中,使线程执行需要Thread的start()方法,不执行strat()方法,它一直就是一个实例,不是一个线程。

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;import org.apache.commons.io.FileUtils;public class WebDownloader {public void download(String url,String name) {try {FileUtils.copyURLToFile(new URL(url), new File(name));} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace(); }}
}
public class TDownloader extends Thread {private String url; //远程路径private String name;  //存储名字public TDownloader(String url, String name) {this.url = url; this.name = name;}@Overridepublic void run() {WebDownloader wd =new WebDownloader();wd.download(url, name);      System.out.println(name);}public static void main(String[] args) {TDownloader td1 =new TDownloader("","phone.jpg");TDownloader td2 =new TDownloader("","spl.jpg");TDownloader td3 =new TDownloader("","success.jpg");//启动三个线程td1.start();td2.start();td3.start();}
}
输出结果:
spl.jpg
success.jpg
phone.jpg

打印名字可以看出,它的打印顺序并不是按照代码的顺序打印,而是不唯一的,这个因为创建了三个线程(三条路径)三条路径并行 ,执行start()方法交给CPU执行,而何时执行是CPU进行调度,是CPU说了算。

(2)Runnable接口

1.Runnable接口的实现

在多线程优先使用Runnable接口,虽然Runnable接口比较麻烦。常用匿名类。

class RunnableTest implements Runnable{
}
new Thread(new RunnableTest ()).start();    //必须要创建Thread对象,传入实现Runnable的对象,来调用start()方法

如果一个类需要继承其他泪,而且还需要实现多线程,那就需要Runnable接口。

public Son extends Father implements Runnable{
}

实现Runnable接口会创建一个Thread对象,并将Runnable对象与Thread对象想关联。
Thread类的构造方法

public Thread(Runnbale target)
public Thread(Runnable target,String name)

这两个构造方法参数中都有Runnable实例,使用构造方法就能将Runnable实例和Thread类关联起来
使用Runnable接口 启动 新的线程的步骤
1.建立Runnable对象
2.使用参数为Runnable对象的构造方法创建Thread实例
3.调用start()方法启动线程
通过Runnable接口创建线程首先需要编写一个实现Runnable接口的类,然后实例化该类的对象,这样就建立了Runnable对象.然后用相应的构造方法创建Thread实例。最后使用Thread实例调用start()方法启动线程

启动线程是调用Thread的start()方法,而不是调用run()方法

实现图标的移动import javax.swing.*;
import java.awt.*;
import java.net.URL;public class Test extends JFrame {private Container container=getContentPane();private JLabel bike=new JLabel();private static Thread t;private int x=0;public Test(){setBounds(300,150,800,100);container.setLayout(null);URL url=Test.class.getResource("bike.jpg");Icon icon=new ImageIcon(url);bike.setIcon(icon);bike.setHorizontalAlignment(SwingConstants.LEFT);bike.setOpaque(true);t=new Thread(new Runnable() {    //匿名内部类@Overridepublic void run() {while (x<=750){bike.setBounds(x,10,200,50);try {Thread.sleep(10);   //休眠10毫秒} catch (InterruptedException e) {e.printStackTrace();}x+=5;if(x==750)x=10;}}});t.start();   //构造方法直接启动线程   container.add(bike);setVisible(true);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}public static void main(String[] args) {new Test();}
}

2一份资源,多个代理

Runnable方便共享资源
比如抢票,它是多个线程对同一个资源的操作,因为票源只有一个

public class Web12306 implements Runnable{private int ticketNums = 100;  //票数@Overridepublic void run() {while(true) {if(ticketNums<0) {break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}}public static void main(String[] args) {//一份资源Web12306 web =new Web12306();//多个代理new Thread(web,"黄牛一").start();    //传入线程名new Thread(web,"黄牛二").start();new Thread(web,"黄牛三").start();;}
}
模拟龟兔赛跑  (一份资源,多份代理)public class Racer implements Runnable{private  String winner;//胜利者@Overridepublic void run() {for(int steps =1;steps<=50;steps++) {     //模拟休息if(Thread.currentThread().getName().equals("rabbit") && steps%10==0) {  //兔子每走十步停一下try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"-->"+steps);//比赛是否结束boolean flag = gameOver(steps);if(flag) {break;}}}private boolean gameOver(int steps) {if(winner!=null) { //存在胜利者return true;}else {if(steps ==50) {winner = Thread.currentThread().getName();System.out.println("winner==>"+winner);return true;}}return false;}public static void main(String[] args) {//一份资源Racer racer = new Racer();//多份代理new Thread(racer,"tortoise").start();       //传入线程名new Thread(racer,"rabbit").start();}
}
输出结果:
rabbit-->1
tortoise-->1
rabbit-->2
tortoise-->2
rabbit-->3
tortoise-->3
rabbit-->4
tortoise-->4
rabbit-->5
tortoise-->5
rabbit-->6
tortoise-->6
rabbit-->7
tortoise-->7
rabbit-->8
tortoise-->8
rabbit-->9
tortoise-->9
tortoise-->10
tortoise-->11
tortoise-->12
tortoise-->13
tortoise-->14
tortoise-->15
tortoise-->16
tortoise-->17
tortoise-->18
tortoise-->19
tortoise-->20
tortoise-->21
tortoise-->22
tortoise-->23
tortoise-->24
tortoise-->25
tortoise-->26
tortoise-->27
tortoise-->28
tortoise-->29
tortoise-->30
tortoise-->31
tortoise-->32
tortoise-->33
tortoise-->34
tortoise-->35
tortoise-->36
tortoise-->37
tortoise-->38
tortoise-->39
tortoise-->40
tortoise-->41
tortoise-->42
tortoise-->43
tortoise-->44
tortoise-->45
tortoise-->46
tortoise-->47
tortoise-->48
tortoise-->49
tortoise-->50
winner==>tortoise
rabbit-->10

(3)Callable接口

Callable接口类似于Runnable 。是高级并发编程,juc并发编程的一部分

class CallText implements Callable<V>{//.......public V call() throws Exception {    }
}

Callable比Runnable更强大,它的call()方法有返回值(),可以throws异常(Runnable的run()方法不能throws 异常,只能在线程体内使用try-catch捕获异常)

区别 Callable的call()方法 Runnable的run()方法
返回值 可以返回Callable接口的泛型 没有返回值void
异常 call()方法可以throws异常 run()方法不能throws异常,只能在线程体内try-catch

Callable的使用较麻烦,下面是执行步骤
1.创建目标对象
2. 创建执行服务: ExecutorService ser=Executors.newFixedThreadPool(int nThreads); 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
3. 提交执行: Future result =ser.submit(Callable task) ; 传入目标对象
4. 获取结果: V r1 =result1.get();
5. 关闭服务: ser.shutdownNow();

public class Racer implements Callable<Integer>{private  String winner;//胜利者@Overridepublic Integer call() throws Exception {for(int steps =1;steps<=50;steps++) {     //模拟休息if(Thread.currentThread().getName().equals("pool-1-thread-1") && steps%10==0) {   //这里的getName()获取和Runnable的不一样,Runnable传入了线程名Thread.sleep(100);}System.out.println(Thread.currentThread().getName()+"-->"+steps);//比赛是否结束boolean flag = gameOver(steps);if(flag) {return steps;}}return null;}private boolean gameOver(int steps) {if(winner!=null) { //存在胜利者return true;}else {if(steps ==50) {winner = Thread.currentThread().getName();System.out.println("winner==>"+winner);return true;}}return false;}public static void main(String[] args) throws InterruptedException, ExecutionException {Racer racer = new Racer();//创建执行服务: ExecutorService  ser=Executors.newFixedThreadPool(2);//提交执行: Future<Integer> result1 =ser.submit(racer) ;     //一份资源,多份代理Future<Integer> result2 =ser.submit(racer) ;//获取结果:  Integer r1 =result1.get();Integer r2 =result2.get();System.out.println(r1+"-->"+r2);//关闭服务:  ser.shutdownNow();}
}

(4) 线程创建的时间点

public class StartThread extends Thread{public void run() {  // 线程入口点for(int i=0;i<20;i++) {System.out.println("一边听歌");}}public static void main(String[] args) {            //创建子类对象StartThread st =new StartThread();//启动 st.start(); //不保证立即运行 cpu调用for(int i=0;i<20;i++) {System.out.println("一边上网");}}
}
输出结果:
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边上网
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边上网
一边上网
一边上网
一边上网
一边上网

每次输出结果都是不同的 ,多运行几次就会出现穿插的效果,这是因为有两条并行的路径

public class StartThread extends Thread{// 线程入口点@Overridepublic void run() {for(int i=0;i<20;i++) {System.out.println("一边听歌");}}public static void main(String[] args) {    for(int i=0;i<20;i++) {System.out.println("一边上网");}     StartThread st =new StartThread();st.start(); }
}

如果是这么实现多线程的,那就跟普通方法的调用没区别了,它一定会先执行完"上网"的循环,再去执行“听歌”的循环。因为在执行上网的循环时,它并没有开启一个新的线程,没有另一条路径和它一块走。执行完上网的循环再开启新线程,这时上网的循环已经执行完了,你再开启一个没有用。这样看起来类似还是“一条路径”

静态代理设计模式

Runnable接口实现多线程,启动必须借助Thread对象,这个Thread对象称为代理对象(静态代理)
代理分为静态代理和动态代理
代理一般用于记录日志,增强服务。
静态代理和动态代理的区别是:静态代理的类是写好了的,直接拿来用。动态代理是在运行过程中,这个类是动态构建出的(临时拿过来,临时构建出的)

静态代理:代理角色,真实角色

静态代理public class StaticProxy {public static void main(String[] args) {new WeddingCompany(new Man ("小明","小红")).marry();//new Thread(线程对象).start();}
}
interface Marry{void marry();
}
class Man implements Marry{   //真实角色private String manname;private String  wifename;public Man(String manname,String  wifename){this.manname=manname;this.wifename=wifename;}@Overridepublic void marry() {System.out.println(manname+"和"+wifename+"结婚了");}}
class WeddingCompany implements Marry{  //代理角色private Marry target;         //真实角色public WeddingCompany(Marry target) {this.target = target;}@Overridepublic void marry() {ready();this.target.marry();after();}private void ready() {System.out.println("布置婚礼");}private void after() {System.out.println("完善后续");}
}输出结果:
布置婚礼
小明和小红结婚了
完善后续

Runnable就是使用了静态代理的模式,
new WeddingCompany(new Man (“小明”,“小红”)).marry();
new Thread(线程对象).start();
可以对比一下

创建线程的简化表达式

当这个线程使用一次或次数很少时,我们可以使用简化的形式去创建一个线程

(1)静态内部类

public class LambdaThread {//静态内部类static class Test implements Runnable{     public void run() {for(int i=0;i<20;i++) {System.out.println("学习"+i);}}}public static void main(String[] args) {           new Thread(new Test()).start();}
}

(2)局部内部类

public class LamdaThread {public static void main(String[] args) {class Test implements Runnable{public void run() {for(int i=0;i<20;i++) {System.out.println("学习"+i);}}}new Thread(new Test()).start();}
}

(3)匿名内部类

public class LamdaThread {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<20;i++) {System.out.println("学习"+i);}}}).start();}
}

(4)Lamda表达式

jdk1.8新特性
它的用法:https://blog.csdn.net/weixin_44035017/article/details/102536670

public class LamdaThread {public static void main(String[] args) {new Thread(()->{for(int i=0;i<20;i++) {System.out.println("学习"+i);}}).start();}
}

线程的生命周期( 线程状态)

线程具有生命周期,它细分包含七种状态,分别为出生状态,就绪状态,运行状态,等待状态,休眠状态,阻塞状态和死亡状态
出生状态:线程被创建时的状态,在调用start()方法之前都处于出生状态
就绪状态:当调用start()方法后,线程就处于就绪状态。一旦线程进入就绪状态,他就会在就绪状态和运行状态下转换,同时也有可能进入等待,休眠,阻塞或死亡状态。
运行状态:当线程得到系统资源后就处于运行状态。
等待状态:当在运行状态的线程调用Thread的wait()方法时,该线程就会处于等待状态。进入等待状态的线程必须调用Thread的notify()方法才能被唤醒,进入就绪状态,得到系统资源进入运行状态。notifyAll()是唤醒所有处于等待状态的线程。
休眠状态:当线程调用sleep()方法时,该线程会进入休眠状态,休眠时间结束后进入就绪状态。
阻塞状态:如果一个线程发出输入/输出请求时,该线程就会进入阻塞状态,当输入/输出结束时,就会进入就绪状态。当线程进入阻塞状态,即使系统资源空闲,线程也不能回到运行状态。
死亡状态:当线程的run()方法执行完毕,线程就会进入死亡状态

粗略的分包含五大状态,出生状态,就绪状态,运行状态,阻塞状态,死亡状态
1.当new一个线程对象,就进入出生状态。一旦进入出生状态,每个线程都有自己的工作空间

工作空间跟主存进行交互,从主存拷贝数据考到工作空间做操作,这就是它的工作台
2.调用start()方法,线程进入就绪状态。进入就绪状态,不代表立即被调度到。它需要等待CPU的调度。进入就绪状态只能表示它具有运行的能力和条件,还没有分配到CPU,处于线程的就绪队列。
3.当系统选定了一个等待执行的线程,它才会进入运行状态。进入运行状态,它才会调用run()方法
4.进入运行状态,线程才真正 执行线程体的代码块
5.当调用sleep()、wait()或同步锁定时, 线程进入阻塞状态,所谓阻塞就是 代码不往下执行,在等待着,同理 不保证调用以上方法就立即阻塞。 阻塞事件解除后,重新进入就绪状态, 等待cpu调度执行才进入运行状态。
6.一旦进入死亡状态,不能 再调用start()再次启动线程

使线程处于就绪状态的几种方法
1.start()
2.阻塞事件解除
3.yield()方法
4.jvm将本地线程切换到其他线程
7.等待输入/输出完成(也就是阻塞事件解除)

使线程处于阻塞状态的原因
1.sleep()方法 抱着资源睡觉,等待多少秒
2.wait()方法 资源不占用
3.join()方法
4.IO操作 read write

使线程处于死亡状态的原因
1.线程执行完了
2.被强制终止(stop(),destroy() 这两个方法不推荐使用 而且也被JDK废弃了)

当线程处于就绪状态,使线程再次进入运行状态的几种方法
1.notify()方法
2.notifyAll()方法
3.interrupt方法
4.线程的休眠时间结束
5.输入/输出结束

线程的停止

不使用JDK提供的stop()/destroy()方法


提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。

class Test implements Runnable{ private boolean flag =true; //定义 线程体使用的标识@Override public void run() {  while(flag){           //线程体使用该标识System.out.println("study thread...."); } } public void stop(){          //对外提供方法改变标识  this.flag =false; }
}
public class StopThread implements  Runnable {private boolean flag=true;   //线程体使用的标识private String playerName;public StopThread(String playerName) {this.playerName = playerName;}@Overridepublic void run() {int i=0;while (flag){System.out.println(playerName+"得分:"+i++);}}public void stopThread(){    //提供对外停止线程的方法this.flag=false;}public String getPlayerName() {return playerName;}public static void main(String[] args) {StopThread st=new StopThread("欧文");new Thread(st).start();for(int i=0;i<30;i++){if(i==20){    st.stopThread();    System.out.println(st.getPlayerName()+"下场休息");}System.out.println("主线程执行:"+i);}}
}输出结果:
主线程执行:0
欧文得分:0
主线程执行:1
欧文得分:1
主线程执行:2
欧文得分:2
主线程执行:3
欧文得分:3
主线程执行:4
欧文得分:4
欧文得分:5
欧文得分:6
欧文得分:7
主线程执行:5
欧文得分:8
主线程执行:6
欧文得分:9
主线程执行:7
欧文得分:10
主线程执行:8
欧文得分:11
主线程执行:9
欧文得分:12
主线程执行:10
欧文得分:13
主线程执行:11
主线程执行:12
主线程执行:13
欧文得分:14
主线程执行:14
欧文得分:15
主线程执行:15
欧文得分:16
主线程执行:16
欧文得分:17
主线程执行:17
欧文得分:18
主线程执行:18
欧文得分:19
主线程执行:19
欧文得分:20
欧文下场休息
主线程执行:20
主线程执行:21
主线程执行:22
主线程执行:23
主线程执行:24
主线程执行:25
主线程执行:26
主线程执行:27
主线程执行:28
主线程执行:29

线程的休眠

sleep(时间)指定当前线程阻塞的毫秒数;
每一个对象都有一个锁,sleep不会释放锁;
sleep时间达到后线程进入就绪状态;

try {Thread.sleep(10);    //休眠10毫秒} catch (InterruptedException e) {e.printStackTrace();}

sleep()放在try-catch中
sleep()方法执行后,线程进入休眠状态,当它醒来后,不能保证它醒来后就进入运行状态,只能保证它进入就绪状态。就绪状态得到系统资源后进入运行状态
sleep可以模拟网络延时,放大问题。可以模拟倒计时

线程的礼让

yield跟sleep一样都属于暂停线程,sleep()占着资源不放,从运行状态进入阻塞状态,当执行秒数结束进入就绪状态。
yield()直接从运行状态跳回就绪状态,表示退让线程,让出CPU的调度,避免当前线程占用CPU过久 。重新和其他就绪线程竞争
yield和sleep一样都属于静态方法

public class YieldDemo01 {public static void main(String[] args) {MyYield my =new MyYield();new Thread(my,"a").start();new Thread(my,"b").start();}}
class MyYield implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"-->start");Thread.yield(); //礼让System.out.println(Thread.currentThread().getName()+"-->end");}
}

执行Thread.yield()不一定礼让成功,比如它又回来掉自己了
每次的输出结果不同

礼让成功                          礼让不成功
a-->start                        b-->start
b-->start                        b-->end
b-->end                          a-->start
a-->end                          a-->end
public class Yield {public static void main(String[] args) {new Thread(()->{for(int i=0;i<20;i++) {System.out.println("lambda...."+i);}}) .start();for(int i=0;i<20;i++) {if(i%5==0) {Thread.yield(); //main礼让}System.out.println("main...."+i);}}
}
输出结果:
main....0
main....1
main....2
main....3
main....4
main....5                 礼让没成功
main....6
main....7
main....8
main....9
main....10                礼让没成功
main....11
main....12
main....13
main....14
main....15
main....16
main....17
main....18
main....19               礼让成功
lambda....0
lambda....1
lambda....2
lambda....3
lambda....4
lambda....5
lambda....6
lambda....7
lambda....8
lambda....9
lambda....10
lambda....11
lambda....12
lambda....13
lambda....14
lambda....15
lambda....16
lambda....17
lambda....18
lambda....19

线程的加入

join合并线程,待此线程执 行完成后,再执行其他线程,其他线程阻塞
join是成员方法
如果当前某个程序为多线程程序,假如存在一个线程A,现在需要插入线程B,并要求线程B先执行完毕,然后再执行线程A,此时可以使用Thread类的join()方法

线程对象.join();   //等待线程终止,原线程才继续执行
线程对象.join(lonh mills)   //long mills:指原线程最多等待mills毫秒
public class BlockedJoin01 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for(int i=0;i<10;i++) {System.out.println("lambda...."+i);}});t.start();for(int i=0;i<10;i++) {if(i==5) {t.join(); //插队 main被阻塞了  必须等待t执行完}System.out.println("main...."+i);}}
}
输出结果:
main....0
main....1
main....2
main....3
main....4
lambda....0        main被阻塞了  必须等待t执行完
lambda....1
lambda....2
lambda....3
lambda....4
lambda....5
lambda....6
lambda....7
lambda....8
lambda....9
main....5
main....6
main....7
main....8
main....9
package com.THREAD;import javax.swing.*;
import java.awt.*;public class Bar extends JFrame {private Thread threadA;private Thread threadB;final JProgressBar progressBar1=new JProgressBar();final JProgressBar progressBar2=new JProgressBar();private int count=0;public Bar(){super();setBounds(600,300,100,100);setVisible(true);getContentPane().add(progressBar1, BorderLayout.NORTH);getContentPane().add(progressBar2,BorderLayout.SOUTH);progressBar1.setStringPainted(true);progressBar2.setStringPainted(true);threadA=new Thread(new Runnable() {int count=0;@Overridepublic void run() {while(true){progressBar1.setValue(++count);try {Thread.sleep(100);//    if(count==20)///     threadB.join();    //线程不加入} catch (InterruptedException e) {e.printStackTrace();}}}});threadA.start();threadB=new Thread(new Runnable() {int count=0;@Overridepublic void run() {while (true) {progressBar2.setValue(++count);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(count==100)break;}}});threadB.start();setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}public static void main(String[] args) {new Bar();}
}


上图是没有加入join()方法的,两个线程同步进行

package com.THREAD;import javax.swing.*;
import java.awt.*;public class Bar extends JFrame {private Thread threadA;private Thread threadB;final JProgressBar progressBar1=new JProgressBar();final JProgressBar progressBar2=new JProgressBar();private int count=0;public Bar(){super();setBounds(600,300,100,100);setVisible(true);getContentPane().add(progressBar1, BorderLayout.NORTH);getContentPane().add(progressBar2,BorderLayout.SOUTH);progressBar1.setStringPainted(true);progressBar2.setStringPainted(true);threadA=new Thread(new Runnable() {int count=0;@Overridepublic void run() {while(true){progressBar1.setValue(++count);try {Thread.sleep(100);if(count==20)threadB.join();    //线程加入} catch (InterruptedException e) {e.printStackTrace();}}}});threadA.start();threadB=new Thread(new Runnable() {int count=0;@Overridepublic void run() {while (true) {progressBar2.setValue(++count);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(count==100)break;}}});threadB.start();setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}public static void main(String[] args) {new Bar();}
}


当线程A的进度条执行到百分之20时,线程B join。这时线程A会等待线程B执行完毕再继续执行

线程的中断

线程对象.interrput()     //谁调用谁中断

线程突然被中断可能会造成死锁的问题,调用这个方法会抛出InterruptedException的异常,所以必须捕捉这个异常.
可以在线程中断后做一些资源释放的操作,如断开数据库,断开数据流等

让A进度条在50%时中断package com.THREAD;import javax.swing.*;
import java.awt.*;public class Bar extends JFrame {private Thread threadA;private Thread threadB;final JProgressBar progressBar1=new JProgressBar();final JProgressBar progressBar2=new JProgressBar();private int count=0;public Bar(){super();setBounds(600,300,100,100);setVisible(true);getContentPane().add(progressBar1, BorderLayout.NORTH);getContentPane().add(progressBar2,BorderLayout.SOUTH);progressBar1.setStringPainted(true);progressBar2.setStringPainted(true);threadA=new Thread() {int count=0;@Overridepublic void run() {while(true){progressBar1.setValue(++count);try {if (count == 50)threadA.interrupt();    //线程加入Thread.sleep(100);}catch(InterruptedException e){System.out.println("当前线程中断");e.printStackTrace();}}}};threadA.start();threadB=new Thread() {int count=0;@Overridepublic void run() {while (true) {progressBar2.setValue(++count);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(count==100)break;}}};threadB.start();setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}public static void main(String[] args) {new Bar();}
}


上面这段代码显示在50%时,并进度条A并没有停止,这是因为try-catch写到了循环的里面,我们知道当一个代码抛出异常后,不会执行这个try-catch代码块里面的此代码行后面的代码,而去执行catch之后的代码,而try-cath写在循环里面,执行完catch后会继续执行循环,所以他不会停止。只会抛出异常。所以循环要写在try-catch里面

package com.THREAD;import javax.swing.*;
import java.awt.*;public class Bar extends JFrame {private Thread threadA;private Thread threadB;final JProgressBar progressBar1=new JProgressBar();final JProgressBar progressBar2=new JProgressBar();private int count=0;public Bar(){super();setBounds(600,300,100,100);setVisible(true);getContentPane().add(progressBar1, BorderLayout.NORTH);getContentPane().add(progressBar2,BorderLayout.SOUTH);progressBar1.setStringPainted(true);progressBar2.setStringPainted(true);threadA=new Thread() {@Overridepublic void run() {int count=0;try {while(true){progressBar1.setValue(++count);if (count == 50)threadA.interrupt();    //线程加入Thread.sleep(100);}}catch(InterruptedException e){System.out.println("当前线程中断");e.printStackTrace();}}};threadA.start();threadB=new Thread() {int count=0;@Overridepublic void run() {while (true) {progressBar2.setValue(++count);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if(count==100)break;}}};threadB.start();setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}public static void main(String[] args) {new Bar();}
}

线程状态的观察

线程所处状态可以通过Thread.getState()方法获取它所处状态
Thread.State是Thread类的一个常量,它是一个枚举类型

当new一个Thread对象但没有调用start()方法时,它处于NEW
当调用start()方法,或处于运行状态,它处于RUNNABLE。RUNNABLE包含就绪状态和运行状态
当线程遇到了阻塞,如果这个阻塞为IO操作,wait()的锁定,juc里的锁定,它处于BLOCKED。如果是sleep(),join(0,它处于WAITING。有时间的等待是TIMED_WAITING,如sleep()
死亡状态就是TERMINATED

public class AllState {public static void main(String[] args) {Thread t = new Thread(()->{for(int i=0;i<5;i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i);}}) ;//观察状态State state = t.getState();System.out.println(state);  //NEWt.start(); state = t.getState();   //RUNNABLESystem.out.println(state);while(state != Thread.State.TERMINATED) {    //当t线程状态为TERMINATED时停止监控try {Thread.sleep(100);  //主线程每隔100毫秒监控一下t线程的状态} catch (InterruptedException e) {e.printStackTrace();}state = t.getState();   //TIMED_WAITINGSystem.out.println(state);}        state = t.getState();   //TERMINATEDSystem.out.println(state);     }
}
输出结果:
NEW
RUNNABLE
RUNNABLE
0
1
RUNNABLE
2
RUNNABLE
TIMED_WAITING
3
4
RUNNABLE
TERMINATED
TERMINATED
while(true) {//活动的线程数int num = Thread.activeCount();System.out.println(num);if(num==1) {   //IDEA要num==2break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}state = t.getState();   //TIMED_WAITINGSystem.out.println(state);
}

退出出监控时,除了可以通过当 t 线程为TERMINATED时推出监控循环,还可以按当前线程数推出。但是这一点eclipse和IDEA有区别
eclipse:当 t 线程死亡时,只剩下主线程,
但IDEA在没有其他线程时就有两个线程
Thread.currentThread().getThreadGroup().list() 可以列出当前线程所在组有哪些线程,线程的输出格式为 [线程名称,线程的优先级,线程所在线程组的名称]:

public class Main {public static void main(String[] args) {Thread.currentThread().getThreadGroup().list();}
}


可见除了主线程(“Thread[main]”),还有一个 “Monitor Ctrl-Break” 线程,这应该是 IDEA 通过反射的方式,伴随你的程序一起启动的对你程序的监控线程。
Thread.activeCount() 返回的只是一个估计值,所以你并不能依靠这个值来判断当前有多少线程在运行。

线程的优先级

每个线程都有各自的优先级,线程的优先级可以表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。
Thread类中包含的成员变量代表了线程的某些优先级,如Thread.MIN_PROIORITY(常数1),Thread.MAX_PROIORITY(常数10),Thread.NROM_PROIORITY(常数5)。每个线程的优先级都在Thread.MIN_PROIORITY~Thread.MAX_PROIORITY之间,默认情况下都是Thread.NROM_PROIORITY
多任务操作系统中,每个线程都会得到一小段CPU时间片运行,时间结束后,将轮换另一个线程进入运行状态,这时系统就会选择与当前线程优先级相同的线程让其进入运行状态。
比如线程A,线程B的优先级为5,线程C的优先级为4,线程D的优先级为3.所以优先级为5的线程A会率先得到CPU时间片,当该时间结束后,轮换到与线程A优先级相同的线程B;当线程B的运行时间结束后,会继续轮换到A。直到线程A和线程B都执行完毕,才会轮换到线程C;当线程C结束后,才会轮换到线程D。
线程的优先级可以使用setPriority()方法,如果使用该方法设置的优先级不在1~10内,将产生IllegalArgumentException异常

守护线程

线程分为用户线程和守护线程;
虚拟机必须确保用户线程执行完毕;
虚拟机不用等待守护线程执行完毕;
守护线程可以做如后台记录操作日志、监控内存使用等

记录一件事的日志,当这件事结束后就不在记录日志。public class DaemonTest {public static void main(String[] args) {OneThing oneThing = new OneThing();Record log  = new Record();Thread t =new Thread(log);t.setDaemon(true);//将用户线程调整为守护           设置一定要在start()前t.start();new Thread(oneThing).start();}}
class OneThing implements Runnable{@Overridepublic void run() {for(int i=1;i<=5;i++) {System.out.println("do thing");}System.out.println("结束");}
}
class Record implements Runnable{@Overridepublic void run() {while (true) {System.out.println("记录日志中....");}}
}

java默认所有线程都是用户线程
如果不把Record线程设为用户线程,那么虚拟机就会一直等待Record线程结束。

线程的信息

isAlive() 判断线程是否还活着,即线程是否还未终止
setName() 给线程起一个名字
getName() 获取线程的名字
currentThread() 取得当前正在运行的线程对象,也就是获取自己本身

setName()和getName()都是代理名称

设置名称 :真实角色+代理角色public class InfoTest {public static void main(String[] args) throws InterruptedException {System.out.println(Thread.currentThread().isAlive());MyInfo info = new MyInfo("真实角色");    //真实角色Thread t = new Thread(info);      //代理角色t.setName("代理角色");            //代理名称          设置一定要在start()前t.start();Thread.sleep(1000);System.out.println(t.isAlive());}
}
class MyInfo implements Runnable{private String name;public MyInfo(String name) {this.name = name;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"-->"+name);}
}
输出结果:
true
代理角色-->真实角色
false

Thread t是一个代理对象,MyInfo 是一个真实对象
这就是静态代理设计模式

多线程并发

下一篇https://blog.csdn.net/weixin_44035017/article/details/102566747

JavaSE 多线程相关推荐

  1. [javaSE] 多线程(守护线程)

    我们一般使用多线程,都是while的死循环,想要结束线程,只需退出死循环即可 当线程中调用了sleep()方法或者wait()方法,当前的线程就会进入冻结状态,这个线程就结束不了 调用Thread对象 ...

  2. JavaSE | 多线程

    一.实现线程的方式一:继承java.lang.Thread类 1.步骤 (1)编写线程类,继承java.lang.Thread类 (2)重写public void run(){} 在run()的方法体 ...

  3. 22.11.20补卡 javaSE多线程学习笔记

    自用 并发编程 多个任务同时执行 并发原理: CPU分时间片交替执行, 宏观并行, 微观串行; 由OS调度 进程: OS中并发的一个任务 线程: 在一个进程中,并发的一个顺序执行流程 每当执行新的进程 ...

  4. [javaSE] 多线程(join方法)

    多条线程并发执行,随机切换,调用join()方法,会使当前线程所在的线程(一般主线程)冻结,直到当前线程结束,所在的线程才恢复继续执行 class JoinTestDemo implements Ru ...

  5. 视频专辑:JAVA语言入门视频教程

    为什么80%的码农都做不了架构师?>>>    专辑:JAVA语言入门视频教程 简介:该视频专辑是java的入门教程,适合初学者学习java,讲的也非常容易懂,希望能给想学习java ...

  6. android 精灵图的使用方法,css sprites(精灵图)如何使用?

    CSS Sprites是一种性能优化技术,一种网页图片应用处理方式:将多个图像组合成单个图像文件以在网站上使用的方法,以提高性能:也被称为css 精灵图. 网页通常包含多个图像.这些包括图标,按钮,徽 ...

  7. 互联网java工程师面试突击第三季知识点总结

    目录 Java集合包 01. HashMap的底层数据结构是什么? 02. JDK1.8中对hash算法和寻址算法是如何优化的? 03.HashMap是如何解决hash碰撞问题的? 04.说说Hash ...

  8. JavsSE内容全链接

    JavsSE内容全链接 JavaSE语言概述 JavaSE基本语法上 JavaSE基本语法下 JavaSE数组 JavaSE面向对象编程上 JavaSE面向对象编程中 JavaSE面向对象编程下 Ja ...

  9. 【备战校招】阅文集团Java实习生笔试题

    好久没学习后端知识了(大概4天吧.)最近状态比较低迷,除了开始沉迷游戏,还要复习准备本专业的考试.通过一段时间的努力,我突然才意识太高估自己的自控能力了,原来坚持是一件那么难的事情,然而,我没有退路了 ...

最新文章

  1. android app功能 配置,配置安装时分发  |  Android 开发者  |  Android Developers
  2. ipad/iphone启动界面Default.png
  3. div固定在浏览器顶部_手写几种常见的css布局,1垂直两栏左边固定右边自适应,垂直3栏左右固定中间自适应...
  4. PinyinUtil
  5. 【渝粤题库】国家开放大学2021春2109刑事诉讼法学题目
  6. mybatis 映射成多个list_SSM:Mybatis架构与原理
  7. 无罪的罪人_探索敏捷和无罪的文化
  8. Shrink space合并表的碎片
  9. 销售灵魂人物的潜伏笔记5
  10. 苹果手机来电归属地_Python批量查询手机号码归属地
  11. 如何成为一名优秀的测试/开发程序员?专注谋定而后动......
  12. java基础入门习题答案_传智播客-Java基础入门习题答案
  13. android控件显示在最上层,「总是可见的时钟和备忘录」永远显示在屏幕最上层的贴心助手(Android)...
  14. 微信小程序如何实现上拉刷新(即分页加载数据)?
  15. 信奥一本通2069(糖果问题)配解析
  16. Springboot实现浏览器下载文件
  17. WSF操作系统抽象层学习笔记 (一) ---简介和内存管理
  18. 产业区块链:像投资股票一样投资数字资产
  19. .net下进行文件下载
  20. Plant Simulation软件RGV应用

热门文章

  1. 壹度同城新零售系统v4.1.23 社交电商 同城商城
  2. c++实现简易trpg角色生成器
  3. css中鼠标手,css各种鼠标手型集合
  4. 域名邮箱什么,如何开通自定义邮箱后缀的邮箱?
  5. 企业微信登录报错:应用程序无法正常启动(0xc0000142);Win10应用程序无法正常启动0xc0000142错误的解决方法
  6. WP7 SDK模拟器对应PC键盘的功能键
  7. 搭建简单windows版NAS
  8. 解决aspx页面中关键词(keywords)和描述(descript)不显示问题
  9. cad图片怎么转换成pdf格式
  10. mac硬盘故障升级系统_硬件升级:如何安装新硬盘,第2页,故障排除