Java 并发一文读懂

Posted 在奋斗的大道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 并发一文读懂相关的知识,希望对你有一定的参考价值。

目录

线程实现方式

方式一:实现 Runnable 接口

方式二:实现 Callable 接口

方式三:继承 Thread 类

演示功能代码

实现接口 VS 继承 Thread

线程机制

Executor(线程执行容器)

Executor(线程执行容器)实现

CachedThreadPool 实例

FixedThreadPool实例

SingleThreadExecutor实例

演示功能代码

Daemon(守护线程)

Daemon(守护线程)实现方式

演示功能代码:

sleep(休眠)

演示功能代码:

yield(线程让步)

演示功能代码:

线程中断

InterruptedException

演示功能代码:

interrupted() 线程中断方法

演示功能代码:

Executor 中断操作

演示功能代码:

Executor 指定线程中断

演示功能代码:

线程互斥同步

synchronized

同步一个代码块

演示功能代码:

同步一个方法

同步一个类

演示功能代码:

同步一个静态方法

ReentrantLock

演示功能代码:

synchronized与ReentrantLock比较

推荐选择

线程协作

join()

演示功能代码:

wait() notify() notifyAll()

演示功能代码

wait() 和 sleep() 的区别

await() signal() signalAll()

演示功能代码

线程状态

新建(NEW)

可运行(RUNABLE)

阻塞(BLOCKED)

无限期等待(WAITING)

限期等待(TIMED_WAITING)

死亡(TERMINATED)

J.U.C - AQS

CountDownLatch

执行图

演示功能代码

CyclicBarrier

构造函数

执行图

演示功能代码

Semaphore 

演示功能代码

J.U.C - 其它组件 

FutureTask 

 演示功能代码:

BlockingQueue 

使用 BlockingQueue 实现生产者消费者模式优化

ForkJoin

演示功能代码:

线程不安全示例 

Java 内存模型

主内存与工作内存

 内存间交互操作

内存模型三大特性

原子性

 可见性

有序性

先行发生原则

单一线程原则

 管程锁定规则 

 volatile 变量规则 

线程启动规则 

线程加入规则 

 线程中断规则

对象终结规则

传递性

线程安全

不可变

演示功能代码:

 互斥同步

非阻塞同步

CAS

AtomicInteger

ABA 

无同步方案

栈封闭

 线程本地存储(Thread Local Storage) 

演示功能代码:

可重入代码(Reentrant Code)

锁优化

自旋锁

锁消除

锁粗化

轻量级锁

偏向锁

多线程开发良好的实践 


线程实现方式

方式一:实现 Runnable 接口

Runnable 接口实例

static class RunnableImpl implements Runnable
		@Override
		public void run() 
			// TODO Auto-generated method stub
			System.out.println("线程实现之Runnable 接口");
		
		
	

方式二:实现 Callable 接口

温馨提示:Callable 存在返回值,返回值通过 FutureTask 进行封装。

Callable 接口实例

	static class CallableImpl implements Callable<Boolean>

		@Override
		public Boolean call() throws Exception 
			// TODO Auto-generated method stub
			System.out.println("线程实现之Callable 接口");
			return true;
		
		
	

方式三:继承 Thread 类

Thread 类实例

/**
	 * 温馨提示:继承Thread类,覆写run方法
	 * @author zzg
	 *
	 */
	static class ThreadImpl extends Thread
		
		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			System.out.println("线程实现之Thread 类");
		
	

演示功能代码

package com.zzg.test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class RunnableTest 
	public static void main(String[] args) 
		Thread runnableImpl = new Thread(new RunnableImpl());
		runnableImpl.start();
		
		Thread threadImpl = new ThreadImpl();
		threadImpl.start();
		
		CallableImpl callableImpl = new CallableImpl();
	    FutureTask<Boolean> task = new FutureTask<>(callableImpl);
	    Thread callableImplThread = new Thread(task);
	    callableImplThread.start();
	    try 
			System.out.println("是否执行成功" + task.get());
		 catch (InterruptedException e) 
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("Callable 线程实现执行失败");
		 catch (ExecutionException e) 
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("Callable 线程实现执行失败");
		
	
	
	static class RunnableImpl implements Runnable
		@Override
		public void run() 
			// TODO Auto-generated method stub
			System.out.println("线程实现之Runnable 接口");
		
		
	
	
	/**
	 * 温馨提示:继承Thread类,覆写run方法
	 * @author zzg
	 *
	 */
	static class ThreadImpl extends Thread
		
		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			System.out.println("线程实现之Thread 类");
		
	
	
	static class CallableImpl implements Callable<Boolean>

		@Override
		public Boolean call() throws Exception 
			// TODO Auto-generated method stub
			System.out.println("线程实现之Callable 接口");
			return true;
		
		
	


实现接口 VS 继承 Thread

实现接口会更好一些,因为:

  • Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

线程机制

Executor(线程执行容器)

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作

Executor(线程执行容器)实现

主要有三种 Executor:

  • CachedThreadPool:一个任务创建一个线程;
  • FixedThreadPool:所有任务只能使用固定大小的线程;
  • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。

CachedThreadPool 实例

/**
	 * CachedThreadPool 线程池调用
	 */
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("CachedThreadPool"));
		    
		    executorService.shutdown();
	

FixedThreadPool实例

/**
	 * FixedThreadPool 线程池调用
	 */
	public static void fixedThreadPoolTest()
		 ExecutorService executorService = Executors.newFixedThreadPool(5);
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("FixedThreadPool"));
		    
		    executorService.shutdown();
	

SingleThreadExecutor实例

/**
	 * SingleThreadExecutor 线程池调用
	 */
	public static void singleThreadExecutorTest()
		 ExecutorService executorService = Executors.newSingleThreadExecutor();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("SingleThreadExecutor"));
		    
		    executorService.shutdown();
	

演示功能代码

package com.zzg.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorTest 

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		cachedThreadPoolTest();
		fixedThreadPoolTest();
		singleThreadExecutorTest();
	
	
	/**
	 * CachedThreadPool 线程池调用
	 */
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("CachedThreadPool"));
		    
		    executorService.shutdown();
	
	
	/**
	 * FixedThreadPool 线程池调用
	 */
	public static void fixedThreadPoolTest()
		 ExecutorService executorService = Executors.newFixedThreadPool(5);
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("FixedThreadPool"));
		    
		    executorService.shutdown();
	
	
	/**
	 * SingleThreadExecutor 线程池调用
	 */
	public static void singleThreadExecutorTest()
		 ExecutorService executorService = Executors.newSingleThreadExecutor();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("SingleThreadExecutor"));
		    
		    executorService.shutdown();
	
	
	static class RunnableImpl implements Runnable
		private String name;
		
		public String getName() 
			return name;
		
		
		public void setName(String name) 
			this.name = name;
		
		
		public RunnableImpl(String name) 
			super();
			this.name = name;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			System.out.println("线程执行容器之"+this.name+"线程实现Runnabe接口");
		
		
	


Daemon(守护线程)

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

温馨提示

  • 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
  • main() 属于非守护线程。

Daemon(守护线程)实现方式

在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。

演示功能代码:

package com.zzg.test;

public class DaemonTest 

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		Thread thread = new Thread(new RunnableImpl());
		// 设置thread 线程为守护线程
		thread.setDaemon(true);
		// 守护线程启动
		thread.start();
	
	
	
	static class RunnableImpl implements Runnable

		@Override
		public void run() 
			// TODO Auto-generated method stub
			System.out.println("线程实现之Runnable 接口");
		
		
	


sleep(休眠)

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

重点注意:sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SleepTest 

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		Thread thread = new Thread(new RunnableImpl());
		// 线程启动
		thread.start();	
	
	
	static class RunnableImpl implements Runnable

		@Override
		public void run() 
			// TODO Auto-generated method stub
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			System.out.println("线程开始执行时间" + format.format(new Date()));
			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
			
			System.out.println("线程开始结束时间" + format.format(new Date()));
		
		
	


yield(线程让步)

Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;

import com.digipower.test.SleepTest.RunnableImpl;

public class YieldTest 
	
	public static void main(String[] args) 
		Thread thread = new Thread(new RunnableImpl("normal Runnable"));
		Thread yieLdThread = new Thread(new YieldRunnableImpl("yidld Runnable"));
		// 设置线程等级
		//yieLdThread.setPriority(Thread.MAX_PRIORITY);
		// 线程启动
		yieLdThread.start();
		thread.start();	
	
	
	static class RunnableImpl implements Runnable
		
		private String name;
		
		public String getName() 
			return name;
		

		public void setName(String name) 
			this.name = name;
		

		public RunnableImpl(String name) 
			super();
			this.name = name;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			System.out.println(this.name + ",线程执行时间" + format.format(new Date()));
			
			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
			
			
			System.out.println(this.name + ",线程结束时间" + format.format(new Date()));
		
		
	
	
	static class YieldRunnableImpl implements Runnable
		
		private String name;
		
		public String getName() 
			return name;
		

		public void setName(String name) 
			this.name = name;
		

		public YieldRunnableImpl(String name) 
			super();
			this.name = name;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			System.out.println(this.name + ",线程执行时间" + format.format(new Date()));
			// 线程让步
			Thread.yield();
			
			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
			
			
			System.out.println(this.name + ",线程结束时间" + format.format(new Date()));
		
		
	
	
	


线程中断

一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。

InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。

演示功能代码:

代码片段说明:在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;

public class InterruptedExceptionTest 

	public static void main(String[] args) 
		Thread thread = new Thread(new RunnableImpl("interrupted Runnable"));
		thread.start();
		// 调用线程中断方法
		thread.interrupt();
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		System.out.println("Main run" + format.format(new Date()));

	

	static class RunnableImpl implements Runnable 

		private String name;

		public String getName() 
			return name;
		

		public void setName(String name) 
			this.name = name;
		

		public RunnableImpl(String name) 
			super();
			this.name = name;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			System.out.println(this.name + ",线程执行时间" + format.format(new Date()));

			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
				// 终止线程继续运行
				return;
			

			System.out.println(this.name + ",线程结束时间" + format.format(new Date()));
		

	

运行结果:

interrupted Runnable,线程执行时间2022-03-18 05:29:05
Main run2022-03-18 05:29:05
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.digipower.test.InterruptedExceptionTest$RunnableImpl.run(InterruptedExceptionTest.java:42)
	at java.lang.Thread.run(Thread.java:745)

interrupted() 线程中断方法

问题描述:如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。

问题解决办法:在线程中设置中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;

public class InterruptedTest 

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		Thread thread = new ThreadImpl("Interrupted  Runnabel");
		thread.start();
		
		// 主线程休眠
		try 
			Thread.sleep(500);
		 catch (InterruptedException e) 
			// TODO Auto-generated catch block
			e.printStackTrace();
		
		// 调用线程的中断方法
		thread.interrupt();
		// 主线程执行完毕
		System.out.println("Main Run");
	
	
	static class ThreadImpl extends Thread 

		private String threadNam;

		
		public String getThreadNam() 
			return threadNam;
		

		public void setThreadNam(String threadNam) 
			this.threadNam = threadNam;
		

		public ThreadImpl(String threadNam) 
			super();
			this.threadNam = threadNam;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			System.out.println(this.threadNam + ",线程执行时间" + format.format(new Date()));
			
			 while (!interrupted()) 
				 System.out.println("我没有收到中断信息"); 
	         

			
			System.out.println("我进入了中断线程的条件中,将要结束run方法"); 
			System.out.println(this.threadNam + ",线程结束时间" + format.format(new Date()));
		

	


Executor 中断操作

调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorInterruptTest 
	
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("CachedThreadPool"));
		    
		   // 立即中断线程池中的线程
		    executorService.shutdownNow();
		   // 线程池中的线程全部执行完毕后,执行线程池关闭操作。
		   // executorService.shutdown();
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		cachedThreadPoolTest();
	
	
	
	static class RunnableImpl implements Runnable
		private String name;
		
		public String getName() 
			return name;
		
		
		public void setName(String name) 
			this.name = name;
		
		
		public RunnableImpl(String name) 
			super();
			this.name = name;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			System.out.println("线程执行容器之"+this.name+"线程实现Runnabe接口");
			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
				System.out.println("线程执行中断操作");
				return;
			
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println("线程正常执行完毕" + format.format(new Date()));
		
		
	


Executor 指定线程中断

如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorInterruptTest 
	
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new RunnableImpl("CachedThreadPool"));
		    
		   // 立即中断线程池中的线程
		    executorService.shutdownNow();
		   // 线程池中的线程全部执行完毕后,执行线程池关闭操作。
		   // executorService.shutdown();
	
	
	public static void cachedThreadPoolTestInterrupt()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		    for (int i = 0; i < 5; i++) 
		    	Future<?> future = executorService.submit(new RunnableImpl("CachedThreadPool" + i));
		    	if(i == 3)
		    		// 中断满足条件的线程
		    		future.cancel(true);
		    	
		    
		   // 立即中断线程池中的线程
		   // executorService.shutdownNow();
		   // 线程池中的线程全部执行完毕后,执行线程池关闭操作。
		   executorService.shutdown();
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		// cachedThreadPoolTest();
		cachedThreadPoolTestInterrupt();
	
	
	
	static class RunnableImpl implements Runnable
		private String name;
		
		public String getName() 
			return name;
		
		
		public void setName(String name) 
			this.name = name;
		
		
		public RunnableImpl(String name) 
			super();
			this.name = name;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			System.out.println("线程执行容器之"+this.name+"线程实现Runnabe接口");
			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
				System.out.println("线程执行中断操作");
				return;
			
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println("线程正常执行完毕" + format.format(new Date()));
		
		
	


线程互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

synchronized

同步一个代码块

语法:

public void func() 
    synchronized (this) 
        // ...
    

功能说明:它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedBlockTest 
	
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		 
		 SynchronizedObject object = new SynchronizedObject();
		    for (int i = 0; i < 5; i++) 
		        executorService.execute(new Synchronizedhread(object, "SynchronizedBlockThread" + i));
		    
		 
		   // 线程池中的线程全部执行完毕后,执行线程池关闭操作。
		   executorService.shutdown();
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		cachedThreadPoolTest();
	
	
	static class SynchronizedObject
		public void printNUmber()
			synchronized (this) 
				for (int i = 0; i < 20; i++) 
					String name = Thread.currentThread().getName();
	                System.out.println(name +"正在输出数字" + i);
	                // 当前正在执行的线程进行休眠,锁不会释放
	                try 
						Thread.sleep(1000);
					 catch (InterruptedException e) 
						// TODO Auto-generated catch block
						e.printStackTrace();
						// 出现异常,立即中止当前循环
						break;
					
	                System.out.println(name +"数字输出完毕" );
	            
			
		
	
	
	static class Synchronizedhread extends Thread
		private SynchronizedObject object;
		
		private String threadName;
		
		public String getThreadName() 
			return threadName;
		

		public void setThreadName(String threadName) 
			this.threadName = threadName;
		

		public SynchronizedObject getObject() 
			return object;
		

		public void setObject(SynchronizedObject object) 
			this.object = object;
		

		public Synchronizedhread(SynchronizedObject object, String threadName) 
			super();
			this.object = object;
			this.setName(threadName);
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println(this.getName() + "线程:开始启动" + format.format(new Date()));
			// 执行同步对象方法
			this.object.printNUmber();
			System.out.println(this.getName()  + "线程:运行结束" + format.format(new Date()));
		
		
		
		
	


同步一个方法

语法:

public synchronized void func () 
    // ...

功能说明:它和同步代码块一样,作用于同一个对象。

同步一个类

语法:

public void func() 
    synchronized (SynchronizedExample.class) 
        // ...
    

功能说明:作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。

演示功能代码:

package com.digipower.test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedClassTest 
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		 
		
		    for (int i = 0; i < 5; i++) 
		    	SynchronizedObject object = new SynchronizedObject();
		        executorService.execute(new Synchronizedhread(object, "SynchronizedBlockThread" + i));
		    
		 
		   // 线程池中的线程全部执行完毕后,执行线程池关闭操作。
		   executorService.shutdown();
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		cachedThreadPoolTest();
	
	
	static class SynchronizedObject
		public void printNUmber()
			synchronized (SynchronizedObject.class) 
				for (int i = 0; i < 10; i++) 
					String name = Thread.currentThread().getName();
	                System.out.println(name +"正在输出数字" + i);
	                // 当前正在执行的线程进行休眠,锁不会释放
	                try 
						Thread.sleep(1000);
					 catch (InterruptedException e) 
						// TODO Auto-generated catch block
						e.printStackTrace();
						// 出现异常,立即中止当前循环
						break;
					
	                System.out.println(name +"数字输出完毕" );
	            
			
		
	
	
	static class Synchronizedhread extends Thread
		private SynchronizedObject object;
		
		private String threadName;
		
		public String getThreadName() 
			return threadName;
		

		public void setThreadName(String threadName) 
			this.threadName = threadName;
		

		public SynchronizedObject getObject() 
			return object;
		

		public void setObject(SynchronizedObject object) 
			this.object = object;
		

		public Synchronizedhread(SynchronizedObject object, String threadName) 
			super();
			this.object = object;
			this.setName(threadName);
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println(this.getName() + "线程:开始启动" + format.format(new Date()));
			// 执行同步对象方法
			this.object.printNUmber();
			System.out.println(this.getName()  + "线程:运行结束" + format.format(new Date()));
		
		
		
		
	

同步一个静态方法

语法:

public synchronized static void fun() 
    // ...

功能说明:作用于整个类。

ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

演示功能代码:

package com.zzg.test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest 
	
	public static void cachedThreadPoolTest()
		 ExecutorService executorService = Executors.newCachedThreadPool();
		 
		
		    for (int i = 0; i < 5; i++) 
		    	ReentrantLockObject object = new ReentrantLockObject();
		        executorService.execute(new Synchronizedhread(object,  "" + i));
		    
		 
		   // 线程池中的线程全部执行完毕后,执行线程池关闭操作。
		   executorService.shutdown();
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		cachedThreadPoolTest();
	

	static class ReentrantLockObject 
		private Lock lock = new ReentrantLock();

		public void func() 
			lock.lock();
			try 
				for (int i = 0; i < 10; i++) 
					String name = Thread.currentThread().getName();
	                System.out.println(name +"正在输出数字" + i);
	                System.out.println(name +"数字输出完毕" );
				
			 finally 
				lock.unlock(); // 确保释放锁,从而避免发生死锁。
			
		
	
	
	static class Synchronizedhread extends Thread
		private ReentrantLockObject object;
		
		private String threadName;
		
		public String getThreadName() 
			return threadName;
		

		public void setThreadName(String threadName) 
			this.threadName = threadName;
		

		public ReentrantLockObject getObject() 
			return object;
		

		public void setObject(ReentrantLockObject object) 
			this.object = object;
		

		public Synchronizedhread(ReentrantLockObject object, String threadName) 
			super();
			this.object = object;
			this.setName("ReentrantLockThread"+threadName);
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println(this.getName() + "线程:开始启动" + format.format(new Date()));
			// 执行同步对象方法
			this.object.func();
			System.out.println(this.getName()  + "线程:运行结束" + format.format(new Date()));
		
		
		
		
	


synchronized与ReentrantLock比较

1. 锁的实现

synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。

2. 性能

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

3. 等待可中断

当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。

ReentrantLock 可中断,而 synchronized 不行。

4. 公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

5. 锁绑定多个条件

一个 ReentrantLock 可以同时绑定多个 Condition 对象。

推荐选择

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

线程协作

当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。

join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。自身才会再次执行。

演示功能代码:

演示功能说明:b 线程优先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

package com.zzg.test;

public class ThreadJoinTest 

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		ThreadA a = new ThreadA();
		ThreadB b = new ThreadB(a);
		// 设置线程B,执行级别最高
		b.setPriority(Thread.MAX_PRIORITY);
		a.start();
		b.start();
	

	static class ThreadA extends Thread 

		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			System.out.println("线程A开始执行");
			try 
				Thread.sleep(3000);
			 catch (InterruptedException e) 
				// TODO Auto-generated catch block
				e.printStackTrace();
				// 线程出现异常,终止代码继续执行
				System.out.println("线程A出现中断异常");
				return;
			
			System.out.println("线程A执行完毕");
		

	

	static class ThreadB extends Thread 
		private ThreadA a;

		public ThreadA getA() 
			return a;
		

		public void setA(ThreadA a) 
			this.a = a;
		

		public ThreadB(ThreadA a) 
			super();
			this.a = a;
		

		@Override
		public void run() 
			// TODO Auto-generated method stub
			super.run();
			System.out.println("线程B开始执行");
			try 
				a.join();
			 catch (InterruptedException e) 
				e.printStackTrace();
				// 线程异常中断,终止代码继续执行。
				System.out.println("线程B出现中断异常");
				return;
			
			System.out.println("线程B执行完毕");
		

	


wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。

演示功能代码

package com.zzg.test;

/**
 * 多线程模式之生产与消费者模式
 * 
 * @author zzg
 *
 */
public class WaitNotifyTest 

	private static Integer count = 0;
	private static final Integer FULL = 5;
	private static Object lock = "lock";

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		Thread product = new Thread(new Product());
		Thread comsumer = new Thread(new Consumer());
		
		product.start();
		comsumer.start();
		
	
	
	static class Product implements Runnable

		@Override
		public void run() 
			// TODO Auto-generated method stub
			 for (int i = 0; i < 5; i++)
	         
	             try 
	                 Thread.sleep(1000);
	              catch (InterruptedException e1) 
	                 // TODO Auto-generated catch block
	                 e1.printStackTrace();
	                 System.out.println("生成线程在A代码段出现中断异常");
	             
	             synchronized (lock)
	             
	            	 System.out.println("生产count 属性值:" + count);
	                 while (count == FULL)
	                 
	                         try 
	                        	 System.out.println("生产线程释放锁");
	                             lock.wait();
	                          catch (InterruptedException e) 
	                             // TODO Auto-generated catch block
	                             e.printStackTrace();
	                             System.out.println("生成线程在B代码段出现中断异常");
	                         
	                 
	                 count++;
	                 System.out.println(Thread.currentThread().getName() + "produce:: " + count);
	                 lock.notifyAll();
	                 System.out.println("生产线程唤醒消费线程");
	             
	         
		
		
	
	
	static class Consumer implements Runnable

		@Override
		public void run() 
			for(int i =0; i < 5; i++)
				// 当前线程执行休眠
				try 
					Thread.sleep(1000);
				 catch (InterruptedException e) 
					// TODO Auto-generated catch block
					e.printStackTrace();
					System.out.println("当前消费者线程在A代码段出现中断异常");
				
				// 线程公用lock对象
				synchronized (lock) 
					// 判断lock对象 调用wait 和notifyAll 方法判断条件
					while(count ==0)
						try 
							System.out.println("消费者线程释放锁");
							lock.wait();
						 catch (InterruptedException e) 
							// TODO Auto-generated catch block
							e.printStackTrace();
							System.out.println("当前消费者线程在B代码段出现中断异常");
						
					
					 count--;
	                 System.out.println(Thread.currentThread().getName()+ "consume:: " + count);
	                 lock.notifyAll();
	                 System.out.println("消费者线程唤醒生产者线程");
				
				
			
			
		
		
	


wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。

await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。

相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

演示功能代码

package com.zzg.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多线程模式之生产与消费者模式
 * 
 * @author zzg
 *
 */
public class AwaitSignalTest 
	
	private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    
	private static Integer count = 0;
	private static final Integer FULL = 5;

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		Thread product = new Thread(new Product());
		Thread comsumer = new Thread(new Consumer());
		
		product.start();
		comsumer.start();
	
	
	static class Product implements Runnable

		@Override
		public void run() 
			// TODO Auto-generated method stub
			 for (int i = 0; i < 5; i++)
	         
	             try 
	                 Thread.sleep(1000);
	              catch (InterruptedException e1) 
	                 // TODO Auto-generated catch block
	                 e1.printStackTrace();
	                 System.out.println("生成线程在A代码段出现中断异常");
	             
	             lock.lock();
	             try
	            	 System.out.println("生产count 属性值:" + count);
	                 while (count == FULL)
	                 
	                         try 
	                        	 System.out.println("生产线程释放锁");
	                        	 condition.await();
	                          catch (InterruptedException e) 
	                             // TODO Auto-generated catch block
	                             e.printStackTrace();
	                             System.out.println("生成线程在B代码段出现中断异常");
	                         
	                 
	                 count++;
	                 System.out.println(Thread.currentThread().getName() + "produce:: " + count);
	                 condition.signalAll();
	                 System.out.println("生产线程唤醒消费线程");
	             finally 
	            	 lock.unlock();
	             
	         
		
		
	
	
	static class Consumer implements Runnable

		@Override
		public void run() 
			for(int i =0; i < 5; i++)
				// 当前线程执行休眠
				try 
					Thread.sleep(1000);
				 catch (InterruptedException e) 
					// TODO Auto-generated catch block
					e.printStackTrace();
					System.out.println("当前消费者线程在A代码段出现中断异常");
				
				// 线程公用lock对象
				 lock.lock(); 
				 try
					// 判断lock对象 调用wait 和notifyAll 方法判断条件
					while(count ==0)
						try 
							System.out.println("消费者线程释放锁");
							condition.await();
						 catch (InterruptedException e) 
							// TODO Auto-generated catch block
							e.printStackTrace();
							System.out.println("当前消费者线程在B代码段出现中断异常");
						
					
					 count--;
	                 System.out.println(Thread.currentThread().getName()+ "consume:: " + count);
	                 condition.signalAll();
	                 System.out.println("消费者线程唤醒生产者线程");
				finally
					lock.unlock();
				
				
			
			
		
		
	


线程状态

一个线程只能处于一种状态,并且这里的线程状态特指 Java 虚拟机的线程状态,不能反映线程在特定操作系统下的状态。

新建(NEW)

创建后尚未启动。

可运行(RUNABLE)

正在 Java 虚拟机中运行。但是在操作系统层面,它可能处于运行状态,也可能等待资源调度(例如处理器资源),资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层操作系统的资源调度。

阻塞(BLOCKED)

请求获取 monitor lock 从而进入 synchronized 函数或者代码块,但是其它线程已经占用了该 monitor lock,所以出于阻塞状态。要结束该状态进入从而 RUNABLE 需要其他线程释放 monitor lock。

无限期等待(WAITING)

等待其它线程显式地唤醒。

阻塞和等待的区别在于,阻塞是被动的,它是在等待获取 monitor lock。而等待是主动的,通过调用 Object.wait() 等方法进入。

以上是关于Java 并发一文读懂的主要内容,如果未能解决你的问题,请参考以下文章

Java 并发关键字大练兵—一文读懂各个关键字

Java 并发关键字大练兵—一文读懂各个关键字

一文读懂Java类加载机制

一文读懂响应式编程到底是什么?(附赠国外优质Java学习视频)

一文读懂 Nginx

一文让你读懂高并发编程的意义及其好处和注意事项

(c)2006-2024 SYSTEM All Rights Reserved IT常识

进入方法退出方法