Java基础回顾系列-第四天-高级编程之多线程编程

Java多线程编程

进程、线程概念

Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

进程和线程的区别
进程
应用程序的执行实例,有独立的内存空间和系统资源。一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程
CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
进程和线程的关系:

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
  3. 处理机分给线程,即真正在处理机上运行的是线程。
  4. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

并行与并发:

  • 并行: 多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  • 并发: :一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

多线程程序的优点:

  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  • 提高计算机系统CPU的利用率
  • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

创建一个线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 CallableFuture 创建线程。
通过继承Thread来创建线程

写一个类继承自 Thread 类,重写 run 方法。用 start 方法启动线程。 创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

package javase.util;

/**
 * 继承Thread实现多线程
 */
class MyThread extends Thread {
	public String title;

	public MyThread(String title) {
		this.title = title;
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.title+":执行 i = " + i);
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		new MyThread("线程A").start();
		new MyThread("线程B").start();
		new MyThread("线程C").start();
	}
}

程序运行结果如下:

线程B:执行 i = 0
线程A:执行 i = 0
线程C:执行 i = 0
线程A:执行 i = 1
线程B:执行 i = 1
线程A:执行 i = 2
线程C:执行 i = 1
线程A:执行 i = 3
线程B:执行 i = 2
线程A:执行 i = 4
线程C:执行 i = 2
线程A:执行 i = 5
线程B:执行 i = 3
线程A:执行 i = 6
线程C:执行 i = 3
线程A:执行 i = 7
线程B:执行 i = 4
线程A:执行 i = 8
线程C:执行 i = 4
线程A:执行 i = 9
线程B:执行 i = 5
线程C:执行 i = 5
线程B:执行 i = 6
线程C:执行 i = 6
线程B:执行 i = 7
线程C:执行 i = 7
线程B:执行 i = 8
线程C:执行 i = 8
线程B:执行 i = 9
线程C:执行 i = 9

MyThread thread = new MyThread("线程A");
thread.start();
// 出错,线程对象只允许启动一次:Exception in thread "main" java.lang.IllegalThreadStateException
// thread.start();
通过实现 Runnable 接口来创建线程

写一个类实现 Runnable 接口,实现 run 方法。用 new Thread(Runnable target).start() 方法来启动。
注意:Runnable接口是函数式接口@FunctionalInterface,只有一个run()方法

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

Lambda实现:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		for (int i = 1; i <= 3; i++) {
			String title = "线程对象"+i;
			// 函数式Runnable接口实现
			new Thread(()->{
				for (int j = 0; j < 10; j++) {
					System.out.println(title+ "执行 i = " + j);
				}
			}).start();
		}
	}
}

package javase.util;

/**
 * 实现Runnable接口创建多线程
 */
class MyThread implements Runnable {
	private String title;
	public MyThread(String title) {
		this.title = title;
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.title+":执行 i = " + i);
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		new Thread(new MyThread("线程A")).start();
		new Thread(new MyThread("线程B")).start();
		new Thread(new MyThread("线程C")).start();
	}
}

通过 Callable 和 Future 创建线程
  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

注意: Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

package javase.util;

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

/**
 * 通过 Callable 和 Future(FutureTask) 创建线程
 */
class MyThread implements Callable<String> {
	private String title;
	public MyThread(String title) {
		this.title = title;
	}

	@Override
	public String call() throws Exception {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.title+":执行 i = " + i);
		}
		return "多线程执行完毕";
	}
}

public class TestMain {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		FutureTask<String> futureTask = new FutureTask<String>(new MyThread("线程A"));
		new Thread(futureTask).start();
		String callStr = futureTask.get();
		System.out.println("callStr = " + callStr);
	}
}

面试题: 请解释 RunnableCallable的区别?。

  • Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
  • java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值;
  • java.util.concurrent.Callable接口提供有call()方法,可以有返回值;.
通过 线程池创建线程

背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理。
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API: ExecutorServiceExecutors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
  • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
  • void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n): 创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//创建并使用多线程的第四种方法:使用线程池
class MyThread implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}

}

public class ThreadPool {
	public static void main(String[] args) {
		// 1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
		ExecutorService pool = Executors.newFixedThreadPool(10);
		// 2.将Runnable实现类的对象作为形参传递给ExecutorService的submit()方法中,开启线程
		// 并执行相关的run()
		pool.execute(new MyThread());
		pool.execute(new MyThread());
		pool.execute(new MyThread());
		// 3.结束线程的使用
		pool.shutdown();

	}
}
线程池类型区别
package test;

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

public class ThreadForpools implements Runnable{
	private Integer index;
	ThreadForpools(Integer index) {
		this.index = index;
	}

	@Override
	public void run() {
		try {
			System.out.println("开始处理线程!!!");
			Thread.sleep(index *100);
			System.out.println("我的线程标识是:"+ System.identityHashCode(Thread.currentThread()));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println("创建一个可缓存线程池,应用中存在的线程数可以无限大。输出结果是:可以有无限大的线程数进来(线程地址不一样)");
		ExecutorService executorService = Executors.newCachedThreadPool();
		/*
		for (int i = 1; i <= 5; i++) {
			executorService.execute(new ThreadForpools(i));
		}
		
		System.out.println("创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。输出结果:每次只有两个线程在处理,当第一个线程执行完毕后,新的线程进来开始处理(线程地址不一样)");
		executorService = Executors.newFixedThreadPool(2);
		for (int i = 1; i <= 5; i++) {
			executorService.execute(new ThreadForpools(i));
		}
		
		
		System.out.println("创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。执行结果:只存在一个线程,顺序执行");
		executorService = Executors.newSingleThreadExecutor();
		for (int i = 1; i <= 5; i++) {
			executorService.execute(new ThreadForpools(i));
		}
		*/
		
		System.out.println("创建一个定长线程池,支持定时及周期性任务执行。执行结果:延迟三秒之后执行,除了延迟执行之外和newFixedThreadPool基本相同,可以用来执行定时任务");
		ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
		for (int i = 1; i <= 5; i++) {
			scheduledExecutorService.schedule(new ThreadForpools(i), 3, TimeUnit.SECONDS);
		}
	}

}

创建线程的三种方式的对比

  1. 采用实现 RunnableCallable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程的状态/生命周期

参考博文:线程状态 https://www.cnblogs.com/GooPolaris/p/8079490.html

线程的状态使用一个 枚举类型(Thread.State) 来描述的。这个枚举一共有6个值: NEW(新建)、RUNNABLE(运行)、BLOCKED(锁池)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束)。

线程共包括以下 5 种状态:
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程常用操作方法

线程的命名与获取

设置线程的名称:

  • 构造函数:public Thread​(String name)
  • 构造函数:public Thread​(Runnable target, String name)
  • 实例方法:public final void setName​(String name)

获取线程的名称:

  • 实例方法:public final String getName()
  • 静态方法:public static Thread currentThread().getName()
package javase.util;

/**
 * 实现Runnable接口创建多线程
 */
class MyThread implements Runnable {
	@Override
	public void run() {
		// 获取线程名称
		System.out.println(Thread.currentThread().getName());
	}
}

public class TestMain {
	public static void main(String[] args) {
		System.out.println("main主方法进程:"+ Thread.currentThread().getName());// main
		new Thread(new MyThread(), "线程A").start();// 线程A
		new Thread(new MyThread()).start(); // Thread-0
		new Thread(new MyThread()).start();// Thread-1
		new Thread(new MyThread(), "线程B").start();// 线程B
		Thread threadC = new Thread(new MyThread());
		threadC.setName("线程C");
		threadC.start();// // 线程C
	}
}

主线程子线程的关系

问题:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		System.out.println("执行任务一");

		int temp = 0;
		for (int i = 0; i < Integer.MIN_VALUE; i++) {
			temp += i;
		}
		
		// 以下的输出需要以上先执行完才会执行
		System.out.println("执行任务二");
		System.out.println("执行任务三");
	}
}

解决方案:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		System.out.println("执行任务一");
		
		new Thread(()->{
			int temp = 0;
			for (int i = 0; i < Integer.MIN_VALUE; i++) {
				temp += i;
			}
		});

		// 以下的输出不依赖于上面的执行,速度快多了
		System.out.println("执行任务二");
		System.out.println("执行任务三");
	}
}

线程休眠(暂停执行)

线程休眠:

  • 静态方法:public static void sleep​(long millis) throws InterruptedException
  • 静态方法:public static void sleep​(long millis, int nanos) throws InterruptedException

单个线程的休眠示例:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		new Thread(()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+",i = " + i);
				try {
					Thread.sleep(100);// 休眠100毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "线程对象").start();
	}
}

多个线程的示例:

package javase.util;

/**
 * 该程序执行上感觉上若干个线程一起休眠,一起唤醒,实际是有差别的
 */
public class TestMain {
	public static void main(String[] args) {
		Runnable runnable = ()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+",i = " + i);
				try {
					Thread.sleep(1000);// 休眠1秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		// 创建5个线程对象
		for (int x = 1; x <= 5; x++) {
			new Thread(runnable, "线程对象"+x).start();
		}
	}
}

线程中断

在我们的程序中经常会有一些不达到目的不会退出的线程,例如:我们有一个下载程序线程,该线程在没有下载成功之前是不会退出的,若此时用户觉得下载速度慢,不想下载了,这时就需要用到我们的线程中断机制了,告诉线程,你不要继续执行了,准备好退出吧。当然,线程在不同的状态下遇到中断会产生不同的响应,有点会抛出异常,有的则没有变化,有的则会结束线程。本篇将从以下两个方面来介绍Java中对线程中断机制的具体实现:

  • Java中对线程中断所提供的API支持
  • 线程在不同状态下对于中断所产生的反应
Java中对线程中断所提供的API支持

在以前的jdk版本中,我们使用stop方法中断线程,但是现在的jdk版本中已经不再推荐使用该方法了,反而由以下三个方法完成对线程中断的支持。

public boolean isInterrupted():实例方法,主要用于判断当前线程对象的中断标志位是否被标记了,如果被标记了则返回true表示当前已经被中断,否则返回false。
public void interrupt():实例方法,该方法用于设置当前线程对象的中断标识位。
public static boolean interrupted() :静态的方法,用于返回当前线程是否被中断。

package javase.util;

/**
 * 线程中断:
 * 我们有一个下载程序线程,该线程在没有下载成功之前是不会退出的,若此时用户觉得下载速度慢,不想下载了,
 * 这时就需要用到我们的线程中断机制了,告诉线程,你不要继续执行了,准备好退出吧。
 */
public class TestMain {
	public static void main(String[] args) {
		Runnable runnable = ()->{
			// 以下模拟正常下载
			System.out.println("开始下载.......");
			for (int i = 0; i < 100; i++) {
				System.out.println("已下载进度" + i +"%");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// 中断了会报错
					//e.printStackTrace();
					System.out.println(Thread.currentThread().getName()+":中断下载");
					break;
				}
				
				/*
				// 是否中断
				if(Thread.interrupted()) {
					break;
				}
				*/
			}
		};
		Thread thread = new Thread(runnable, "下载任务进程");
		// 开始下载任务
		thread.start();


		// 以下模拟用户取消下载
		try {
			Thread.sleep(2000); // 用户等待2秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 下载任务完成了吗?
		if(thread.isInterrupted()) {
			System.out.println("用户:这么快就下载好了,佩服!佩服!");
		}
		else {
			// 中断下载
			thread.interrupt();
			System.out.println("用户:这么慢还没下载好,不下载了!");
		}
	}
}
线程在不同状态下对于中断所产生的反应

参考博文:Java并发之线程中断[https://www.cnblogs.com/yangming1996/p/7612653.html]

线程强制执行

public final void join() throws InterruptedException

package javase.util;


/**
 * 主线程:main
 * 子线程:new Thread()
 * 现象:主线程以及子线程抢占交替执行
 */
public class TestMain {
	public static void main(String[] args) {
		// 子线程
		new Thread(()->{
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName()+": "+ i);
			}
		}, "子线程").start();

		// 主线程
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+": "+ i);
		}
	}
}

执行结果:

main: 0
子线程: 0
main: 1
子线程: 1
子线程: 2
子线程: 3
main: 2
子线程: 4
main: 3
main: 4

强制执行:

package javase.util;


/**
 * 主线程:main
 * 子线程:new Thread()
 * 强制执行:一定需要先获取强制执行的线程对象,再使用join()方法
 */
public class TestMain {
	public static void main(String[] args) {
		// 获取主线程
		Thread mainThread = Thread.currentThread();

		// 子线程
		new Thread(()->{
			for (int i = 0; i < 5; i++) {
				// 满足条件时强制执行
				if(i == 2) {
					try {
						// 主线程先强制执行,此时主线程将全部执行之后,再执行子线程
						mainThread.join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(Thread.currentThread().getName()+": "+ i);
			}
		}, "子线程").start();

		// 主线程
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+": "+ i);
		}
	}
}

执行结果:

main: 0
main: 1
子线程: 0
main: 2
main: 3
main: 4
子线程: 1
子线程: 2
子线程: 3
子线程: 4

线程的礼让

public static void yield()

package javase.util;


/**
 * 主线程:main
 * 子线程:new Thread()
 * 线程的礼让
 */
public class TestMain {
	public static void main(String[] args) {
		// 子线程
		new Thread(()->{
			for (int i = 0; i < 10; i++) {
				// 满足条件时进行礼让
				if(i % 2 == 0) {
					// 进行礼让,让主线程先执行
					Thread.yield();
				}
				System.out.println(Thread.currentThread().getName()+": "+ i);
			}
		}, "子线程").start();

		// 主线程
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName()+": "+ i);
		}
	}
}

执行结果:

子线程: 0
main: 0
main: 1
main: 2
main: 3
子线程: 1
main: 4
子线程: 2
main: 5
main: 6
main: 7
子线程: 3
main: 8
子线程: 4
main: 9
子线程: 5
main: 10
子线程: 6
main: 11
子线程: 7
main: 12
main: 13
main: 14
main: 15
main: 16
子线程: 8
main: 17
main: 18
main: 19
子线程: 9

线程的优先级

参考博文:Thread之五:线程的优先级https://www.cnblogs.com/duanxz/p/5226109.html】

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

  • 设置优先级:public final void setPriority​(int newPriority)
  • 获取优先级:public final int getPriority()

三个常量:

  • public static final int MAX_PRIORITY:10
  • public static final int NORM_PRIORITY:5
  • public static final int MIN_PRIORITY:1
package javase.util;

/**
 * 线程的优先级
 */
public class TestMain {
	public static void main(String[] args) {
		System.out.println("main主线程优先级:" + Thread.currentThread().getPriority()); // 中等:5
		System.out.println("默认线程优先级:" + new Thread().getPriority()); // 中等:5
		Runnable runnable = ()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+" 执行"+ i);
			}
		};
		Thread threadA = new Thread(runnable, "线程A");
		Thread threadB = new Thread(runnable, "线程B");
		Thread threadC = new Thread(runnable, "线程C");
		// 设置优先级
		threadA.setPriority(Thread.MIN_PRIORITY); // 设置优先级最低
		threadB.setPriority(Thread.NORM_PRIORITY); // 设置优先级中等
		threadC.setPriority(Thread.MAX_PRIORITY); // 设置优先级最高

		threadA.start();
		threadB.start();
		threadC.start();
	}
}

线程的同步与死锁

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),
将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

参考博文:5个步骤,教你瞬间明白线程和线程安全
参考博文:java笔记–关于线程同步(7种同步方式)https://www.cnblogs.com/goody9807/p/6522176.html】

示例:

package javase.util;

/**
 * 卖票线程
 */
class TicketThread implements Runnable {
	int ticket = 10; // 总票数
	@Override
	public void run() {
		while (true) {
			if(ticket > 0){
				try {
					// 模拟网络延迟
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" 执行"+ ticket--);
			}
			else {
				System.out.println("票卖完了");
				break;
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		Runnable runnable = new TicketThread();
		Thread threadA = new Thread(runnable, "票贩子A");
		Thread threadB = new Thread(runnable, "票贩子B");
		Thread threadC = new Thread(runnable, "票贩子C");

		threadA.start();
		threadB.start();
		threadC.start();
	}
}

执行结果:

票贩子C 执行8
票贩子A 执行10
票贩子B 执行9
票贩子C 执行7
票贩子A 执行5
票贩子B 执行6
票贩子B 执行4
票贩子A 执行4
票贩子C 执行4

票贩子B 执行2
票卖完了
票贩子A 执行1
票卖完了
票贩子C 执行3
票卖完了

分析:
为什么会出现票数为负数?

synchronized关键字

synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。
当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。
注意点: 虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。

synchronized的锁是什么?

  • 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
  • 同步方法的锁:静态方法(类名.class)、非静态方法(this)
  • 同步代码块:自己指定,很多时候也是指定为this或类名.class
package javase.util;

import java.util.Vector;
import java.util.concurrent.ConcurrentMap;

class TicketThread implements Runnable {
	int ticket = 1000;

	@Override
	public void run() {
		while (true) {
			synchronized (this) {
				if (ticket > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					ticket--;
					System.out.println(Thread.currentThread().getName() + ",还剩余" + this.ticket);
				} else {
					break;
				}
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		TicketThread ticketThread = new TicketThread();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
	}
}
package javase.util;

import java.util.Vector;
import java.util.concurrent.ConcurrentMap;

class TicketThread implements Runnable {
	int ticket = 10;

	@Override
	public void run() {
		while (method()) {

		}
	}
	
	public synchronized boolean method(){
		if (ticket > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ticket--;
			System.out.println(Thread.currentThread().getName() + ",还剩余" + this.ticket);
			return true;
		}
		return false;
	}
}

public class TestMain {
	public static void main(String[] args) {
		TicketThread ticketThread = new TicketThread();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
	}
}

Lock类

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{
	int ticket = 100;
	private final ReentrantLock lock = new ReentrantLock();
	public void run(){
		
		while(true){
			try{
				lock.lock();
				if(ticket > 0){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(ticket--);
				}else{
					break;
				}
			}finally{
				lock.unlock();
			}
		}
	}
}

public class ThreadLock {
	public static void main(String[] args) {
		Window t = new Window();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t1.start();
		t2.start();
	}
}

AtomicInteger原子操作类CAS

import java.util.concurrent.atomic.AtomicInteger;

public class TestMain {
	public static void main(String[] args) {
		TicketThread ticketThread = new TicketThread();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
	}
}

class TicketThread implements Runnable {
	// 注意:volatile可不必加
	private static volatile AtomicInteger atomicInteger = new AtomicInteger(10);
	@Override
	public void run() {
		while (true) {
			if(atomicInteger.get() == 0) break;
			System.out.println(Thread.currentThread().getName()+":"+atomicInteger.getAndDecrement());
		}
	}
}

释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

不会释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程。

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

线程通讯

wait()与 notify() 和 notifyAll()

wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll():唤醒正在排队等待资源的所有线程结束等待
注意

  • 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
  • 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
wait()

在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify()或notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

notify()/notifyAll()

在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个/所有线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

综合实战:“生产者-消费者”模型

示例:

package javase.util;

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

class Producer implements Runnable {
	private Message msg ;
	public Producer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			if (x % 2 == 0) {
				this.msg.setTitle("孙悟空");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				this.msg.setContent("俺老孙来也");
			} else {
				this.msg.setTitle("猪八戒");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				this.msg.setContent("散伙回高老庄");
			}
		}
	}
}
class Consumer implements Runnable {
	private Message msg ;
	public Consumer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(this.msg.getTitle() + "  -  " + this.msg.getContent());
		}
	}

}
class Message {
	private String title ;
	private String content ;
	public void setContent(String content) {
		this.content = content;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public String getTitle() {
		return title;
	}
}

public class TestMain {
	public static void main(String[] args) {
		Message message = new Message();
		new Thread(new Producer(message)).start();    // 启动生产者线程
		new Thread(new Consumer(message)).start();    // 启动消费者线程
	}
}

孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也

package javase.util;

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

class Producer implements Runnable {
	private Message msg ;
	public Producer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			if (x % 2 == 0) {
				this.msg.set("孙悟空", "俺老孙来也");
			} else {
				this.msg.set("猪八戒", "散伙回高老庄");
			}
		}
	}
}
class Consumer implements Runnable {
	private Message msg ;
	public Consumer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			System.out.println(this.msg.get());
		}
	}

}
class Message {
	private String title ;
	private String content ;
	public synchronized void set(String title,String content) {
		this.title = title ;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.content = content ;
	}
	public synchronized String get() {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return this.title + "  -  " + this.content ;
	}
}

public class TestMain {
	public static void main(String[] args) {
		Message message = new Message();
		new Thread(new Producer(message)).start();    // 启动生产者线程
		new Thread(new Consumer(message)).start();    // 启动消费者线程
	}
}

孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄

正常的:

package javase.util;

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

class Producer implements Runnable {
	private Message msg ;
	public Producer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			if (x % 2 == 0) {
				this.msg.set("孙悟空", "俺老孙来也");
			} else {
				this.msg.set("猪八戒", "散伙回高老庄");
			}
		}
	}
}
class Consumer implements Runnable {
	private Message msg ;
	public Consumer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			System.out.println(this.msg.get());
		}
	}

}
class Message {
	private String title ;
	private String content ;
	private boolean flag = true ; // 表示生产或消费的形式
	// flag = true:允许生产,但是不允许消费
	// flag = false:允许消费,不允许生产
	public synchronized void set(String title,String content) {
		if (this.flag == false) {	// 无法进行生产,应该等待被消费
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.title = title ;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.content = content ;
		this.flag = false ; // 已经生产过了
		super.notify(); // 唤醒等待的线程
	}
	public synchronized String get() {
		if (this.flag == true) {	// 还未生产,需要等待
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		try {
			return this.title + "  -  " + this.content ;
		} finally {	// 不管如何都要执行
			this.flag = true ; // 继续生产
			super.notify(); // 唤醒等待线程
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		Message message = new Message();
		new Thread(new Producer(message)).start();    // 启动生产者线程
		new Thread(new Consumer(message)).start();    // 启动消费者线程
	}
}

孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄

生产与消费者-商品生产与消费

package javase.util;

public class JavaAPIDemo {
	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		Productor productor = new Productor(clerk);
		new Thread(productor, "生产者").start();
		Consumer consumer = new Consumer(clerk);
		new Thread(consumer, "消费者").start();
	}
}
// 售货员
class Clerk {
	private int product = 0;

	/**
	 * 生产商品
	 */
	public synchronized void  addProduct(){
		// 商品数大于20的时候等待消费者取走
		if (this.product >= 20){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		else {
			this.product++;
			System.out.println("生产者生产了第" + this.product + "个产品");
			// 通知消费者取走
			notifyAll();
		}
	}

	/**
	 * 取出商品
	 */
	public synchronized void  getProduct(){
		// 商品数小于0的时候等待生产者生产
		if (this.product <= 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		else{
			System.out.println("消费者取走了第" +this.product + "个产品");
			this.product--;
			// 通知生产者生产
			notifyAll();
		}
	}
}

/**
 * 生产者
 */
class  Productor implements Runnable {
	Clerk clerk;
	public Productor(Clerk clerk) {
		this.clerk = clerk;
	}
	@Override
	public void run() {
		System.out.println("生产者开始生产产品");
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	}
}

/**
 * 消费者
 */
class  Consumer implements Runnable {
	Clerk clerk;
	public Consumer(Clerk clerk) {
		this.clerk = clerk;
	}
	@Override
	public void run() {
		System.out.println("消费者开始取走产品");
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.getProduct();
		}
	}
}

多线程深入话题

优雅的停止线程

package javase.util;

/**
 * 优雅的停止线程
 */
public class TestMain {
	// 判断线程停止的条件
	private static boolean flag = true;

	public static void main(String[] args) {
		new Thread(()->{
			long num = 0;
			while (flag) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" : num="+ num ++);
			}
		}, "线程A").start();
		// 模拟运行一段时间
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 优雅停止
		flag = false;
	}
}

后台守护线程

Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。
只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。

设置守护线程:public final void setDaemon​(boolean on)
判断是否为守护线程:public final boolean isDaemon()

package javase.util;

/**
 * 后台守护线程:当用户线程完成之后,守护线程关闭
 */
public class TestMain {
	public static void main(String[] args) {
		Thread userThread = new Thread(()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+"正在运行,i = " + i );
			}
		}, "用户线程,完成核心业务");

		Thread daemonThread = new Thread(()->{
			for (int i = 0; i < Integer.MAX_VALUE; i++) {
				System.out.println(Thread.currentThread().getName()+"正在运行,i = " + i );
			}
		}, "守护线程");

		daemonThread.setDaemon(true); // 设置守护线程
		userThread.start();
		daemonThread.start();
	}
}

volatile关键字

在多线程中,volatile关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理。

package javase.util;

class MyThead implements Runnable {
	// 直接内存操作。能更快对变量处理
	private volatile int ticket = 5;

	@Override
	public void run() {
		synchronized (this) {
			while (this.ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "抢到了票,还剩" + this.ticket--);
			}
		}
	}
}

public class TestMain {

	public static void main(String[] args) {
		MyThead ticketThread = new MyThead();
		new Thread(ticketThread, "线程A").start();
		new Thread(ticketThread, "线程B").start();
		new Thread(ticketThread, "线程C").start();
	}
}

面试题:volatile和synchronized区别?

①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

多线程综合案例

数字加减

设计4个线程,两个线程执行加操作,两个线程执行减操作。

package javase.util;

/**
 * 定义操作的资源
 */
class Resource {
	private int num = 0; // 进行加减的数字
	private boolean flag = true; // 加减的切换,flag = true时,表示可以进行加法操作,不可以减法操作;flag = false时,表示不可以进行加法操作,可以减法操作

	/**
	 * 加法
	 * @throws InterruptedException
	 */
	public synchronized void add() throws InterruptedException{
		if(this.flag == false) { // 不可以加法,只能减法操作。先等待
			super.wait();
		}
		Thread.sleep(100);
		this.num++;
		System.out.println("【执行加法操作】"+Thread.currentThread().getName()+", num = " + num);
		this.flag = false;	// 加法操作之后,只能进行减法操作,需设置为false
		this.notifyAll(); // 唤醒多个线程
	}

	public synchronized void sub() throws InterruptedException{
		if(this.flag == true) { // 不可以减法,只能加法操作。先等待
			super.wait();
		}
		Thread.sleep(200);
		this.num--;
		System.out.println("【执行减法操作】"+Thread.currentThread().getName()+", num = " + num);
		this.flag = true; // 减法操作之后,只能进行加法操作,需设置为true
		this.notifyAll();
	}
}

/**
 * 加法操作的线程
 */
class AddThread implements Runnable {
	Resource resource;
	public AddThread(Resource resource) {
		this.resource = resource;
	}
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			try {
				resource.add();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

/**
 * 减法的线程
 */
class SubThread implements Runnable {
	Resource resource;
	public SubThread(Resource resource) {
		this.resource = resource;
	}
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			try {
				resource.sub();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		Resource resource = new Resource();
		AddThread addThread = new AddThread(resource);
		SubThread subThread = new SubThread(resource);
		// 两个加法线程
		new Thread(addThread, "A").start();
		new Thread(addThread, "B").start();

		// 两个减法线程
		new Thread(subThread, "X").start();
		new Thread(subThread, "Y").start();
	}
}

生产电脑

设计一个生产电脑和搬走电脑类,要求生产一台电脑就搬走一台电脑,如果没有新电脑生产出来则搬运工要等待新电脑的生产;如果生产出的电脑没有搬走,则要等待电脑搬走之后再生产,并统计出生产电脑的数量。

package javase.util;


class Producer implements Runnable {
	private Resource resource ;
	public Producer(Resource resource) {
		this.resource = resource ;
	}
	public void run() {
		for (int x = 0 ; x < 50 ; x ++) {
			try {
				this.resource.make();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};
}
class Consumer implements Runnable {
	private Resource resource ;
	public Consumer(Resource resource) {
		this.resource = resource ;
	}
	public void run() {
		for (int x = 0 ; x < 50 ; x ++) {
			try {
				this.resource.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};
}
class Resource {
	private Computer computer ;
	public synchronized void make() throws Exception {
		if (this.computer != null) {	// 已经生产过了
			super.wait();
		}
		Thread.sleep(100);
		this.computer = new Computer("MLDN牌电脑",1.1) ;
		System.out.println("【生产电脑】" + this.computer);
		super.notifyAll();
	}
	public synchronized void get() throws Exception {
		if (this.computer == null) {	// 没有生产过
			super.wait();
		}
		Thread.sleep(10);
		System.out.println("【取走电脑】" + this.computer);
		this.computer = null ; // 已经取走了
		super.notifyAll();
	}
}

class Computer {
	private static int count = 0 ; // 表示生产的个数
	private String name ;
	private double price ;
	public Computer(String name,double price) {
		this.name = name ;
		this.price = price ;
		count ++ ;
	}
	public String toString() {
		return "【第" + count + "台电脑】" + "电脑名字:" + this.name + "、价值:" + this.price;
	}
}

public class TestMain {
	public static void main(String[] args) {
		Resource res = new Resource() ;
		new Thread(new Producer(res)).start();
		new Thread(new Consumer(res)).start();
	}
}

竞拍抢答

实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功者给出成功提示,未抢答成功者给出失败提示。

package javase.util;

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

class MyThread implements Callable<String> {
	private boolean flag = true; // 抢答处理

	@Override
	public String call() throws Exception {
		synchronized (this) { // 数据同步
			if (this.flag == false) { // 抢答成功
				this.flag = true;
				return Thread.currentThread().getName() + "抢答成功!";
			}
			return Thread.currentThread().getName() + "抢答失败!";
		}
	}
}

public class TestMain {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		MyThread mt = new MyThread() ;
		FutureTask<String> taskA = new FutureTask<String>(mt) ;
		FutureTask<String> taskB = new FutureTask<String>(mt) ;
		FutureTask<String> taskC = new FutureTask<String>(mt) ;
		new Thread(taskA,"竞赛者A").start();
		new Thread(taskB,"竞赛者B").start();
		new Thread(taskC,"竞赛者C").start();
		System.out.println(taskA.get());
		System.out.println(taskB.get());
		System.out.println(taskC.get());
	}
}

CAS机制

参考:
漫画:什么是 CAS 机制?
漫画:什么是 CAS 机制?
漫画:什么是 CAS 机制?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值