多线程学习笔记

多线程

程序是为完成特定任务、用某种语言编写的一组指令的集合。是静态代码

进程是运行中的程序是一个动态的过程:有它自身的产生、存在和消亡的过程

线程:进程中的一个执行路径,一个进程由多个线程组成,同时执行完成不同工作为多线程。

单核CPU同一时刻只运行一个进程:宏观上看是并行,但在微观串行多个进程快速切换,在用户看来时是多个进程同时运行

并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
在这里插入图片描述
线程组成:
CPU时间片:操作系统为每个线程分配执行时间
运行数据:
堆空间:存储对象,多个线程共享堆中对象。
栈空间:存储线程需使用的局部变量,每个线程有独立的栈
线程分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
1.守护线程是用来服务用户线程的,通过在start()方法前调用,用户线程执行完毕,守护线程会自动结束
thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
2.Java垃圾回收就是一个典型的守护线程。
线程特点:
1.抢占执行:效率高,防止某一线程独占CPU时间过长
2.宏观并行,微观串行
多线程的优点
1.提高应用程序的响应。增强用户体验。
5. 提高计算机系统CPU的利用率
6. 改善程序结构。将长而复杂的进程分为多个线程,独立运行,利于理解和修改
多线程使用时机
1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.需要一些后台运行的程序时

线程创建

线程创建三种方式:
1.继承Thread类
2.实现Runnerable接口
3.实现Callable接口(jdk1.5加入)

线程创建:1.继承Thread

构造器
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接
口中的run方法
Thread(Runnable target, String name):创建新的Thread对象

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
                System.out.println("子线程····"+i);
            }
    }
}
-----------------------------------------------------------------
public class ThreadTest {
    @Test
    public void testThread(){
        //1.创建线程对象
        MyThread myThread=new MyThread();
        //2.启动线程
        //myThread.run();是调用普通方法
        myThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程····"+i);
        }

    }
}
--------------------------------------------------------------------
运行结果:
主线程····0
子线程····0
主线程····1
子线程····1
主线程····2
子线程····2
主线程····3
子线程····3
主线程····4
子线程····4
主线程····5
子线程····5
主线程····6
子线程····6
主线程····7
子线程····7
主线程····8
子线程····8
主线程····9
子线程····9

当主线程执行后子线程才会执行,并且是抢占执行。

同一个Thread不能重复调用start方法,跟线程的4中状态有关系。

  线程的4种状态:新生状态;可运行状态;阻塞状态;死亡状态

       a. 新生状态:在调用start()方法之前

       b. 可运行状态:调用start()方法后,系统为该线程分配除cpu外的所需资源,对于只
       有一个cpu的机器而言,任何时刻只能有一个处于可运行的线程占用处理机,获得
       cpu资源,此时系统正正运行线程的run()方法....

       c. 阻塞状态:一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态。这
      是【不可运行】的状态,处于这种状态的线程在得到一个特定的事件后会转回可运行状态
      
       d. 死亡状态:一个线程的run()运行完毕,stop()方法被调用或者在运行过程中出现了未捕获的异常时,线程进入死亡状态。
  1. 线程的4中状态除了【可运行状态】与【阻塞状态】可以来回切换,其余的不可逆转

线程创建:2.实现Runnable接口

Thread类也是实现Runnable接口
接口里面只有一个run()方法

 @Test
    public void testNm(){
     /* Thread thread=new Thread(()->{
          System.out.println("子线程");
      });
      thread.start();
    }*/
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                //之前用的jdk11  System.out.println("线程名"+Thread.currentThread().getName());没法输出
                //只能输出 System.out.println(Thread.currentThread().getName());之后试了与其它的类型数据一起输出都没法输出
                //切到jdk8就能输出了,不明白其中的原理,可能是11版本改了什么
                    System.out.println("线程名"+Thread.currentThread().getName());
            }
        };
        Thread thread=new Thread(runnable,"MyThread");
        thread.start();
    }
    ------------------------------------
    结果:
    线程名MyThread

线程休眠
sleep方法让当前线程休眠单位:ms
线程放弃
调用yield方法主动放弃当前线程,让其他线程有机会获得线程
线程加入
调用join方法,在一个线程中加入其他线程并阻塞当前线程,当其他线程执行完才会继续执行当前线程。
线程调度
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
设置线程优先级
setPriority方法设置优先级(1-10)默认5,优先级获得线程概率高
在这里插入图片描述
多线程安全问题
多线程访问临界资源(共享对象,一次只允许一个进程使用才能保证一致性),若破坏原子操作,会造成数据不一致
同步代码块
synchronized(临界资源对象){//可以自己创建锁或者用this(当前对象)
同步代码//原子操作
}
synchronized的锁是什么
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
多个线程必须共用同一把锁。

public class BankTest {
    @Test
    public void testBank(){
        BankCard bankCard=new BankCard();
        Runnable add= () -> {
            for (int i = 0; i < 5; i++) {
                bankCard.setMoney(bankCard.getMoney() + 1000);
                System.out.println(Thread.currentThread().getName()+"存了1000,余额 "+bankCard.getMoney());
            }
        };
        Runnable sub= () -> {
            for (int i = 0; i < 5; i++) {
                if (bankCard.getMoney() >= 1000) {
                    bankCard.setMoney(bankCard.getMoney() - 1000);
                    System.out.println(Thread.currentThread().getName() + "取了1000,余额 " + bankCard.getMoney());
                }else{
                    System.out.println("余额不足");
                    i--;
            }
            }
        };
        Thread threadA= new Thread(add, "A");
        Thread threadB= new Thread(sub, "B");
        threadA.start();
        threadB.start();
    }
}

---------------------------------------------------------------------------
结果:
A存了1000,余额 0.0
B取了1000,余额 0.0
B取了1000,余额 0.0
余额不足
余额不足
。。。
账户显示余额不足,说明A存完钱还没输出,线程就被B抢占了。
给两个run方法都加上synchronized(bankCard){}
结果:
A存了1000,余额 1000.0
A存了1000,余额 2000.0
A存了1000,余额 3000.0
A存了1000,余额 4000.0
A存了1000,余额 5000.0
B取了1000,余额 4000.0
B取了1000,余额 3000.0
B取了1000,余额 2000.0
B取了1000,余额 1000.0
B取了1000,余额 0.0

同步的范围
1、如何找问题,即代码是否存在线程安全?
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其
他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中
3、范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能
死锁
1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于
阻塞状态,无法继续
解决方法
1.专门的算法、原则
2.尽量减少同步资源的定义
3.尽量避免嵌套同步

Lock(锁)(jdk5.0)

ReentrantLock类,需手动上锁lock,释放锁unlock
synchronized 与 Lock 的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
    隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
    更好的扩展性(提供更多的子类)
    优先使用顺序:
    Lock >同步代码块(已经进入了方法体,分配了相应资源) > 同步方法
    (在方法体之外)
    死锁问题
public class TheadLock {
    @Test
    public void testLock(){
        StringBuffer numberA=new StringBuffer();
        StringBuffer numberB=new StringBuffer();
      new Thread(){
          @Override
          public void run() {
              synchronized (numberA){
                  numberA.append("He");
                  numberB.append("llo");
                  try {
                      sleep(1000);//增加死锁的概率,休眠后下面的线程得到执行
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  synchronized (numberB){
                      numberA.append("wo");
                      numberB.append("rld");
                  }
                  System.out.println(numberA);
                  System.out.println(numberB);
              }
          }
      }.start();
      new Thread(new Runnable(){

          @Override
          public void run() {
              //上面线程休眠,当前线程得到执行
              synchronized (numberB){//握住B
                  numberA.append("Me");
                  numberB.append("XXo");
                  //执行到这需要握住锁A,但是A在上面的线程手里,没有被释放,造成死锁
                  synchronized (numberA){
                      numberA.append("Ho");
                      numberB.append("Mld");
                  }
                  System.out.println(numberA);
                  System.out.println(numberB);
              }
          }
      }).start();
    }
}

线程通信

public class Number implements Runnable{
private int num=1;
   @Override
   public void run() {
       while (true) {
           //wait,notify,notifyAll必须使用在同步代码块或同步方法中
           //三个方法的调用者必须是同步监视器,三个方法定义在Object类中
           synchronized (this) {
               //唤醒wait线程。先等待先唤醒,有优先级的话 优先级高的被唤醒概率会高
               notify();
               if (num <= 10) {
                   System.out.println(Thread.currentThread().getName() + ":" + num);
                   num++;
                   try {
                       //调用wait方法进入阻塞状态吗,并释放锁。
                       wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               } else break;
           }
       }
   }
}

-------------------------------------------------------------------
public class NumberTest {
   @Test
   public void testNumber(){
       Number number=new Number();
     Thread thread1=new Thread(number,"A");
     Thread thread2=new Thread(number,"B");
     thread1.start();
     thread2.start();
   }
}
----------------------------------------------------
结果:
B1
A2
B3
A4
B5
A6
B7
A8
B9
A10

sleep与wait异同
相同点:都会使当前线程阻塞
不同点:声明位置不同,Thread类中声明sleep,Object中声明wait
调用范围不同:wait必须使用在同步代码块中或同步方法中,sleep在哪都能用
如果都在同步代码中使用,sleep不会释放锁,wait会。

线程创建:3.实现Callable接口

与使用Runnable相比, Callable功能更强大些
1.相比run()方法,call()方法可以有返回值
2.方法可以抛出异常
3.支持泛型的返回值
4.需要借助FutureTask类,比如获取返回结果
Future接口
1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是
否完成、获取结果等。
2. FutrueTask是Futrue接口的唯一的实现类
3.FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值

public class CallableTest {
    @Test
    public void testCall() throws ExecutionException, InterruptedException {
      Call call=new Call();
      //借助FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(call);
        //作为runnable被执行(实现了Runnable接口)
        new Thread(futureTask).start();
        //使用get获得返回值
        Integer sum= futureTask.get();
        System.out.println("总和为:"+sum);
    }
}
class Call implements Callable<Integer>{
    /**
     * 实现call方法,将线程具体操作卸载方法中
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        int sumNu=0;
        for (int i = 0; i <= 10; i++) {
            sumNu+=i;
        }
        return sumNu;
    }
}

线程创建:4.线程池

开发常用,带“池”字的基本都是开发常用的(数据库连接池,线程池)
一次创建多个放入池子中,使用时直接获取,用完再放回池子。可以避免频繁创建销毁、实现重复利用。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
。。。
ExecutorService接口 和 Executors工具类
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
1.void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
2. Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
3.void shutdown() :关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
1.Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
2.Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
3.Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
4.Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

public class ExecutorTset {
    @Test
    public void testEx(){
        ExecutorService service = Executors.newFixedThreadPool(5);
        //适用于Runnable
        service.execute(()-> {
            System.out.println("Runnable");
        });
        //适用于Callable
        service.submit(new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                System.out.println("Callable");
                return null;
            }
        }));
        service.shutdown();//关闭连接池
    }
}
```java
public class ExecutorTset {
    @Test
    public void testEx(){
        ExecutorService service = Executors.newFixedThreadPool(5);
        //System.out.println(service.getClass());
        ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
        //设置最大线程数
        service1.setMaximumPoolSize(5);
        //设置核心池大小
        service1.setCorePoolSize(10);
        //适用于Runnable
        service.execute(()-> {
            System.out.println("Runnable");
        });
        //适用于Callable
        service.submit(new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                System.out.println("Callable");
                return null;
            }
        }));
        service.shutdown();//关闭连接池
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值