【Java】MultiThread 多线程 Re01
学习参考:
https://www.bilibili.com/video/BV1ut411T7Yg
一、线程创建的四种方式:
1、集成线程类
/** * 使用匿名内部类实现子线程类,重写Run方法 */ static class CustomThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("custom subThread2 is running " + new Date().getTime()); } } }
执行
Thread thread2 = new CustomThread(); thread.start();
2、实现Runnable接口
/** * 或者重写Runnable接口完成 */ static class RunnableImpl implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) System.out.println("custom RunnableImpl is running " + new Date().getTime()); } }
执行
Thread thread2 = new Thread(new RunnableImpl(), "custom RunnableThread"); thread2.start();
3、实现Callable接口
/** * 实现Callable接口方式 */ static class CustomCallable implements Callable<String> { @Override public String call() { for (int i = 0; i < 10; i++) System.out.println(Thread.currentThread().getName() + " execute" + new Date().getTime() + " loopCount " + i); return "CustomCallable execute finish"; } }
执行代码
Callable接口
FutureTask<String> futureTask = new FutureTask<>(new CustomCallable()); Thread thread = new Thread(futureTask, "futureTask"); thread.start(); for (int i = 0; i < 10; i++) System.out.println("main thread is running " + System.currentTimeMillis()); boolean stopFlag = false; if (stopFlag) { // 可以中断执行 futureTask.cancel(stopFlag); } // boolean cancelled = futureTask.isCancelled(); try { // 判断是否执行完成 if (futureTask.isDone()) { // 如果执行完成,可以获取Callable返回的结果 String s = futureTask.get(); System.out.println("thread execute finish, result => " + s); } } catch (Exception exception) { exception.printStackTrace(); }
4、使用线程池,只需要提供Runnable接口的实现实例
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); fixedThreadPool.execute(() -> { for (int i = 0; i < 10; i++) System.out.println("custom Runnable-N-Impl is running " + new Date().getTime()); });
四种线程方式的总结:
接口 和 继承Thread类比较:
- 接口适合多个相同的程序代码的线程 共享一个资源 - 接口避免了Java单继承的局限性 - 接口代码可以被多个线程共享,代码和线程是独立的 - 线程池只能放入实现Runnable或者Callable接口的线程,不能直接放入继承Thread的类
Java程序运行至少有几个线程?
至少两个线程,一个Main主线程,一个GC垃圾回收线程
3、Runnable 和 Callable接口的比较
相同点: - 都是接口定义 - 都可以用来编写多线程程序 - 都需要调用Thread.start()来启动线程
不同点: - Runnable是重写run方法,该方法的返回类型是void,和Thread一样,不能返回结果 - Callable是重写call方法,该方法的返回类型通过泛型决定,默认Object 在FutureTask对象的get方法返回 - Callable允许使用FutureTask对象的cancel方法,取消执行,Runnable则没有对应的方法可以取消 注意事项: FutureTask对象的get方法会阻塞Main线程,直到获取方法结果获取
4、线程生命周期
- 创建阶段 Thread对象已经被new分配内存资源 - 就绪阶段 Thread对象调用start()方法 JVM为线程对象创建方法栈和程序计数器,等待线程调度器调度 - 运行阶段 就绪状态的线程获取CPU资源 开始运行run方法 - 阻塞阶段 1、主动调用sleep方法 主动放弃CPU资源,该线程线程阻塞 2、调用了阻塞式的IO方法 知道该方法返回之前该线程一直阻塞 3、获取同步锁(同步监视器)但是该同步锁被其他线程所持有 4、线程正在等待某个通知 (调用了notify方法) 5、调用线程挂起方法suspend(),该方法容易造成死锁,避免使用 - 死亡阶段 1、run() 或 call() 方法执行完成,线程结束 2、线程抛出未捕获的Exception或者Error 3、调用该线程stop()方法结束该线程,但是容易早曾死锁,避免使用
线程安全问题:
售票代码实例:
public class ThreadSecurity { public static final String TICKET_WINDOW_1 = "12306 TicketWindow1"; public static final String TICKET_WINDOW_2 = "12306 TicketWindow2"; public static final String TICKET_WINDOW_3 = "12306 TicketWindow3"; static class Ticket implements Runnable { private int ticketTotal = 100; @Override public void run() { while (true) { if (ticketTotal > 0) { try { Thread.sleep(100); } catch (Exception exception) { exception.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + " 剩余票数 " + ticketTotal); ticketTotal --; } } } }
/** * 线程安全问题 * 1、多个线程同事执行写操作,需要考虑线程同步 * 2、对于全局变量不需要写入操作,仅读取,该变量线程安全 * * - 代码执行过程,同一个变量值多个线程执行好几回 * - 出现小于1的情况 */ public void threadProblem() { Ticket ticket = new Ticket(); Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1); Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2); Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3); ticketThread1.start(); ticketThread2.start(); ticketThread3.start(); }
public static void main(String[] args) { new ThreadSecurity().threadProblem();
}
}
执行后,会出现三个窗口都在售卖同一张票的情况
或者票已经卖完了,窗口任然在售卖的问题
二、解决线程安全的方式:
/** * 线程安全问题的解决办法: * 线程修改共享资源的时候其他线程必须等待当前执行的线程修改完逼同步之后,才能抢夺CPU资源执行对应的操作 * 这样保证数据的同步性,解决线程不安全的现象 * * Java提供了7种线程同步机制 * 1、同步代码块 * 2、同步方法 * 3、同步锁 ReenreantLock * 4、特殊域变量 volatile * 5、局部变量 ThreadLocal * 6、阻塞队列 LinkedBlockingQueue * 7、原子变量 Atomic */
1、使用同步代码块来解决:
static class TicketLocked implements Runnable { private int ticketTotal = 100; private Object lockKey = new Object(); @Override public void run() { while (true) { // 使用同步代码块 需要一个锁对象,线程执行必须通过【开锁】的方式执行代码块内容,以保证变量写入正确 synchronized (lockKey) { if (ticketTotal > 0) { try { Thread.sleep(100); } catch (Exception exception) { exception.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + " 剩余票数 " + ticketTotal); ticketTotal --; } } } } }
执行部分:
public void threadSync() { TicketLocked ticket = new TicketLocked(); Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1); Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2); Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3); ticketThread1.start(); ticketThread2.start(); ticketThread3.start(); }
2、使用同步方法解决
原理和同步代码块的原理是一致的锁对象不再使用随意变量,而是this当前对象
static class TicketLocked2 implements Runnable { private int ticketTotal = 100; @Override public void run() { while (true) { saleTicket(); } } /** * 使用 synchronized 修饰方法 * 原理和 synchronized代码块是一样的 * 只是同步锁这个对象直接使用的是当前对象 this * 如果是static方法,则是这个类对象 this.getClass() */ private synchronized void saleTicket() { if (ticketTotal > 0) { try { Thread.sleep(100); } catch (Exception exception) { exception.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + " 剩余票数 " + ticketTotal); ticketTotal --; } } }
执行部分
public void threadSyncBySyncMethod() { TicketLocked2 ticket = new TicketLocked2(); Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1); Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2); Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3); ticketThread1.start(); ticketThread2.start(); ticketThread3.start(); }
3、使用锁对象实现
static class TicketLocked3 implements Runnable { private int ticketTotal = 100; /** * 参数:是否公平 * true 公平锁 多个线程公平拥有执行权 * false 独占锁 当线程占用时直到释放,其他线程才能使用 */ private final Lock lock = new ReentrantLock(true); @Override public void run() { while (true) { // 上锁和解锁必须都要执行 lock.lock(); try { if (ticketTotal > 0) { try { Thread.sleep(100); } catch (Exception exception) { exception.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + " 剩余票数 " + ticketTotal); ticketTotal --; } } catch (Exception exception) { exception.printStackTrace(); } finally { lock.unlock(); } } } }
执行部分:
/** * 使用同步锁解决 * 发现同步锁线程执行是均等机会的, */ public void threadSyncBySyncLock() { TicketLocked3 ticket = new TicketLocked3(); Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1); Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2); Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3); ticketThread1.start(); ticketThread2.start(); ticketThread3.start(); }
安全问题的总结:
/** * 线程安全问题 * 总结: * synchronized & lock对象的区别: * 1、synchronized 关键字基于JVM层面上的控制, Lock锁对象是程序编码上的控制 * 2、synchronized自动上锁解锁,Lock锁对象需要手动编写,且解锁必须释放(存在死锁的风险), 同样,使用Lock对象可以在编码上灵活调整 * 3、synchronized属于独占锁,可重入,不可中断, Lock锁可重入,可判断 可公平 * 4、Lock适用场景更为灵活,例如部分需要同步的代码,非全部代码都需要同步 * */
三、死锁问题:
/** * 死锁问题 * * 死锁产生的条件 * 1、互斥 * 2、不可剥夺 * 3、请求与保持关系 * 4、循环等待条件 * * 处理死锁问题 * 1、预防 通过限制条件破坏上述条件的产生 * 2、避免 * 3、检查死锁 * 4、解除死锁 * */
死锁案例:
package cn.cloud9.test.multithread; /** * 死锁问题 * * 死锁产生的条件 * 1、互斥 * 2、不可剥夺 * 3、请求与保持关系 * 4、循环等待条件 * * 处理死锁问题 * 1、预防 通过限制条件破坏上述条件的产生 * 2、避免 * 3、检查死锁 * 4、解除死锁 * */ public class DeadLock { private static class DeadRunnable implements Runnable { private boolean flag; // 是静态的 private static Object lockObj1 = new Object(); private static Object lockObj2 = new Object(); public DeadRunnable(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { // 线程1执行 synchronized (lockObj1) { System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj1 开始请求lockObj2"); try { Thread.sleep(1000); } catch (Exception exception) { exception.printStackTrace(); } synchronized (lockObj2) { System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj2 & lockObj1"); } } } else { // 线程2执行 synchronized (lockObj2) { System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj2 开始请求lockObj1"); try { Thread.sleep(1000); } catch (Exception exception) { exception.printStackTrace(); } synchronized (lockObj1) { System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj1 & lockObj2"); } } } } } }
执行部分:
public static void main(String[] args) { DeadRunnable deadRunnable1 = new DeadRunnable(true); DeadRunnable deadRunnable2 = new DeadRunnable(false); Thread thread1 = new Thread(deadRunnable1, "deadRunnable1"); Thread thread2 = new Thread(deadRunnable2, "deadRunnable2"); thread1.start(); thread2.start(); }
执行后的结果可以看到,锁对象1和锁对象2在线程1和2都上过锁了,
执行到二次上锁的代码块的时候,锁对象没有释放,则线程1和2都发生阻塞,程序既不停止也不结束执行