欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java之多线程

程序员文章站 2023-12-23 15:31:34
...

什么是线程

线程是进程的组成部分,线程可以拥有自己的堆栈,自己的程序计数器及自己的局部变量,但是线程不能拥有系统资源,它与其父进程的其他线程共享进程中的全部资源。
一个进程中可以包含多个线程,但是至少要包含一个线程,即主线程

特点

  1. 同一进程下的不同线程的调度不由程序控制
  2. 线程独享自己的堆栈程序计数器和局部变量
  3. 两个线程将并发执行(本质上是宏观上并行、微观上串行)

进程和线程的区别

  1. 线程是进程的执行单元,线程是操作系统能够进行运算调度的最小单元
  2. 线程和进程的关系是“多对一”
  3. 线程可以独享自己的堆栈、程序计数器和局部 变量;但线程必须与其父进程的其他线程共享代码段、数据段、堆空间等系统资源。

实现线程的三种方法

Thread( )类及其派生类

  1. 使用Thread类或者使用一个派生自Thread类的类构建一个线程
  2. 继承Thread的子类在实例时,使用了上溯造型
  3. 需要new一个线程对象,在调用该对象的start()方法
public class thread {
	
public static void main(String[] args) {
		Thread t= new Child();
		t.start();
	}
}
class Child extends Thread{
	public void run() {
		System.out.print("I am running");
	}
}

实现Runnable接口

  1. 执行时也要new一个线程对象,但是需要进行参数传入,传入的的是实现了Runnable接口的实例对象,以构建线程对象
  2. 推荐使用实现Runnable接口的方法来进行多线程编程
    因为java不支持多继承,如果一个子类继承了Thread类,就不能继承其他类了,使用实现Runnable接口的类,既可以实现一个线程的功能, 又可以更好的复用其他类的属性和方法。
public class thread {
	
public static void main(String[] args) {
		Child child = new Child();
		Thread thrd = new Thread(child);
		thrd.start();
	}
}
class Child implements Runnable{
	public void run() {
		System.out.print("I am running");
	}
}

实现Callable接口

Callable的定义

public interfacec Callable<V> {
  
  V call() throws Exception;
}

1.Callable是一个泛型接口,call()方法类似run()方法,是线程所关联的执行代码,但是call()方法具有返回值,如果call()得不到返回值就会抛出一个异常,Runnable的run()方法不会抛出异常
2.获得call()的返回值,可以通过Future接口来获取。Future接口的get方法用于获取call()的返回值,它会发生阻塞,知道call()返回结果。

public class thread {
	public static void main(String[] args) {
		Child<String> child = new Child<String>();
//FutureTask本身实现了Future和Runnable接口,既可以把FutureTask的实例传入Thread中,在一个新的线程中
执行,同时又可以从FutureTask中通过get方法获取到任务的返回结果
		FutureTask<String> future = new FutureTask<String>(child);
		Thread t = new Thread(future);
		t.start();
		try {
			System.out.print(future.get());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

class Child<Srring> implements Callable<String> {
	@Override
	public String call() throws Exception {
		return "I am calling";
	}
}

线程的状态

线程有5个状态:新建、就绪、运行、死亡、阻塞

新建(new)

  1. new一个线程时,该线程处于新建状态
  2. 此时线程是一个在堆中分配了内容的静态的对象

就绪(Runnable)

  1. 调用start( )方法后,处于就绪状态
  2. JVM会为该线程创建方法调用栈和程序计数器
  3. 线程处于运行池中,等待JVM的调度

运行(Running)

  1. 处于就绪状态的线程获得CPU开始运行run方法,进入运行状态
  2. 对于抢占式策略的操作系统,系统会为每个可执行的线程分配一个时间片。
    当时间片用尽后系统会剥夺该线程所占有的处理器资源,从而让其他线程获得占有CPU的机会,
    此时线程从运行态转为就绪态,继续等待调度

阻塞(Blocked)

遇到如下情况,线程进入阻塞状态

  1. 调用sleep、join方法
  2. 调用一个阻塞式IO方法
  3. 试图获得一个同步监视器,但是该监视器正在被其他线程持有
  4. 等待某个notify通知
  5. 程序调用了suspend方法将该线程挂起

死亡(Dead)

遇到如下情况,程序进入死亡状态

  1. run方法执行完
  2. 抛出一个异常或者错误,而未被捕获
  3. 调用stop方法

线程的控制

join

A调用B(处于就绪)的join方法,A被阻塞,B进行运行,B执行完后才执行A

join( );
join(long millis);//最长等待millis毫秒
join(long millis,int nanos;//最长等待millis毫秒加上nanos微秒

sleep

A调用sleep方法,A进入阻塞状态

sleep(long millis);
sleep(long millis,long nanos);

yield

A调用yield方法,A暂时放弃CPU,A不会被阻塞,而是进入就绪状态,等待调度

优先级

  1. 默认下,线程的优先级和创建该线程的父线程的优先级相等
  2. priority的取值范围1~10
  3. 三个静态常量
    MAX_PRIORITY:最高优先级,值为10
    MIN_PRIORITY:最低优先级,值为1
    NORM_PRIORITY:普通优先级,值为5
setPriority(int priority);//设置优先级
getPriority();//返回优先级

终止线程

不建议使用stop和suspend

  1. 一旦调用stop方法,工作线程将派出一个ThreadDeath的异常,会导致run方法结束运行,结束点不可控
  2. 会释放掉线程所持有的锁,加锁的目的是保持数据的一致性,一旦停止,被保护的数据可能会出现不一致的情况
    其他线程获得同步锁后可能进入临界区使用这些被破坏的数据,造成奇怪错误产生
  3. 使用suspend,可能会产生死锁
    调用suspend()方法,虽然线程被阻塞,但是锁并没有被释放,在等待恢复的过程中,如果能够恢复这个被suspend的线程的线程需要访问被该线程锁定的临界资源,那便会产生死锁。

终止线程的方法

使用退出标志

当线程处于运行状态时,设置退出标志的方法安全结束该线程

public class thread {
	public static volatile boolean exit = false;

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				while (!exit) {//死循环
					System.out.println("1");
				}
				System.out.println("Exit");
			};
		}.start();
		new Thread() {
			public void run() {
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				exit = true;
			};
		}.start();
	}
}

使用interrupt方法

当一个线程被阻塞而无法正常运行时,使用interrupt()方法,可以使得阻塞的线程抛出一个异常使其退出阻塞状态,并catch住该异常从而安全结束该线程。

public class thread {
	public static void main(String[] args) throws InterruptedException {
		Thread thr = new Thread() {
			public void run() {
				System.out.println("Thread is running!");
				try {
					Thread.sleep(1000*10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		thr.start();
		Thread.sleep(1000);
		thr.interrupt();
	}
	}

线程的同步(三种)

同步代码块

  1. 参数obj是一个引用类型的对象,也就是同步监视器,可以是任何引用类型的对象,即Object类的对象
  2. 任何时刻只能有一条线程获得对同步监视器的锁定
  3. 代码块执行完,释放同步监视器的锁定
synchronized(ob){
	//需要同步的代码块
}

同步方法

  1. 使用关键字synchronized来修饰某个方法
  2. 同步方法中默认的同步监视器是this,该对象本身

同步锁

  1. 使用同步锁机制对临界资源保护时需要定义一个Lock类型的对象
  2. 通常使用Lock类的子类ReentrantLock(可重入锁)的对象来进行加锁和解锁的操作
private final ReentranLock lock = new ReentrantLock();
lock.lock(); //加锁
try{
	//访问临界资源
}
finally{
	lock.unlock();//释放锁
}

synchronized和Lock的区别

  1. 锁机制不同
  2. 用法不同
  3. 性能不同
Lock的功能更加强大,Lock能完成synchronized实现的所有功能

守护线程(Daemon Thread)

  1. 守护线程又称后台线程或服务线程,主要用来后台为用户线程提供服务
  2. 守护线程依赖于用户线程的执行状态。当一个程序中的所有用户线程执行完后JVM会退出,此时无论守护线程是否执行完都会结束掉。相反,如果还存在未执行完的用户线程,则JVM不会退出,守护线程也会继续执行
  3. JVM中的GC(垃圾回收器),就是一个经典的守护线程。当JVM运行时,GC会一直运行, 实时监控和管理着系统中可被回收的资源。
Thread t = new Thread();
t.setDaemon(true);
t.start();

线程的协调机制

  1. wait( )和notify( )方法
  2. await( )和signal( )方法
  3. BlockingQueue阻塞队列方法
  4. PipedInputStream/PipedOutputStream方法
相关标签: java

上一篇:

下一篇: