多线程学习(第三天)线程间通信
一、volatile 与 synchronized
java多线程支持每个线程拥有对象的拷贝,这样每个线程内部就是独立的java运行环境。但是这样存在问题,共享内存中的对象或变量,在线程内对其拷贝进行修改后,其他线程读取的数据则为脏数据。
volatile:作用就是告诉程序,当线程修改拷贝后,需要将修改后的内容同步到内存中,其他线程读取数据时,需要到内存中同步。(缺点:影响效率)
synchronized:可以修饰方法或代码块,保证多线程的情况,同一时间只允许一个线程执行该代码。
当一个线程获取到锁之后,其他线程进入同步队列,线程状态编程BLOCKED。当线程执行完成后,会进行释放操作,并唤醒所有等待的线程。
代码验证:
package com.example.thread.state; public class MyThread implements Runnable { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread one = new Thread(myThread, "线程1"); Thread two = new Thread(myThread, "线程2"); Thread three = new Thread(myThread, "线程3"); Thread four = new Thread(myThread, "线程4"); one.start(); two.start(); three.start(); four.start(); while(true) { System.out.println("线程1状态:"+one.getState()); System.out.println("线程2状态:"+two.getState()); System.out.println("线程3状态:"+three.getState()); System.out.println("线程4状态:"+four.getState()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void run() { synchronized (this) { try { System.out.println(Thread.currentThread().getName()+"抢到了锁"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"释放锁"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果状态:
线程1状态:RUNNABLE
线程2状态:RUNNABLE
线程3状态:RUNNABLE
线程4状态:RUNNABLE
线程2抢到了锁
线程1状态:BLOCKED
线程2状态:TIMED_WAITING
线程3状态:BLOCKED
线程4状态:BLOCKED
线程1状态:BLOCKED
线程2状态:TIMED_WAITING
线程3状态:BLOCKED
线程4状态:BLOCKED
线程1状态:BLOCKED
线程2状态:TIMED_WAITING
线程3状态:BLOCKED
线程4状态:BLOCKED
线程2释放锁
线程4抢到了锁
线程1状态:BLOCKED
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TIMED_WAITING
线程1状态:BLOCKED
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TIMED_WAITING
线程1状态:BLOCKED
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TIMED_WAITING
线程1状态:BLOCKED
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TIMED_WAITING
线程4释放锁
线程1抢到了锁
线程1状态:TIMED_WAITING
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TERMINATED
线程1状态:TIMED_WAITING
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TERMINATED
线程1状态:TIMED_WAITING
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TERMINATED
线程1状态:TIMED_WAITING
线程2状态:TERMINATED
线程3状态:BLOCKED
线程4状态:TERMINATED
线程1释放锁
线程3抢到了锁
线程1状态:TERMINATED
线程2状态:TERMINATED
线程3状态:TIMED_WAITING
线程4状态:TERMINATED
线程1状态:TERMINATED
线程2状态:TERMINATED
线程3状态:TIMED_WAITING
线程4状态:TERMINATED
线程1状态:TERMINATED
线程2状态:TERMINATED
线程3状态:TIMED_WAITING
线程4状态:TERMINATED
线程1状态:TERMINATED
线程2状态:TERMINATED
线程3状态:TIMED_WAITING
线程4状态:TERMINATED
线程3释放锁
线程1状态:TERMINATED
线程2状态:TERMINATED
线程3状态:TERMINATED
线程4状态:TERMINATED
二、等待/通知机制
等待(wait):
wait的使用前提是,线程必须获取当前对象的锁。所以wait的使用必须在synchronized中。
通知(notify):
notify唤醒一个等待当前锁的线程。(多个线程等待的时候,随机唤醒一个)。synchronized中使用。
wait和sleep比较
wait:当前线程会释放锁
sleep:不释放锁
下面一洗车喷漆为例
public class Car { /** * INIT:初始化 * WASHED: 洗完 * COLORED:染完色 */ private String state; public Car(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
package com.example.thread.wait.carworker; import com.example.thread.SleepUtil; /** * 给车上色 */ public class CarColor implements Runnable{ Car car; public CarColor(Car c) { this.car = c; } @Override public void run() { synchronized (car) { while (true) { System.out.println(Thread.currentThread().getName() + "准备上色"); if ("WASHED".equals(car.getState())) { System.out.println("上色中。。。。。。"); SleepUtil.sleep(3000); car.setState("COLORED"); car.notify(); System.out.println(Thread.currentThread().getName() + "完成上色"); } else { try { car.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } SleepUtil.sleep(1000); } } } }
/** * 洗车 */ public class CarWash implements Runnable { private Car car; public CarWash(Car c) { this.car = c; } @Override public void run() { synchronized (car) { while(true) { System.out.println(Thread.currentThread().getName() + "准备洗车"); if ("INIT".equals(car.getState()) || "COLORED".equals(car.getState())) { System.out.println("洗车中。。。。。。"); SleepUtil.sleep(3000); car.setState("WASHED"); car.notify(); System.out.println(Thread.currentThread().getName() + "完成洗车"); } else { System.out.println(Thread.currentThread().getName() + "等待洗车"); try { car.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } SleepUtil.sleep(1000); } } } }
import com.example.thread.SleepUtil; public class CarWorker { public static void main(String[] args) { Car car = new Car("INIT"); CarWash carWash = new CarWash(car); CarColor carColor = new CarColor(car); Thread a = new Thread(carWash, "洗车位1"); Thread b = new Thread(carWash, "洗车位2"); Thread c = new Thread(carColor, "喷漆位"); a.start(); b.start(); c.start(); while(true) { SleepUtil.sleep(1000); System.out.println("---------------"+a.getName()+"->"+a.getState()); System.out.println("---------------"+b.getName()+"->"+b.getState()); System.out.println("---------------"+c.getName()+"->"+c.getState()); } } }
执行结果:
洗车位1准备洗车 洗车中。。。。。。 ---------------洗车位1->TIMED_WAITING ---------------洗车位2->BLOCKED ---------------喷漆位->BLOCKED ---------------洗车位1->TIMED_WAITING ---------------洗车位2->BLOCKED ---------------喷漆位->BLOCKED 洗车位1完成洗车 ---------------洗车位1->TIMED_WAITING ---------------洗车位2->BLOCKED ---------------喷漆位->BLOCKED 洗车位1准备洗车 洗车位1等待洗车 洗车位2准备洗车 洗车位2等待洗车 喷漆位准备上色 上色中。。。。。。 ---------------洗车位1->WAITING ---------------洗车位2->WAITING ---------------喷漆位->TIMED_WAITING ---------------洗车位1->WAITING ---------------洗车位2->WAITING ---------------喷漆位->TIMED_WAITING ---------------洗车位1->WAITING ---------------洗车位2->WAITING ---------------喷漆位->TIMED_WAITING 喷漆位完成上色 ---------------洗车位1->BLOCKED ---------------洗车位2->WAITING ---------------喷漆位->TIMED_WAITING 喷漆位准备上色 ---------------洗车位1->TIMED_WAITING ---------------洗车位2->WAITING ---------------喷漆位->WAITING 洗车位1准备洗车 洗车中。。。。。。 ---------------洗车位1->TIMED_WAITING ---------------洗车位2->WAITING ---------------喷漆位->WAITING ---------------洗车位1->TIMED_WAITING ---------------洗车位2->WAITING ---------------喷漆位->WAITING ---------------洗车位1->TIMED_WAITING ---------------洗车位2->WAITING ---------------喷漆位->WAITING 洗车位1完成洗车 ---------------洗车位1->TIMED_WAITING ---------------洗车位2->BLOCKED ---------------喷漆位->WAITING 洗车位1准备洗车 洗车位1等待洗车 ---------------洗车位1->WAITING ---------------洗车位2->TIMED_WAITING ---------------喷漆位->WAITING 洗车位2准备洗车 洗车位2等待洗车 ---------------洗车位1->WAITING ---------------洗车位2->WAITING ---------------喷漆位->WAITING Process finished with exit code -1
经典范式:
等待方遵循如下原则。 1)获取对象的锁。 2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。 3)条件满足则执行对应的逻辑。 对应的伪代码如下。 synchronized(对象) { while(条件不满足) { 对象.wait(); } 对 应的处理逻辑 } 通知方遵循如下原则。 1)获得对象的锁。 2)改变条件。 3)通知所有等待在对象上的线程。 对应的伪代码如下。 synchronized(对象) { 改变条件 对象.notifyAll(); }
三、Thread.join
当前线程里如果存在其他线程A调用join()方法后,那么当前线程会等待线程A执行完之后,继续执行。
场景:在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将可能早于子线程结束。如果主线程需要知道子线程的执行结果时,就需要等待子线程执行结束了。主线程可以sleep(xx),但这样的xx时间不好确定,因为子线程的执行时间不确定,join()方法比较合适这个场景。
import com.example.thread.SleepUtil; public class JoinThread extends Thread{ Thread thread; public Thread getThread() { return thread; } public void setThread(Thread thread) { this.thread = thread; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "待执行"); SleepUtil.sleep(1000); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行"); } }
public class Main { public static void main(String[] args) { Thread threadPre = new JoinThread(); for (int i = 0; i < 10; i++) { JoinThread joinThread = new JoinThread(); joinThread.setThread(threadPre); joinThread.start(); threadPre = joinThread; } System.out.println("ENDING......"); } }
执行结果:
Thread-1执行 Thread-2执行 Thread-3执行 Thread-4执行 Thread-5执行 Thread-6执行 Thread-7执行 Thread-8执行 Thread-9执行 Thread-10执行
状态变化:
状态验证代码:
public class JoinThread extends Thread{ Thread thread; public Thread getThread() { return thread; } public void setThread(Thread thread) { this.thread = thread; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "待执行"); try { if (thread != null) thread.join(); SleepUtil.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行"); } }
public static void main(String[] args) { JoinThread joinThread1 = new JoinThread(); JoinThread joinThread2 = new JoinThread(); JoinThread joinThread3 = new JoinThread(); joinThread2.setThread(joinThread1); joinThread3.setThread(joinThread2); joinThread1.start(); joinThread2.start(); joinThread3.start(); while (true) { System.out.println(joinThread1.getName()+"->"+joinThread1.getState()); System.out.println(joinThread2.getName()+"->"+joinThread2.getState()); System.out.println(joinThread3.getName()+"->"+joinThread3.getState()); SleepUtil.sleep(500); } }
运行结果:
Thread-0->RUNNABLE Thread-1->RUNNABLE Thread-2->RUNNABLE Thread-0待执行 Thread-1待执行 Thread-2待执行 Thread-0->TIMED_WAITING Thread-1->WAITING Thread-2->WAITING Thread-0->TIMED_WAITING Thread-1->WAITING Thread-2->WAITING Thread-0->TIMED_WAITING Thread-1->WAITING Thread-2->WAITING Thread-0执行 Thread-0->TERMINATED Thread-1->TIMED_WAITING Thread-2->WAITING Thread-0->TERMINATED Thread-1->TIMED_WAITING Thread-2->WAITING Thread-0->TERMINATED Thread-1->TIMED_WAITING Thread-2->WAITING Thread-0->TERMINATED Thread-1->TIMED_WAITING Thread-2->WAITING Thread-1执行 Thread-0->TERMINATED Thread-1->TERMINATED Thread-2->TIMED_WAITING
四、ThreadLocation
线程私有局部变量的存储器,确保每个线程处理自己变量的副本,互不干扰。
代码演示:
public class ThreadLocationThread implements Runnable { ThreadLocal<Long> longThreadLocal = new ThreadLocal<Long>(); Long aaa = 0L; @Override public void run() { if (longThreadLocal.get() == null) { longThreadLocal.set(0L); } longThreadLocal.set(longThreadLocal.get()+1); aaa++; System.out.println("longThreadLocal:"+longThreadLocal.get()); System.out.println(aaa); } public static void main(String[] args) { ThreadLocationThread threadLocationThread = new ThreadLocationThread(); new Thread(threadLocationThread).start(); new Thread(threadLocationThread).start(); new Thread(threadLocationThread).start(); new Thread(threadLocationThread).start(); new Thread(threadLocationThread).start(); new Thread(threadLocationThread).start(); } }
运行结果:
longThreadLocal:1 --互不干扰 longThreadLocal:1 2 2 longThreadLocal:1 longThreadLocal:1 5 5 longThreadLocal:1 6 longThreadLocal:1 6
ThreadLocation原理
查看源码ThreadLocal的set方法:
public void set(T value) { Thread t = Thread.currentThread();
//ThreadLocalMap为ThreadLocation内部类 ThreadLocalMap map = getMap(t); //获取当前线程的Map对象 if (map != null)
//set方法为Thread当前对象的ThreadLocal实例(每个线程实例都不一样,所以map中存储的值保证每个线程只能存储自己对象的value),value为要存储的值 map.set(this, value); else createMap(t, value); }
使用场景:
- 处理较为复杂的业务时,使用ThreadLocal代替参数的显示传递。
- ThreadLocal可以用来做数据库连接池保存Connection对象,这样就可以让线程内多次获取到的连接是同一个了(Spring中的DataSource就是使用的ThreadLocal)。
- 管理Session会话,将Session保存在ThreadLocal中,使线程处理多次处理会话时始终是一个Session。
五、应用实例
等待超时模式
调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果
import com.example.thread.SleepUtil; import java.util.ArrayList; import java.util.Date; import java.util.List; public class WaitTimeoutThread implements Runnable { List<String> list = new ArrayList<>(); public static void main(String[] args) { WaitTimeoutThread waitTimeoutThread = new WaitTimeoutThread(); new Thread(waitTimeoutThread).start(); new Thread(waitTimeoutThread).start(); new Thread(waitTimeoutThread).start(); new Thread(waitTimeoutThread).start(); waitTimeoutThread.testWait(); } public String get() { if (list.size() <= 0) { return null; } String result = list.get(0); list.remove(0); return result; } @Override public void run() { while(true) { testRead(10000); SleepUtil.sleep(5000); } } public synchronized void testRead(long timeout1) { System.out.println(Thread.currentThread().getName()+"->"+new Date()+"取数据开始"); long start = System.currentTimeMillis(); //等待30秒 Long waitLong = timeout1; long remaining = 1000L; boolean timeout = false; long end = start + waitLong; String result = get(); while(result == null && !timeout) { try { //休息1秒钟 wait(remaining); result = get(); } catch (InterruptedException e) { e.printStackTrace(); } timeout = System.currentTimeMillis() > end; if (timeout) { result = "超时了"; } } System.out.println(Thread.currentThread().getName()+"->"+new Date()+result); } public String testWait() { while (true) { SleepUtil.sleep(3000); this.list.add(System.currentTimeMillis() + ""); System.out.println("------------------------------>"+new Date()+"生成一个数据"); } } }
结果:
Thread-0->Thu May 13 14:22:05 CST 2021取数据开始 Thread-3->Thu May 13 14:22:05 CST 2021取数据开始 Thread-2->Thu May 13 14:22:05 CST 2021取数据开始 Thread-1->Thu May 13 14:22:05 CST 2021取数据开始 ------------------------------>Thu May 13 14:22:08 CST 2021生成一个数据 Thread-3->Thu May 13 14:22:08 CST 20211620886928046 ------------------------------>Thu May 13 14:22:11 CST 2021生成一个数据 Thread-0->Thu May 13 14:22:11 CST 20211620886931063 Thread-3->Thu May 13 14:22:13 CST 2021取数据开始 ------------------------------>Thu May 13 14:22:14 CST 2021生成一个数据 Thread-3->Thu May 13 14:22:14 CST 20211620886934063 Thread-1->Thu May 13 14:22:15 CST 2021超时了 Thread-2->Thu May 13 14:22:15 CST 2021超时了 Thread-0->Thu May 13 14:22:16 CST 2021取数据开始 ------------------------------>Thu May 13 14:22:17 CST 2021生成一个数据 Thread-0->Thu May 13 14:22:17 CST 20211620886937064 Thread-3->Thu May 13 14:22:19 CST 2021取数据开始 ------------------------------>Thu May 13 14:22:20 CST 2021生成一个数据 Thread-3->Thu May 13 14:22:20 CST 20211620886940066 Thread-1->Thu May 13 14:22:20 CST 2021取数据开始 Thread-2->Thu May 13 14:22:20 CST 2021取数据开始
六、线程池技术及示例
频繁的创建线程,销毁线程是比较耗费系统资源。
线程池技术能够很好地解决这个问题,它预先创建了若干数量的线程,并且不能由用户直接对线程的创建进行控制,在这个前提下重复使用固定或较为固定数目的线程来完成任务的执行。
这样做的好处是,一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量任务的提交能够平缓的劣化
线程池例子代码:
import com.example.thread.SleepUtil; import java.util.*; import java.util.concurrent.atomic.AtomicLong; public class DefaultThreadPool<Job extends JobService> implements ThreadPool<Job> { //线程池最大线程数 private static final int MAX_WORKER_NUMBERS = 10; //线程池默认线程数 private static final int DEFAULT_WORKER_NUMBERS = 5; //线程池最小线程数 private static final int MIN_WORKER_NUMBERS = 1; // 线程编号生成 private AtomicLong threadNum = new AtomicLong(); //工作线程队列 private final LinkedList<Job> jobs = new LinkedList<>(); //工作者线程队列 private final List<Worker> workers = Collections.synchronizedList(new ArrayList<>()); public DefaultThreadPool(int num){ initWorkers(num); } @Override public void execute(Job job) { synchronized (jobs) { jobs.add(job); jobs.notify(); } } @Override public void shutdown() { for(Worker worker: workers) { worker.shutdown(); } } @Override public void addWorkers(int num) { synchronized (jobs) { initWorkers(num); } } @Override public void removeWorkers(int num) { synchronized (jobs) { for (Worker worker : workers) { worker.shutdown(); workers.remove(worker); } } } @Override public int getJobSize() { return jobs.size(); } /** * 初始化线程池,可用线程 * @param num */ public void initWorkers(int num) { for (int i = 0;i < num; i++) { Worker worker = new Worker(); Thread thread = new Thread(worker, "Thread-Worker-"+threadNum.incrementAndGet()); this.workers.add(worker); thread.start(); } } /** * 负责消费任务 */ class Worker implements Runnable { //是否工作(线程同步) private volatile boolean running = true; @Override public void run() { while(running) { Job job = null; synchronized (jobs) { if (jobs.isEmpty()) { //任务列表为空 try { jobs.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } job = jobs.removeFirst(); } if (job != null) { //???????? job.run(); System.out.println(Thread.currentThread().getName()+"开始执行"); job.exe(); } } } public void shutdown() { running = false; } } public static void main(String[] args) { ThreadPool pool = new DefaultThreadPool(5); for(int i = 0;i < 110;i++) { SleepUtil.sleep(3000); String tmp = i+""; pool.execute(() -> { System.out.println("hello"+tmp); }); } } }
public interface JobService { public void exe(); }
public interface ThreadPool<Job extends JobService> { //执行一个job void execute(Job job); //关闭线程池 void shutdown(); //增加线程 void addWorkers(int num); //较少线程 void removeWorkers(int num); //获得正在等待的执行的线程数量 int getJobSize(); }
执行效果:
Connected to the target VM, address: '127.0.0.1:14108', transport: 'socket' Thread-Worker-1开始执行 hello0 Thread-Worker-2开始执行 hello1 Thread-Worker-3开始执行 hello2 Thread-Worker-4开始执行 hello3 Thread-Worker-5开始执行 hello4 Thread-Worker-1开始执行 hello5 Thread-Worker-2开始执行 hello6 Thread-Worker-3开始执行 hello7 Thread-Worker-4开始执行 hello8 Thread-Worker-5开始执行 hello9