当前位置:   article > 正文

java多线程基本概念_java多线程的概念

java多线程的概念


1.什么是线程以及多线程与进程的区别

在现代操作在运行一个程序时,会为其创建一个进程。例如启动一个QQ程序,操作系统就会为其创建一个进程。而操作系统中调度的最小单位元是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。因此我们可以这样理解:
进程: 正在运行的程序,是系统进行资源分配和调用的独立单位。 每一个进程都有它自己的内存空间和系统资源。
线程: 是进程中的单个顺序控制流,是一条执行路径 一个进程如果只有一条执行路径,则称为单线程程序。 一个进程如果有多条执行路径,则称为多线程程序。

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • main()称之为主线程,为系统的入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的。
  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制;
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销;
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;

java真的可以开启线程吗?
开不了,本地方法 底层的 C++ , Java 无法直接操作硬件,因为它是运行在虚拟机之上的
在这里插入图片描述

2.多线程的创建与启动

注意:
线程开启不一定立即执行,由cpu调度执行

2.1 多线程的创建

创建多线程有三种方法,一种是继承Thread类重写run方法,另一种是实现Runnable接口重写run方法。还有一种是实现Callable接口,下面我们分别给出代码示例,
继承Thread类重写run方法:

package com.test;
/**
 1. @decrition 继承Thread实现线程
 */
public class ThreadByEx extends Thread{
	/**
	 * 重写run方法
	 */
	@Override
	public void run() {
		System.out.println("I'm a thread that extends Thread!");
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

实现Runnable接口重写run方法:

package com.test;
/**
 2. @decrition 实现Runnable接口重写run方法
 */
public class ThreadByRunnable implements Runnable{
	/**
	 * 实现run方法
	 */
	@Override
	public void run() {
		System.out.println("I'm a thread that implements Runnable !");
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

实现 Callable 接口

  1. 实现 Callable 接口,需要返回值类型
  2. 重写 call 方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行:Future< Boolean > result1 = ser.submit(1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务:ser.shutdownNow();

2.2 多线程启动

怎么启动线程?

package com.test;
public class MainTest {
	
	public static void main(String[] args) {
		//继承Thread启动的方法
		ThreadByEx t1=new ThreadByEx();
		t1.start();//启动线程
		
		
		//实现Runnable启动线程的方法
		ThreadByRunnable r = new ThreadByRunnable();
		Thread t2 =new Thread(r);
		t2.start();//启动线程
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

运行结果:

I'm a thread that extends Thread!
I'm a thread that implements Runnable !
  • 1
  • 2

代码相当简单,不过多解释。这里有点需要注意的是调用start()方法后并不是是立即的执行多线程的代码,而是使该线程变为可运行态,什么时候运行多线程代码是由操作系统决定的。

2.3 扩展

实现静态代理对比 Thread

new Thread( ()-> System.out.println(“11111”) ).start() ;
new WeddingCompany(new You())).HappyMarry() ;

静态代理模式总结:
真实对象和代理对象都要实现同一接口
代理对象要代理真实角色


好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事情

lambda 表达式

为什么要用 lambda表达式?

  • 避免匿名内部类定义过多
  • 可以让你的代码看起来很简洁
  • 去掉了一堆没有意义的代码,只留下核心的逻辑
  • λ希腊字母表中排序第十一位的字母,英语名称为 Lambda
  • 避免匿名内部类定义过多
  • 其实质属于函数式编程的概念
	(params) -> expression [表达式]
	(params) -> statement [语句]
	(params) -> {statements}

	a-> System.out.println("i like lambda ---->" +a);
  • 1
  • 2
  • 3
  • 4
  • 5

函数式接口
理解 Functional Interface(函数式接口) 是学习 Java8 lambda表达式的关键所在

函数式接口的定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
public interface Runnable{
	public abstract void run();
}
  • 1
  • 2
  • 3
  • 对于函数式接口,我们可以通过 lambda 表达式来创建该接口的对象。

总结
lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹;
前提是接口为函数式接口
多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。

3.中断线程和守护线程以及线程优先级

3.1中断线程

我们先来看看中断线程是什么?(该解释来自java核心技术一书,我对其进行稍微简化), 当线程的run()方法执行方法体中的最后一条语句后,并经由执行return语句返回时,或者出现在方法中没有捕获的异常时线程将终止。在java早期版本中有一个stop方法,其他线程可以调用它终止线程,但是这个方法现在已经被弃用了,因为这个方法会造成一些线程不安全的问题。我们可以把中断理解为一个标识位的属性,它表示一个运行中的线程是否被其他线程进行了中断操作,而中断就好比其他线程对该线程打可个招呼,其他线程通过调用该线程的 interrupt方法对其进行中断操作,当一个线程调用interrupt方法时,线程的中断状态(标识位)将被置位(改变),这是每个线程都具有的boolean标志,每个线程都应该不时的检查这个标志,来判断线程是否被中断。而要判断线程是否被中断,我们可以使用如下代码

Thread.currentThread().isInterrupted()
  • 1
while(!Thread.currentThread().isInterrupted()){
    do something
}
  • 1
  • 2
  • 3

但是如果此时线程处于阻塞状态(sleep或者wait),就无法检查中断状态,此时会抛出 InterruptedException异常。如果每次迭代之后都调用sleep方法(或者其他可中断的方法),isInterrupted检测就没必要也没用处了,如果在中断状态被置位时调用sleep方法,它不会休眠反而会清除这一休眠状态并抛出InterruptedException。所以如果在循环中调用sleep,不要去检测中断状态,只需捕获InterruptedException。代码范例如下:

public void run(){
		while(more work to do ){
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				//thread was interrupted during sleep
				e.printStackTrace();
			}finally{
				//clean up , if required
			}
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

同时还有点要注意的就是我们在捉中断异常时尽量按如下形式处理,不要留空白什么都不处理!
不妥的处理方式:

void myTask(){
    ...
   try{
       sleep(50)
      }catch(InterruptedException e){
   ...
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

正确的处理方式:

void myTask()throw InterruptedException{
    sleep(50)
}
  • 1
  • 2
  • 3

或者

void myTask(){
    ...
    try{
    sleep(50)
    }catch(InterruptedException e){
     Thread.currentThread().interrupt();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

最后关于中断线程,我们这里给出中断线程的一些主要方法:
void interrupt():向线程发送中断请求,线程的中断状态将会被设置为true,如果当前线程被一个sleep调用阻塞,那么将会抛出interrupedException异常。
static boolean interrupted():测试当前线程(当前正在执行命令的这个线程)是否被中断。注意这是个静态方法,调用这个方法会产生一个副作用那就是它会将当前线程的中断状态重置为false。
boolean isInterrupted():判断线程是否被中断,这个方法的调用不会产生副作用即不改变线程的当前中断状态。
static Thread currentThread() : 返回代表当前执行线程的Thread对象。

3.2守护线程

首先我们可以通过t.setDaemon(true)的方法将线程转化为守护线程。而守护线程的唯一作用就是为其他线程提供服务。计时线程就是一个典型的例子,它定时地发送“计时器滴答”信号告诉其他线程去执行某项任务。当只剩下守护线程时,虚拟机就退出了,因为如果只剩下守护线程,程序就没有必要执行了。另外JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。最后还有一点需要特别注意的是在java虚拟机退出时Daemon线程中的finally代码块并不一定会执行哦

  • 线程分为 用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如 后台记录操作日志,监控内存,垃圾回收等。

代码示例:

package com.test;
/**
  * @decrition 守护线程代码示例
 */
public class Demon {
	public static void main(String[] args) {
		Thread deamon = new Thread(new DaemonRunner(),"DaemonRunner");
		//设置为守护线程
		deamon.setDaemon(true);
		deamon.start();//启动线程
	}
	
	
	static class DaemonRunner implements Runnable{
		@Override
		public void run() {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally{
				System.out.println("这里的代码在java虚拟机退出时并不一定会执行哦!");
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

因此在构建Daemon线程时,不能依靠finally代码块中的内容来确保执行关闭或清理资源的逻辑。

public class TestDaemon{
	God god = new God();
	You you = new You();
	
	Thread thread = new Thread(god);
	thread.setDaemon(true);  //默认是 false 表示用户线程,正常的线程都是用户线程....
	thread.start(); //  上帝守护线程启动

	new Thread(you).start();  // 你用户线程启动
	
}

class God implements Runnable{
	@Override
	public void run(){
		while(true){
			System.out.println("上帝保佑着你")
		}
	}
}

class You implements Runnable{
	@Override
	public void run(){
		for(int i = 0; i < 36500; i++){
			System.out.println("哈哈哈");
		}
		System.out.println("====goodbye word===="); // Hello World
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3.3线程优先级

在现代操作系统中基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下一次分配。线程分配到的时间片多少也决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。在java线程中,通过一个整型的成员变量 Priority来控制线程优先级 ,每一个线程有一个优先级,默认情况下,一个线程继承它父类的优先级。可以用setPriority方法提高或降低任何一个线程优先级。可以将优先级设置在MIN_PRIORITY(在Thread类定义为1)与MAX_PRIORITY(在Thread类定义为10)之间的任何值。线程的默认优先级为NORM_PRIORITY(在Thread类定义为5)。 尽量不要依赖优先级,如果确实要用,应该避免初学者常犯的一个错误。如果有几个高优先级的线程没有进入非活动状态,低优先级线程可能永远也不能执行。每当调度器决定运行一个新线程时,首先会在具有高优先级的线程中进行选择,尽管这样会使低优先级的线程可能永远不会被执行到。因此我们在设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高的优先级,而偏重计算(需要较多CPU时间或者运算)的线程则设置较低的优先级,这样才能确保处理器不会被长久独占。当然还有要注意就是在不同的JVM以及操作系统上线程的规划存在差异,有些操作系统甚至会忽略对线程优先级的设定,如mac os系统或者Ubuntu系统…

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从1-10。
    - Thread.MIN_PRIORITY = 1;
    - Thread.MAX_PRIORITY = 10;
    - Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
    getPriority().setPriority(int xxx)
    
    • 1
    注意:优先级的设定建议在start()调度前,优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
    public class TestPriority{
     	public static void main(String[] args){
     	// 主线程优先级
     	System.out.println(Thread.currentThread().getName+"--->"+Thread.currentThread().getPriority());
     	
     	MyPriority myPriority = new MyPriority();
       
        Thread t1 = new Thread(myPriority );
        Thread t2 = new Thread(myPriority );
        Thread t3 = new Thread(myPriority );
        Thread t4 = new Thread(myPriority );
        Thread t5 = new Thread(myPriority );
        Thread t6 = new Thread(myPriority );
     	
     	// 先设置优先级,再启动
     	t1.start();
     	
     	t2.setPriority(1);
     	t2.start();
     
     	t3.setPriority(4);
     	t3.start();
     
        t4.setPriority(Thread.MAX_PRIORITY);  //  MAX_PRIORITY = 10
        t4.start(); 
    
     	t5.setPriority(8);
     	t5.start();
     
     	t6.setPriority(7);
     	t6.start();
     	}
     }
     
     class MyPriority implements Runnable{
     	public void run(){
     		System.out.println(Thread.currentThread().getName+"--->"+Thread.currentThread().getPriority());
     	}
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

3.4线程的状态转化关系

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

等待阻塞(WAITING):运行的线程执行wait()方法,JVM会把该线程放入等待池中。

同步阻塞(Blocked):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

超时阻塞(TIME_WAITING):运行的线程执行sleep(long)或join(long)方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。

  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

在这里插入图片描述
在这里插入图片描述
图中的方法解析如下:
Thread.sleep():在指定时间内让当前正在执行的线程暂停执行,但不会释放"锁标志"。不推荐使用。
Thread.sleep(long):使当前线程进入阻塞状态,在指定时间内不会执行。
Object.wait()和 Object.wait(long) :在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常, waite()和notify()必须在synchronized函数或synchronized中进行调用。如果在non-synchronized函数或non-synchronized中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
Object.notifyAll():则从对象等待池中唤醒所有等待等待线程
Object.notify():则从对象等待池中唤醒其中一个线程。
Thread.yield():方法 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会。
Thread.Join():等待该线程终止。把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
boolean isAlive() :测试线程是否处于活动状态
setPriority(int newPriority): 更改线程的优先级

3.4.1 线程停止

  • 不推荐使用JDK提供的 stop()、destroy()方法【已废弃】
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量 当 flag=false,则终止线程运行。
    public class TestStop implements Runable{
      
      private boolean flag = true;
      public void run(){
      	int i = 0;
      	while (flag){
      		Systeml.out.println("run.....Thread"+i++);
      	}
      }
      public  void stop(){	
      	this.flag = false;
      }
      public  static  void main(String[] args){
      	TestStop testStop = new TestStop();
      	new Thread(testStop).start();
      	
      	for(int i=0;i<1000;i++){
      		System.out.pringln("main"+i);
      		if(i==900){
      			// 调用 stop 方法 切换标志位,让线程停止
      			testStop.stop();
      			System.out.println("线程该停止了");
      		}
      	}
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

3.4.2 线程休眠

  • sleep(时间) 指定当前线程阻塞的毫秒数;
  • sleep 存在异常 InterruptedException;
  • sleep 时间达到后线程进入就绪状态;
  • sleep可以模拟网络延时,倒计时等;
  • 每一个对象都有一个锁,sleep不会释放锁;

3.4.3 线程礼让_yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功,看cpu心情
public class TestYield{
	public static void main(String[] args){
		MyYield myYield = new MyYield();

		new Thread(myYield,"a").start();
		new Thread(myYield,"b").start();
	}
}

class MyYield implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread().getName()+"线程开始执行");
		Thread.yield();  // 礼让
		System.out.println(Thread.currentThread().getName()+"线程停止执行");
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3.4.4 线程强制执行_join

  • Join合并线程,待此线程执行完成后,再执行其他的线程,其他线程阻塞
  • 可以想象成插队
public class TestJoin implements Runnable{
	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("线程vip来了"+i);
		}
	}

	public static void mian(String[] args) throws InterruptedException{
		TestJoin testJoin = new TestJoin();
		Thread thread = new Thread(testJoin);
		thread.start();

		for(int i=0;i<500;i++){
			if(i==200){
				thread.join(); //插队
			}
			System.out.println("main" +i);
		}
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.4.5 线程状态观测

Thread.State
线程状态。线程可以处于以下状态之一:

  • New
    尚未启动的线程处于此状态
  • Runnable
    在Java虚拟机中执行的线程处于此状态
  • Blocked
    被阻塞等待监视器锁定的线程处于此状态
  • Watting
    正在等待另一个线程执行特定动作的线程处于此状态
  • Timed_Waitting
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • Terminated
    已退出的线程处于此状态
public class TestState{
	
	public static void main(String[] args){
		Thread thread = new Thread(()->{
			for(int i=0;i<5;i++){
				try{
					Thread.sleep(1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
			System.out.println("//");
		});

		// 观察状态
		Thread.State state = thread.getState();
		System.out.println(state);  //New
		
		// 观察启动后
		thread.start();  // 启动线程
		state = thread.getState();
		System.out.println(state);  // Run

       while(state != Thread.State.TERMINATED){ //只要线程不终止,就一直输出状态
			Thread.sleep(100);
			state = thread.getState(); // 更新线程状态
			System.out.println(state); // 输出状态
		}
	}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

3.4.6 wait / sleep 区别

  1. 来自不同的类

    wait => Object
    sleep => Thread

  2. 关于锁的释放
    wait 会释放锁, sleep 睡觉了,抱着锁睡觉,不会释放。

  3. 使用范围不同
    wait 必须在同步代码块中,sleep可以在任何地方睡

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/822688
推荐阅读
相关标签
  

闽ICP备14008679号