如何创建线程及线程特性与常用API

本文详细介绍了Java中多线程的创建方式,包括继承Thread类、实现Runnable接口和使用Callable与Future。讲解了线程的生命周期、线程优先级、中断以及常用API如ThreadFactory、CountDownLatch、CyclicBarrier、Semaphore和Exchanger。强调了线程状态转换、中断机制和线程同步的重要性,并提供了相关示例代码。
摘要由CSDN通过智能技术生成

继承Thread类

public class ExtendsThreadTest extends Thread {

	@Override
	public void run() {
		System.out.println("我是新开的线程:" + Thread.currentThread().getName());
		// TODO 执行其他业务
	}

	public static void main(String[] args) {
		
		ExtendsThreadTest test = new ExtendsThreadTest();
		Thread t1 = new Thread(test);
		t1.start();
		System.out.println("我是主线程:" + Thread.currentThread().getName());
		// TODO 执行其他主业务
	}
	
}

运行结果可以看到,2个不同的线程并行执行:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_19,color_FFFFFF,t_70,g_se,x_16

实现Runnable接口

public class ImplementsRunnableTest implements Runnable {

	@Override
	public void run() {
		System.out.println("我是新开的线程:" + Thread.currentThread().getName());
		// TODO 执行其他业务
	}
	
	public static void main(String[] args) {
		ImplementsRunnableTest test = new ImplementsRunnableTest();
		Thread t1 = new Thread(test);
		t1.start();
		System.out.println("我是主线程:" + Thread.currentThread().getName());
		// TODO 执行其他主业务
	}

}

 运行结果可以看到,2个不同的线程并行执行:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_19,color_FFFFFF,t_70,g_se,x_16

Callable与Future

可以从上面看到,run()方法是没有返回值的,当我们需要新开的线程有返回值的时候,就可以用Callable接口去实现。

Future接口是用于获取Callable计算结果的,同时由于Thread类不能直接处理Callable接口。其中Future有一个实现类FureTask,它同时实现了Futrue接口和Runnable接口,可以很便利的处理Callable和Future,所以Callable和FureTask总是成对的出现。

public class CallableTest implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		System.out.println("我是新开的线程:" + Thread.currentThread().getName());
		// TODO 执行其他业务
		// 返回新开线程的执行结果
		return 1;
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		CallableTest test = new CallableTest();
		FutureTask<Integer> task = new FutureTask<Integer>(test);
		Thread t1 = new Thread(task);
		t1.start();
		System.out.println("我是主线程:" + Thread.currentThread().getName());
		System.out.println("新开线程执行结果:" + task.get());
		// TODO 执行其他主业务
	}
	
}

运行结果可以看到,2个不同的线程并行执行,同时得到了新开线程的运行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

Futrue中有以下常用方法:

V get():获取Callable运行结果,该方法会阻塞当前方法所在的线程,直到获取到结果;

V get(long timeout, TimeUnit unit):获取Callable运行结果。与get()的区别在于可以设置一个超时时间,如果在指定超时时间内没有获取到结果,则抛出一个TimeoutException异常;

boolean isCancelled():如果Callable任务在完成前被取消,则返回true;

boolean isDone():如果Callable任务结束,无论是正常结束还是中途取消或异常结束,都返回true。

改造一下代码得到一下结果:

public class CallableTest implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		System.out.println("我是新开的线程:" + Thread.currentThread().getName());
		// TODO 执行其他业务
		// 延迟3s
		Thread.sleep(3000);
		// 返回新开线程的执行结果
		return 1;
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		CallableTest test = new CallableTest();
		FutureTask<Integer> task = new FutureTask<Integer>(test);
		Thread t1 = new Thread(task);
		t1.start();
		System.out.println("新开线程是否取消:" + task.isCancelled());
		System.out.println("新开线程是否结束:" + task.isDone());
		System.out.println("新开线程执行结果:" + task.get());
		System.out.println("我是主线程:" + Thread.currentThread().getName());
		// TODO 执行其他主业务
	}
	
}

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

ThreadFactory接口

不管是上面提到的继承Thread,还是实现Runnable接口,又或是Callable与Future,在实际工程中都建议使用ThreadFactory去创建线程,方便工程统一管理,以及出现异常时的回溯。

代码示例:

/**
 * 在一个工程中,可能有很多不同的业务需要创建线程.
 * 建议:不同的业务创建不同的线程工厂
 * */
public class UserThreadFactory implements ThreadFactory {

	// 线程组命名标识
	private final String namePrefix;
	// 线程编号
	private final AtomicInteger nextId = new AtomicInteger(1);

	// 定义线程组名称,在 jstack 问题排查时,非常有帮助
	UserThreadFactory(String whatFeaturOfGroup) {
		namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-";
	}

	@Override
	public Thread newThread(Runnable task) {
		String name = namePrefix + nextId.getAndIncrement();
		Thread thread = new Thread(task, name);
		return thread;
	}

	public static void main(String[] args) {
		ThreadFactory threadFactory = new UserThreadFactory("123");
		Runnable r = () -> {
			// TODO --run()方法-- 线程业务
		};
		for(int i=0; i<3; i++) {
			Thread newThread = threadFactory.newThread(r);
			System.out.println("新创建的线程名称:" + newThread.getName());
		}
	}

}

运行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

可以看到,创建的了3个不同的线程,每个线程都根据不同的业务组,都有专属的命名,既方便管理,也方便异常回溯。

创建过程总结

1.不管是继承Thread,还是实现Runnable,Callable接口,点进源码可以发现,最终执行的都是run()方法,FutureTask只是利用缓存将run()方法执行结果作为返回值返回,作用到Callable接口上;

2.非常建议用ThreadFactory去管理线程组;

3.Thread.currentThread()可以返回当前线程对象,可以很方便的在任何地方调用Thread类的非静态方法;

4.不要直接调用run()方法,否则将不会启动新线程;

5.对于run()方法是不能抛出受检查异常的,但是不受检查异常会中断线程执行;

6.Runnable和Callable都是一个函数式接口,可以用lambda表达式创建一个实例:

Runnable r = () -> {
    // 这里就是run方法
	System.out.println("我是新开的线程:" + Thread.currentThread().getName());
	// TODO 执行其他业务
};
Thread t = new Thread(r);
t.start();
// TODO 其他业务代码

注:从java1.8开始,函数式接口都有@FunctionalInterface注解标记。该接口没有任何实际意义,仅用于编译器检测,该注解只能标记在"有且仅有一个抽象方法"的接口上,对于符合函数式标准的接口,加不加该注解都无所谓(为了规范,当然是要加上的好)。可以尝试创建一个用于2个或多个抽象方法的接口,用该注解去标记这个接口是会在编译器报错的。

线程的状态

在java代码中,Thread.state枚举定义了以下几种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,并且每种引发每种元素的方法都有解释。总结起来就是以下几种状态:

新建状态(NEW):当线程被创建,没有调用start()方法的时候;

可运行状态(RUNNABLE):调用start()方法之后,线程不会立即运行,需要获取到CPU资源之后才会运行(在windows平台可与线程优先级有关);当线程获取到CPU资源之后,线程开始运行(可定义为"运行中状态"),官方没有单独定义此状态的枚举,通过Thread类的getState()方法获取的状态也被义为RUNNABLE,其实该状态也是不存在的,被阻塞的线程重新唤醒之后都是处于"可运行状态",需要重新获取CPU资源;

阻塞状态(BLOCKED/WAITING/TIMED_WAITING):BLOCKED就是等待锁的阻塞,WAITING是需要被手动唤醒的阻塞,比如调用了wait()方法,TIMED_WAITING是带有自动结束时间的阻塞,比如调用了sleep(long millis)方法;

终止状态(TERMINATED):线程运行结束之后的状态(正常运行完毕或异常结束);

线程状态示例图如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

线程优先级

线程从"可运行状态"到真正开始运行,是需要获取CPU资源的,即优先获取资源的线程优先执行。可以调用Thread类的实例方法setPriority(int newPriority)为线程设置优先级,可设置[1,10]的范围,超过此范围会抛出IllegalArgumentException异常,如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

同时,Thread类提供了3个优先级常量,MAX_PRIORITY(10)、MIN_PRIORITY(1)和NORM_PRIORITY(5);

注:线程的优先级高度依赖于操作系统,windows平台中,java可以将优先级映射到系统中,但在linux中,线程的优先级会被忽略--所有线程具有相同的优先级;另外,在高优先级的线程未得到执行之前,低优先级的线程永远不会执行,要注意避免资源耗尽的情况。

线程中断

线程终止的标志:run()方法执行完毕,或经由return语句返回,或出现了没有捕获的异常,线程都将被终止。

目前没有任何可以强制中断线程的方法,在历史版本中,Thread类有个stop()方法可以中断线程,但已被废弃。因为这个操作是可以在不同的线程中进行,比如可以在A线程中去停止B线程,这个操作是及其危险的行为,因为A线程根本不能完全知道B线程的运行情况,首先容易造成死锁,其次容易破坏数据的一致性,比如在A到B的转账过程,扣减了A的账户,此时中断线程,如果这里忘记了回滚事务,A账户余额少了,但是B账户没有增加,此时就破坏了数据一致性。

刚刚说到stop()方法被抛弃,但是中断一个线程又显得尤为重要(比如在线程池ThreadPoolExecutor中,就大量使用interrupt去标记空闲线程),所以Thread类提供了以下操作线程中断的api:

void interrupt():向线程发出一个中断请求,并不是强行中断线程,同时将线程的中断状态置为true。

static boolean interrupted():(静态方法)判断当前线程的中断状态,同时将当前线程的中断状态取消(置为false)。

boolean isInterrupted():判断当前线程的中断状态,与static boolean interrupted()不同,不会改变当前线程的中断状态。

如果在开发中,仅仅只调用了void interrupt()方法,无非就是将当前线程的中断状态置为true,线程依然会持续运行,那么,到底如何理解这个线程中断?

有的人可能会认为,在常规编程中,需要停止线程的时候直接return或者抛出一个异常,那线程不就停止了吗?当然,这是在可控的情况下。interrupt被设计的初衷,是用来快捷的处理线程阻塞问题,一旦当前线程被标记为可中断状态,同时当前线程发生了阻塞(调用了sleep(),wait(),join()等方法),那么就会立即抛出一个InterruptedException异常来中断当前线程,同时,开发者可以很好的利用这个InterruptedException做出一些其他收尾的工作。

异常处理器

前面说到,在run()方法中,不能抛出受检查异常的,但是不受检查异常会中断线程执行。

因为运行期异常(不受检查异常),是不能确定什么时候出现的,为了更方便的追踪异常,Thread类提供了一个不受检查异常处理器:UncaughtExceptionHandler。

代码示例:

public class UncaughtExceptionHandlerTest {

	public static void main(String[] args) {
		Runnable r = () -> {
			// 模拟运行期异常
			throw new RuntimeException("测试异常");
		};
		Thread t = new Thread(r);
		// 创建一个异常处理器
		UncaughtExceptionHandler handler = new UncaughtExceptionHandler() {
			
			@Override
			public void uncaughtException(Thread t, Throwable e) {
				System.out.println("当前线程名称:"+t.getName());
				System.out.println(e instanceof RuntimeException);
				System.out.println("异常信息:" + e.getMessage());
				// TODO 其他处理业务:比如日志收集/系统报警等
			}
		};
		t.setUncaughtExceptionHandler(handler);
		t.start();
	}
}

从运行结果中可以看到,线程还是会因异常中断,但是console控制台没有输出异常栈信息,全部异常信息都由异常处理器处理:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

线程的休眠

Thread类提供的一个静态方法static void sleep(long millis),使当前线程立即进入休眠阻塞状态,在指定的毫秒结束后,当前线程会结束休眠。但是,结束休眠后的线程自动重新进入"可运行状态",获取到CPU资源后再开始运行。

注:Thread类同时还提供一个静态方法static void sleep(long millis, int nanos),nanos参数将休眠时间控制在纳秒级别,指定的休眠时间为millis+nanos;

线程的等待

Object类提供的一个实例方法wait(),使当前线程进入阻塞状态(状态枚举:Thread.State.WAITING);

该方法区别于sleep():

1、该方法只能限制持锁资源调用,否则会抛出异常:IllegalMonitorStateException(监视器非法状态异常,监视器指的是锁的监视器,即当前资源没有持有锁资源),同时被阻塞的资源会释放锁,进入等待池中等待被唤醒;

2、是可以被手动唤醒的,唤醒之后重新进入"可运行状态",获取到CPU资源后再开始运行。唤醒被wait()方法阻塞的线程,同样是由Object提供的实例方法:

void notify():从所有等待的线程中随机唤醒一个;

void notifyAll():唤醒所有等待的线程。

注:Object类同时还提供另外2个实例方法,wait(long timeout)和wait(long timeout, int nanos),和sleep类似,在指定时间结束后,结束阻塞状态,无需手动唤醒,也可以在阻塞结束前手动唤醒。

伪代码示例:

public class ThreadWaitTest {

	static ThreadWaitTest lockObj = new ThreadWaitTest();
	
	static boolean flag = false;
	
	public static void main(String[] args) {
		
		Runnable r = () -> {
			synchronized(lockObj) {
				Thread currentThread = Thread.currentThread();
				System.out.println(currentThread.getName() + ":开始标记");
				if(flag) {
					lockObj.notify();
					return;
				}
				try {
					// 让该线程阻塞,同时会释放锁
					lockObj.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(currentThread.getName() + ":结束标记");
			}
		};
		
		Thread t1 = new Thread(r);
		Thread t2 = new Thread(r);
		Thread t3 = new Thread(r);
		t1.start();
		t2.start();
		t3.start();
		
		long start = System.currentTimeMillis();
		while(true) {
			if(System.currentTimeMillis() - start >= 5000) {
				System.out.println(t1.getName() + ",线程状态:" +t1.getState());
				System.out.println(t2.getName() + ",线程状态:" +t2.getState());
				System.out.println(t3.getName() + ",线程状态:" +t3.getState());
				// 5s之后,创建一个新的线程,去获取相同的锁资源,同时调用notify()随机唤醒一个线程
				Thread t4 = new Thread(r);
				t4.start();
				flag = true;
				break;
			}
		}
	}
}

运行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

结果分析:

1、线程0,1,2都进入了运行状态,说明调用wait()方法之后释放了锁;

2、5s钟之后,线程的状态都是阻塞状态(Thread.State.WAITING);

3、5s钟之后创了一个新的线程去访问相同的锁资源(一定要去访问同一个锁资源),随机唤醒了线程0,同时可以发现程序并未结束,因为线程1,2,3都还处于阻塞状态。

线程的礼让

Thread类提供了一个静态方法yield();它表示给当前正处于运行中的线程一个提示,告知它可以将CPU资源让给其他线程执行,这仅仅是一个暗示,没有任何一种机制可以完全保证线程会礼让。它具有以下特点:

1、调用yield()方法之后,会让当前线程重新进入"可执行状态"(也就是说依旧处于Thread.State.RUNNABLE),需要重新获取CPU资源执行,这样就可以使具有相同优先级的线程优先获取CPU资源并执行的可能;

2、调用yield()方法并不会释放锁资源;

对于支持多任务操作的操作系统来讲,都会为线程自动分配CPU资源,所以几乎不需要调用yield()方法。

线程的加入

比如有两个线程A和B,当A在执行过程中,可以插入B线程,并等B线程执行完毕之后再接着执行A线程。

工程场景:一个统计账户的需求,在主线程中分别新开启2个线程,一个线程统计收入,一个线程统计支出,当2个线程都统计完成之后,回到主线程,由主线程将收入和支出数据按要求返回;实现代码如下:

public class JoinTest {

	public static void main(String[] args) {
		
		Runnable r1 = () -> {
			System.out.println("我是统计收入的线程.");
			try {
				// 模拟统计收入需要1s钟
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		};
		Thread t1 = new Thread(r1);
		t1.start();
		
		Runnable r2 = () -> {
			System.out.println("我是统计支出的线程.");
			try {
				// 模拟统计支持收入需要2s钟
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		};
		Thread t2 = new Thread(r2);
		t2.start();
		
		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("2个统计线程执行完毕之后,由主线程汇总");
	}
}

运行结果如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

注:调用join()方法会使用,该代码所在的线程处于阻塞状态(示例中主线程会处于阻塞状态),所以应尽量避免interrupt()方法的出现,除非业务需要你这么做。

同步器之CountDownLatch

CountDownLatch位于java.util.concurrent包下,用于灵活解决线程加入场景。

构造方法:public CountDownLatch(int count);需要强制声明参与计数的线程数;

void countDown():计数器,减少1个构造方法中声明的线程数量;

boolean await(long timeout, TimeUnit unit):当构造方法中声明的线程数量计数器为0时,立即回到方法所在的线程,标志着需要加入当前线程的其他所有线程全部执行完毕。如果在指定的时间内计数器不能为0,会立即回到主线程执行,但是其他线程并不会被强行中断,仍然会继续执行。所以在使用过程中,需要认真评估所有可能的场景,同时,调用此方法会让被加入的线程处于阻塞状态,也应尽量避免interrupt()方法的出现,除非业务需要你这么做。

示例代码:

public class CountDownLatchTest {

	public static void main(String[] args) {
		
		CountDownLatch cdl = new CountDownLatch(2);
		Runnable r1 = () -> {
			System.out.println("我是统计收入的线程.");
			try {
				// 模拟统计收入需要1s钟
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally {
				// 一定要确保 countDown 方法被执行到,避免主线程无法执行至await 方法,直到超时才返回结果
				cdl.countDown();
			}
			
		};
		Thread t1 = new Thread(r1);
		t1.start();
		
		Runnable r2 = () -> {
			System.out.println("我是统计支出的线程.");
			try {
				// 模拟统计支持收入需要2s钟
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally {
				cdl.countDown();
			}
		};
		Thread t2 = new Thread(r2);
		t2.start();
		
		try {
			cdl.await(5, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("2个统计线程执行完毕之后,由主线程汇总");
	}
}

运行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

注:一定要确保 countDown()方法被执行到,避免主线程无法执行至await 方法,直到超时才返回结果。

同步器之CyclicBarrier

CyclicBarrier位于java.util.concurrent包下,从字面意思,看得出来是环形计数器意思。从应用上讲,与CountDownLatch最大的区别就是CyclicBarrier可重复,CountDownLatch是一次性的,另外CountDownLatch是减法计数器,CyclicBarrier则是加法计数器。

代码示例:

public class CyclicBarrierTest {

	public static void main(String[] args) {
		
		CyclicBarrier cb = new CyclicBarrier(4, ()->{
			System.out.println("----所有线程干完一件事----");
		});
		
		Runnable r = () -> {
			Thread currentThread = Thread.currentThread();
			System.out.println(currentThread.getName() + "已到达工地");
			try {
				cb.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(currentThread.getName() + "开始搬砖");
			try {
				cb.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		};
		
		Thread t1 = new Thread(r);
		t1.start();
		Thread t2 = new Thread(r);
		t2.start();
		Thread t3 = new Thread(r);
		t3.start();
		
	}
}

执行结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWmVwYWw=,size_20,color_FFFFFF,t_70,g_se,x_16

从执行结果可以看到,调用await()方法之后,所有线程会等到一起之后,再接着往下执行。

就好比需要所有人都达到工地之后,再分别去干自己的事,相比较于CountDownLatch就不太适合汇总的应用场景。

CyclicBarrier提供个构造方法:

public CyclicBarrier(int parties):声明参与计数的线程数;

public CyclicBarrier(int parties, Runnable barrierAction):parties-声明参与计数的线程数,barrierAction-声明当线程计数器结束之后,需要执行的线程;

不管是Thread的join()方法,还是CountDownLatch,亦或是CyclicBarrier,点进源码可以发现(因能力有限,源码还不太敢深究),都是基于Object的wait()方法进行阻塞,join()是直接利用synchronized,CountdownLatch则是利用AbstractQueuedSynchronizer(AQS)去实现,CyclicBarrier则是利用ReentrantLock去实现。

所以,遇到不同的场景,需要尽可能的去选择最适合的实现方式。

同步器之Semaphore

Semaphore(信号量)位于java.util.concurrent包下,是用来控制同时访问特定资源的线程数量,通过协调各个线程以保证合理地使用公共资源。Semaphore通过使用计数器来控制对共享资源的访问,如果计数器大于0则允许访问,如果等于0则一直阻塞直到获取许可资源或超时放弃执行。

Semaphore有以下构造方法:

public Semaphore(int permits):指定初始许可数,即最大允许访问线程;

public Semaphore(int permits, boolean fair):指定许可书,fair指定当有竞争时,即当没有可用资源,剩余线程全部阻塞等待的时候,构建具有先进先出公平策略的公平锁;

semaphore有以下常用方法:

void acquire() throws InterruptedException:获取1个资源,如没有资源可用,一直阻塞直到获取资源;

void acquire(int permits) throws InterruptedException:获取指定数目的资源(等价于多次调用acquire()进行计数),如没有资源可用,一直阻塞直到获取资源;

boolean tryAcquire():从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞;

boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException:从信号量尝试获取一个许可,如果无可用许可,会阻塞到直至超时返回false;

boolean tryAcquire(int permits):尝试获取指定数目的许可,如果无可用许可直接返回false,不会阻塞;

boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException:尝试获取指定数目的许可,如果无可用许可,会阻塞到直至超时返回false;

简单的代码示例:

/**
 * 信号量,semaphore
 * */
public class SemaphoreTest {

	public static void main(String[] args) {
		
		// 创建只有1个资源许可的信号量对象
		Semaphore semaphore = new Semaphore(1);
		
		Runnable r = () -> {
			try {
				// 获取许可
				semaphore.acquire();
				System.out.println(Thread.currentThread().getName() + "---开始执行--" + (new Date()).toString());
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				// 记得在finally中释放资源
				semaphore.release();
			}
			
		};
		
		Thread t1 = new Thread(r, "A线程");
		Thread t2 = new Thread(r, "B线程");
		Thread t3 = new Thread(r, "C线程");
		Thread t4 = new Thread(r, "D线程");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		
	}
}

从执行结果可以观察到,4个线程间隔3s左右获取到资源后,依次运行,如下:

A线程---开始执行--Fri Apr 15 15:25:05 CST 2022
B线程---开始执行--Fri Apr 15 15:25:08 CST 2022
C线程---开始执行--Fri Apr 15 15:25:11 CST 2022
D线程---开始执行--Fri Apr 15 15:25:14 CST 2022

同步器之Exchanger

Exchanger位于java.util.concurrent包下,用于交换2个不同线程的数据。

它只有1个无参的构造方法,有以下常用方法:

V exchange(V x) throws InterruptedException:将当前线程的数据加入交换器中,返回其他线程利用当前Exchanger对象并调用该方法所传入的对象数据。当A线程调用该方法之后,会进入阻塞状态,直到另一个B线程调用此方法,然后以线程安全的方式交换数据后,AB线程继续运行。

V exchange(V x, long timeout, TimeUnit unit):在exchange()方法基础上,增加一个超时时间。

注:Exchanger仅支持2个或2n个线程进行交换数据,当2个线程时,相互交换数据,当2n>2个线程时,每2个优先获得资源的线程交换数据,依次类推,如果是奇数个线程,由于最后1个线程没有与之交换数据的线程,则会一直处于阻塞状态,注意设置超时释放资源。

一个简单的示例:

public class ExchangerTest {

	public static void main(String[] args) {
		// 创建交换器
		Exchanger<String> exchanger = new Exchanger<String>();
		Exchanger<String> exchanger2 = new Exchanger<String>();
		
		Runnable r = () -> {
			
			String currentThreadName = Thread.currentThread().getName();
			try {
				// 注意这里,需要用同一个Exchanger对象进行
				System.out.println(currentThreadName + ",得到交换后的信息:" + exchanger.exchange(currentThreadName));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		};
		
		new Thread(r, "A线程").start();
		new Thread(r, "B线程").start();
	}
}

 从执行结果可以看到,A线程用自己的线程名与B线程的名字进行交换。

B线程,得到交换后的信息:A线程
A线程,得到交换后的信息:B线程

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值