• 多线程学习


    多线程的实现方式

    1. 继承Thread方法
    2. 实现Runable接口
    3. 实现Callable接口

    1.继承Thread方法 

    创建一个类继承Thread类,重写run方法

    通过多态创建一个子线程,子线程调用start方法

    1. //创建线程方式一:继承Thread类,重现run方法
    2. public class TestThread1 extends Thread{
    3. @Override
    4. public void run(){
    5. for (int i=0;i<20;i++){
    6. System.out.println(currentThread().getName()+"执行"+i);
    7. }
    8. }
    9. public static void main(String[] args) {
    10. //创建一个子线程
    11. Thread thread = new TestThread1();
    12. //线程启动
    13. thread.start();
    14. for (int i=0;i<200;i++){
    15. System.out.println(currentThread().getName()+"执行"+i);
    16. }
    17. }
    18. }

    2.实现Runnable接口

    创建一个类实现Runnable接口

    重写run方法

    创建runnable接口实现类对象

    创建Thread类对象,通过线程对象来开启我们的线程

    1. // 创建线程的方式2:实现runanable接口,重新写run方法,执行线程需要丢入runnable接口实现类,调用start方法
    2. public class TestThread3 implements Runnable{
    3. @Override
    4. public void run() {
    5. //run方法线程体
    6. for (int i=0 ; i<100; i++){
    7. System.out.println(currentThread().getName()+":"+i);
    8. }
    9. }
    10. public static void main(String[] args) {
    11. //创建runnable接口的实现类对象
    12. TestThread3 testThread3 = new TestThread3();
    13. //创建线程对象,通过线程对象来开启我们的线程,代理
    14. Thread thread = new Thread(testThread3,"子线程1");
    15. thread.start();
    16. currentThread().setName("主线程");
    17. for (int i = 0; i < 100; i++) {
    18. System.out.println(currentThread().getName()+":"+i);
    19. }
    20. }
    21. }

    小结

    • 继承Thread类
      • 子类继承Thread类具有多线程能力
      • 启动线程:子类对象.start()
      • 不建议使用:避免OOP单继承局限性
    • 实现Runnable接口
      • 实现接口Runnable具有多线程能力
      • 启动线程:传入目标对象+Thread对象.start()
      • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

     并发线程遇到数据紊乱问题

    1. //多个线程同时操作一个对象
    2. //买火车票的例子
    3. //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
    4. public class TestThread4 implements Runnable{
    5. //票数
    6. private int sum = 20;
    7. @Override
    8. public void run() {
    9. while (true){
    10. if (sum<=0){
    11. break;
    12. }
    13. try {
    14. Thread.sleep(100);
    15. } catch (InterruptedException e) {
    16. throw new RuntimeException(e);
    17. }
    18. System.out.println(Thread.currentThread().getName()+"拿到了第"+sum--+"票");
    19. }
    20. }
    21. public static void main(String[] args) {
    22. TestThread4 testThread4 = new TestThread4();
    23. new Thread(testThread4,"线程1").start();
    24. new Thread(testThread4,"线程2").start();
    25. }
    26. }
    27. 线程1拿到了第19
    28. 线程2拿到了第20
    29. 线程1拿到了第17
    30. 线程2拿到了第18
    31. 线程1拿到了第16
    32. 线程2拿到了第15
    33. 线程2拿到了第14
    34. 线程1拿到了第13
    35. 线程1拿到了第12
    36. 线程2拿到了第11
    37. 线程1拿到了第9
    38. 线程2拿到了第10
    39. 线程1拿到了第8
    40. 线程2拿到了第8
    41. 线程2拿到了第7
    42. 线程1拿到了第6
    43. 线程2拿到了第4
    44. 线程1拿到了第5
    45. 线程1拿到了第3
    46. 线程2拿到了第2
    47. 线程1拿到了第1
    48. 线程2拿到了第0

    3.实现CallAble接口 

    1. 创建执行服务:创建线程池
    2. 提交执行
    3. 获取结果
    4. 关闭服务

    重写call方法,创建线程池,提交线程子类

    1. /**
    2. * 可以拥有返回值
    3. * 可以抛出异常
    4. */
    5. public class TestCallable implements Callable {
    6. @Override
    7. public Boolean call() throws Exception {
    8. String inputurl = "mvnw.cmd";
    9. String outputurl = Thread.currentThread().getName()+".cmd";
    10. InputStream inputStream = new FileInputStream(inputurl);
    11. OutputStream outputStream = new FileOutputStream(outputurl);
    12. byte[] bytes = new byte[1024];
    13. while (inputStream.read(bytes)!=-1){
    14. outputStream.write(bytes);
    15. }
    16. return Boolean.TRUE;
    17. }
    18. public static void main(String[] args) throws Exception {
    19. TestCallable t1 =new TestCallable();
    20. TestCallable t2 =new TestCallable();
    21. TestCallable t3 =new TestCallable();
    22. //创建执行服务:创建线程池
    23. ExecutorService ser = Executors.newFixedThreadPool(3);
    24. //提交执行
    25. Future submit1 = ser.submit(t1);
    26. Future submit2 = ser.submit(t2);
    27. Future submit3 = ser.submit(t3);
    28. //获取结果
    29. Boolean aBoolean1 = submit1.get();
    30. Boolean aBoolean2 = submit2.get();
    31. Boolean aBoolean3 = submit3.get();
    32. //关闭服务
    33. ser.shutdown();
    34. }
    35. }

    线程的状态

    • 创建状态
    • 就绪状态
    • 运行状态
    • 阻塞状态
    • 死亡状态

    方法说明
    setPriority(int newPriority)更改线程优先级
    static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
    void join()等待该线程终止
    static void yield()暂停当前正在执行的线程对象,并执行其他线程
    void interrrupt()中断线程(不用)
    boolean isAlive测试线程是否处于活动状态

    线程休眠

    • sleep(时间)指定当前线程阻塞的毫秒数;
    • sleep存在异常InterruptedException;
    • sleep时间达到后线程进入就绪状态;
    • sleep可以模拟网络延时,倒计时等;
    • 每一个对象都有一个锁,sleep不会释放锁;

    线程礼让

    yield不一定成功

    1. /**
    2. * 测试yield
    3. * 礼让不一定成功
    4. */
    5. public class TestYield{
    6. public static void main(String[] args) {
    7. MyYield myYield = new MyYield();
    8. Thread t1 = new Thread(myYield,"a线程");
    9. Thread t2 = new Thread(myYield,"b线程");
    10. t1.start();
    11. t2.start();
    12. }
    13. }
    14. class MyYield implements Runnable{
    15. @Override
    16. public void run() {
    17. System.out.println(Thread.currentThread().getName()+"开始执行");
    18. Thread.yield();
    19. System.out.println(Thread.currentThread().getName()+"执行结束");
    20. }
    21. }

    线程优先级

    • Java提供一个线程调度器来监控程序中启动进入就绪状态的所有线程,线程调度器按照优先级决定的那个应该调度哪个线程来执行
    • 线程的优先级用数字表示,范围从1~10
      • Thread.MIN_PRIORITY=1;
      • Thread.Max_PRIORITY=10;
      • Thread.NORM_PRIORITY=5;
    • 使用一下方式改变或获取优先级
      • getPriority.setPRiority(int xxx);
    1. public class TestPriority {
    2. public static void main(String[] args) {
    3. Mypriority mypriority = new Mypriority();
    4. Thread t1 = new Thread(mypriority, "t1");
    5. Thread t2 = new Thread(mypriority, "t2");
    6. Thread t3 = new Thread(mypriority, "t3");
    7. t2.setPriority(4);
    8. t3.setPriority(Thread.MAX_PRIORITY);
    9. t1.start();
    10. t2.start();
    11. t3.start();
    12. System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority());
    13. }
    14. }
    15. class Mypriority implements Runnable{
    16. @Override
    17. public void run() {
    18. System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority());
    19. }
    20. }

    守护线程

    • 线程分为用户线程和守护线程
    • 虚拟机必去确保用户线程执行完毕
    • 虚拟机不用等待守护线程执行完毕,守护线程等待最后一个用户线程结束结束
    • 守护线程:操作日志,监控内存,垃圾回收等待。
    1. //测试守护线程
    2. public class TestDaemon {
    3. public static void main(String[] args) {
    4. Thread human = new Thread(new Human());
    5. Thread god = new Thread(new God());
    6. Thread human2 = new Thread(new Human2());
    7. god.setDaemon(true);//开启守护线程
    8. human.start();
    9. god.start();
    10. human2.start();
    11. }
    12. }
    13. class Human implements Runnable{
    14. @Override
    15. public void run() {
    16. for (int i = 0; i < 10; i++) {
    17. System.out.println("用户进程"+i);
    18. }
    19. }
    20. }
    21. class God implements Runnable{
    22. @Override
    23. public void run() {
    24. while (true){
    25. try {
    26. Thread.sleep(100);
    27. } catch (InterruptedException e) {
    28. throw new RuntimeException(e);
    29. }
    30. System.out.println("守护进程");
    31. }
    32. }
    33. }
    34. class Human2 implements Runnable{
    35. @Override
    36. public void run() {
    37. for (int i = 0; i < 10; i++) {
    38. try {
    39. Thread.sleep(100);
    40. } catch (InterruptedException e) {
    41. throw new RuntimeException(e);
    42. }
    43. System.out.println("用户线程2"+i);
    44. }
    45. }
    46. }

    线程的同步机制

    由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被i访问时的正确性,在访问时加入锁机制sychronized,当一个线程获得对象的排他锁,独占资源,其他线程必须的等待,

    使用后释放锁即可,存在下列问题:

    • 一个线程持有锁会导致其他所有拥有此锁的线程挂起:
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

    同步方法 

    • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized放啊和synchronized块
    • 同步方法:public synchronized void method(int args){}
    • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
    • 缺陷:若将一个大的方法申明为synchronized将会影响效率

    同步块 

    • 同步块:synchronized(OBJ){}
    • Obj称之为同步监视器
      • Obj可以是任何对象,到那时推荐使用共享资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射】
    • 同步监视器的执行过程
      • 第一个线程访问,锁定同步监视器

    死锁

    死锁:多个线程互相抱着对方需要的资源,然后形成僵持

    产生死锁的四个必要条件:

    • 互斥条件:一个资源每次只能被一个进程使用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    1. package cn.cslg.kuangshen.lock;
    2. public class DeadLock extends Thread{
    3. public static void main(String[] args) {
    4. Thread t1 = new DeadLock(0);
    5. Thread t2 = new DeadLock(2);
    6. t1.setName("t1");
    7. t2.setName("t2");
    8. t1.start();
    9. t2.start();
    10. }
    11. private int choose = 0;
    12. private String lipstick ="a";
    13. private String mirror ="b";
    14. @Override
    15. public void run() {
    16. if (choose == 0){
    17. synchronized (lipstick){
    18. System.out.println(Thread.currentThread().getName()+"lipstick");
    19. try {
    20. Thread.sleep(100);
    21. } catch (InterruptedException e) {
    22. throw new RuntimeException(e);
    23. }
    24. synchronized (mirror){
    25. System.out.println(Thread.currentThread().getName()+"mirror");
    26. }
    27. }
    28. }else {
    29. synchronized (mirror){
    30. System.out.println(Thread.currentThread().getName()+"lipstick");
    31. synchronized (lipstick){
    32. System.out.println(Thread.currentThread().getName()+"mirror");
    33. }
    34. }
    35. }
    36. }
    37. public DeadLock(int choose){
    38. this.choose = choose;
    39. }
    40. }

    Lock(锁) 

    1. package cn.cslg.kuangshen.lock;
    2. import java.util.concurrent.locks.ReentrantLock;
    3. //测试Lock锁
    4. public class TestLock {
    5. public static void main(String[] args) {
    6. TestLock2 testLock2 = new TestLock2();
    7. Thread t1 = new Thread(testLock2,"t1");
    8. Thread t2 = new Thread(testLock2,"t2");
    9. t1.start();
    10. t2.start();
    11. }
    12. }
    13. class TestLock2 implements Runnable{
    14. private int ticketNums =10;
    15. //定义lock锁
    16. private ReentrantLock lock = new ReentrantLock();
    17. @Override
    18. public void run() {
    19. try {
    20. //加锁
    21. lock.lock();
    22. while (ticketNums>0){
    23. try {
    24. Thread.sleep(100);
    25. } catch (InterruptedException e) {
    26. throw new RuntimeException(e);
    27. }
    28. System.out.println(Thread.currentThread().getName()+ticketNums--);
    29. }
    30. } catch (Exception e) {
    31. throw new RuntimeException(e);
    32. }finally {
    33. //解锁
    34. lock.unlock();
    35. }
    36. }
    37. }

    生产者消费者问题 略过

    使用线程池

    背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

    好处:

    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池大小
      • maxinmumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长事件后会终止
    1. //测试线程池
    2. public class TestPool {
    3. public static void main(String[] args) {
    4. //创建服务,创建线程池
    5. ExecutorService executorService = Executors.newFixedThreadPool(2);
    6. //执行
    7. executorService.submit(new TestThread());
    8. executorService.submit(new TestThread());
    9. executorService.submit(new TestThread());
    10. //关闭连接
    11. executorService.shutdown();
    12. }
    13. }
    14. class TestThread implements Runnable{
    15. @Override
    16. public void run() {
    17. System.out.println(Thread.currentThread().getName());
    18. }
    19. }

  • 相关阅读:
    指针类型的意义
    Vuex的基本使用
    48页数字政府智慧政务一网通办解决方案
    怎样将excel的科学计数法设置为指数形式?
    Vue使用ElementUi进行模糊搜索
    Java 可变参数及集合工具类(Collections)
    go开发之个微机器人的二次开发
    一种tcp传输json包时出现包不完整、粘包的解决方案
    Linux下的web服务器搭建
    cpp基础学习笔记02
  • 原文地址:https://blog.csdn.net/weixin_46543456/article/details/127692207