前言
本文为Java多线程相关知识,Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
本文上接:【多线程】一文搞懂Java多线程,代码示例,清晰明了(上)
五、守护线程
1.守护线程
线程分为用户线程和守护线程
守护线程要在thread.start()之前设置
主线程结束,守护线程自动销毁
虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
如:后台记录日志操作,监控内存,垃圾回收等待等…
代码展示:
package com.wang.多线程; public class DaemonTest implements Runnable { @Override public void run() { while (true) { System.out.println("守护线程守护着你!!!"); } } public static void main(String[] args) { DaemonTest daemonTest = new DaemonTest(); You you = new You(); Thread thread = new Thread(daemonTest); thread.setDaemon(true); // 默认为false,即正常线程 thread.start(); // 守护线程启动 new Thread(you).start(); } } class You implements Runnable { @Override public void run() { for (int i = 0; i < 365; i++) { System.out.println(i + ":<<<<<<<<<<活着!!!>>>>>>>>>>>"); } System.out.println("<<<<<<<<<<<<<<<<<Good By>>>>>>>>>>>>>>>>>"); } }
六、线程同步
概念:线程同步问题指的是多个线程操作同一个资源对象,如果不进行线程同步,将会报错,具体错误是由于内存存储空间中一块内存中存储的信息在同一时刻保持一致,但是被取走后信息会更新,多个线程对于同一个对象进行操作后会导致重复错误。
1.买票问题
线程不安全情况下会出现有人买到第-1张票的问题。
代码展示:
package com.wang.多线程; // 买票问题 public class ThreadSynchronization1 implements Runnable{ private int count = 10; boolean flag = true; public static void main(String[] args) { ThreadSynchronization1 ts = new ThreadSynchronization1(); new Thread(ts, "李白").start(); new Thread(ts, "杜甫").start(); new Thread(ts, "王维").start(); } @Override public void run() { while (flag) { buy(); } } // synchronized同步方法 锁的是this public synchronized void buy() { if (count <= 0) { flag = false; return; } // 模拟延时 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买到了" + count--); } } // 打印 李白买到了10 李白买到了9 李白买到了8 李白买到了7 李白买到了6 李白买到了5 李白买到了4 李白买到了3 李白买到了2 李白买到了1
2.银行取钱问题
线程不安全情况下会出现最终余额出现负值情况的问题。
代码展示:
package com.wang.多线程; public class ThreadSynchronization2{ public static void main(String[] args) { // 创建转户 Account account = new Account(2000, 202209); Bank bank = new Bank(account, 1500, "李白"); Bank bank1 = new Bank(account, 500, "杜甫"); bank.start(); bank1.start(); } } // 转户类 class Account { public int money; public int id; public Account(int money, int id) { this.money = money; this.id = id; } } // 银行:模拟取款 class Bank extends Thread { public Account account; // 转户 public int subMoney; // 取多少钱 public int newMoney; // 现在手里多少钱 public Bank(Account account, int subMoney, String name) { super(name); this.account = account; this.subMoney = subMoney; } // 取钱 @Override public void run() { /** synchronized默认锁的是this,也就是当前对象 * 所以使用同步块指定锁的对象 * 锁的对象是变化的量,即增删改的对象 */ synchronized (account) { // 先判断转户还有没有钱 if (account.money - subMoney <= 0) { System.out.println(this.getName() + "钱不够了,取不了"); return; } // 延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 余额 account.money -= subMoney; // 手里现在的钱 newMoney += subMoney; System.out.println(account.id + "的余额为" + account.money); System.out.println(this.getName() + "手里的钱有" + newMoney); } } } // 打印 202209的余额为500 李白手里的钱有1500 杜甫钱不够了,取不了
3.List集合问题
线程不安全情况下会出现多个线程在同一时间将元素添加到了数组的同一位置导致最终数组的大小并不是期望的情况。
代码展示:
package com.wang.多线程; import java.util.ArrayList; import java.util.List; public class ThreadSynchronization3 { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(() -> { synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 打印List集合的大小 System.out.println(list.size()); } } // 10000
4.同步方法与同步块
对于同步问题,由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制 ,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
同步方法:
public siynchronized void method(int args) {};
synchronized方法控制对对象的访问,每个对象对应一把锁;每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行就独占该锁直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行;
缺陷:若将一个大的方法申明为synchronized 将会影响效率
同步块:
synchronized (Obj ){};
Obj称之为同步监视器;
Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器;
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class;
同步监视器的执行过程:(1)第一个线程访问,锁定同步监视器,执行其中代码;(2)第二个线程访问,发现同步监视器被锁定,无法访问;(3)第一个线程访问完毕,解锁同步监视器;(4)第二个线程访问, 发现同步监视器没有锁,然后锁定并访问。
七、死锁
1.什么是死锁
死锁的定义:多个线程互相抱着对方的需要资源,然后形成僵持。
2.产生死锁的必要条件
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
PS:根据上面的四个死锁必要条件,我们可以想办法避免死锁
代码展示:
package com.wang.多线程; public class DeadLockTest { public static void main(String[] args) { Makeup makeup = new Makeup(0, "李白"); Makeup makeup1 = new Makeup(1, "杜甫"); makeup.start(); makeup1.start(); } } class Lipstick { } class Mirror { } class Makeup extends Thread { // 需要的资源 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); // 选择 int choice; // 使用的人 String name; Makeup(int choice, String name) { this.choice = choice; this.name = name; } @Override public void run() { // 使用 try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } // makeup方法 public void makeup() throws InterruptedException { if (choice == 0) { synchronized (lipstick) { System.out.println(this.name + "获得lipstick的锁"); Thread.sleep(1000); } synchronized (mirror) { System.out.println(this.name + "获得mirror的锁"); } } else { synchronized (mirror) { System.out.println(this.name + "获得mirror的锁"); Thread.sleep(2000); } synchronized (lipstick) { System.out.println(this.name + "获得lipstick的锁"); } } } } // 李白获得lipstick的锁 杜甫获得mirror的锁 杜甫获得lipstick的锁 李白获得mirror的锁
八、lock锁
1.lock与synchronized对比
lock是显示锁(手动开启锁和关闭锁)synchronized是隐式锁,出来作用域自动关闭
lock只有代码块锁,synchronized有方法锁和代码块锁
使用lock锁。Jvm将花费更少的时间来调度线程,性能更好,并且具有更好的扩展性
优先使用顺序:Lock > 同步代码块>同步方法
代码展示:
package com.wang.多线程; import java.util.concurrent.locks.ReentrantLock; public class LockTest_14 implements Runnable{ private int count = 5; boolean flag = true; private ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { LockTest_14 lockTest = new LockTest_14(); new Thread(lockTest, "李白").start(); new Thread(lockTest, "杜甫").start(); new Thread(lockTest, "王维").start(); } @Override public void run() { while (true) { try { // 加锁 lock.lock(); if (count > 0) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count--); } else { break; } }finally { // 解锁 lock.unlock(); } } } } // 打印 5 4 3 2 1
九、线程协作
1.管程法
代码展示:
package com.wang.多线程; public class Cooperation1 { public static void main(String[] args) { Buffer buffer = new Buffer(); new Production(buffer).start(); new Consumption(buffer).start(); } } // 生产者 class Production extends Thread { Buffer buffer; public Production(Buffer buffer) { this.buffer = buffer; } // 生产 @Override public void run() { for (int i = 0; i < 100; i++) { buffer.push(new Product(i)); System.out.println("生产了第" + i + "个产品"); } } } // 消费者 class Consumption extends Thread { Buffer buffer; public Consumption(Buffer buffer) { this.buffer = buffer; } // 消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了第" + buffer.pop().id + "个产品"); } } } // 产品 class Product { int id; public Product(int id) { this.id = id; } } // 缓冲区 class Buffer { // 需要一个容器大小 Product[] products = new Product[10]; // 计数 int count = 0; // 生产者放入产品方法 public synchronized void push(Product p) { // 判断 如果产品满了 if (count == products.length) { // 通知消费者来消费 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 如果没有满 products[count] = p; count++; // 通知消费者消费 this.notifyAll(); } // 消费者方法 public synchronized Product pop() { // 判断消费者能否消费 if (count == 0) { // 等待生产者生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 如果可以消费 count--; Product product = products[count]; // 消费完了,通知生产 this.notifyAll(); return product; } }
2.信号灯法
代码展示:
package com.wang.多线程; public class Cooperation2_16 { public static void main(String[] args) { TaoBao taoBao = new TaoBao(); new Production1(taoBao).start(); new Consumption1(taoBao).start(); } } class Production1 extends Thread { TaoBao taoBao; public Production1(TaoBao taoBao) { this.taoBao = taoBao; } @Override public void run() { for (int i = 0; i < 20; i++) { this.taoBao.push(i); } } } class Consumption1 extends Thread { TaoBao taoBao; public Consumption1(TaoBao taoBao) { this.taoBao = taoBao; } @Override public void run() { for (int i = 0; i < 20; i++) { this.taoBao.buy(); } } } class TaoBao { int id; boolean flag = true; // 标志位 // 生产 public synchronized void push(int id) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产了第" + id + "件物品"); this.notifyAll(); // 通知消费者购买 this.id = id; this.flag = !this.flag; } // 消费 public synchronized void buy() { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("用户购买了第" + this.id + "件物品"); // 通知生产 this.notifyAll(); this.flag = !this.flag; } }
十、线程池
1.线程池
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
使用线程池可以提高响应速度(减少了创建新线程的时间),降低资源消耗(重复利用线程池中线程,不需要每次都创建),便于线程管理。
参数:corePoolSize:核心池的大小;maximumPoolSize:最大线程数;keepAliveTime:线程没有任务时最多保持多长时间后会终止。
代码展示:
package com.wang.多线程; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool implements Runnable{ public static void main(String[] args) { // 创建服务线程池(参数即为线程池大小) ExecutorService executorService = Executors.newFixedThreadPool(4); executorService.execute(new ThreadPool()); executorService.execute(new ThreadPool()); executorService.execute(new ThreadPool()); executorService.execute(new ThreadPool()); // 关闭线程池 executorService.shutdown(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
后记
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~