JavaSE 多线程

多任务

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

多线程

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

普通方法的调用时一条路径,比如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;
    @Override
    public 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;
	}

	@Override
	public 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() {    //匿名内部类
            @Override
            public 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;  //票数
	@Override
	public 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;//胜利者
	@Override
	public 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;//胜利者
	@Override
	public 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{
	// 线程入口点
	@Override
	public 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;
	}
	@Override
	public void marry() {
		System.out.println(manname+"和"+wifename+"结婚了");
	}
	
}
class WeddingCompany implements Marry{  //代理角色
	private Marry target;     	//真实角色
	public WeddingCompany(Marry target) {
			this.target = target;
	}
	@Override
	public 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() {
         	@Override
         	public 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;
    }
    @Override
    public 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{
	@Override
	public 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;
            @Override
            public 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;
            @Override
            public 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;
            @Override
            public 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;
            @Override
            public 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;
            @Override
            public 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;
            @Override
            public 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() {
            @Override
            public 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;
            @Override
            public 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);  //NEW
		t.start(); 
		state = t.getState();   //RUNNABLE
		System.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_WAITING
			System.out.println(state);
		}		
		state = t.getState();   //TERMINATED
		System.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==2
				break;
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			state = t.getState();   //TIMED_WAITING
			System.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{
	@Override
	public void run() {
		for(int i=1;i<=5;i++) {
			System.out.println("do thing");
		}
		System.out.println("结束");
	}
}
class Record implements Runnable{
	@Override
	public 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;
	}
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+"-->"+name);
	}
}
输出结果:
true
代理角色-->真实角色
false

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

多线程并发

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值