程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

对于多线程的学习总结

发布于2021-03-13 14:48     阅读(644)     评论(0)     点赞(6)     收藏(1)


最近刚学完多线程,趁着自己还没忘记,现总结总结,大家凑合着看看吧

1.多线程的实现方法

1.1并行与并发
    并行:多个对象同一时刻执行多个任务
    并发:单个对象在同一时间段交替执行多个任务
1.2进程和线程
    进程:指正在运行的一个程序(cpu分配资源的基本单位)
    线程:正在运行的程序中的一个执行任务(执行单元)(进程中分配资源的基本单位)
1.3多线程的三种实现方法
    1.3.1继承Thread类
        一个类通过继承Thread类,重写父类的run()方法,来实现多线程,通过这种方法来实现多线程,直接创建对象调用start()方法来开始线程;
        start()方法实际上是开辟新的栈内存,在新的栈内存中执行线程任务
         所以要想开始新的线程,必须调用start()方法
    1.3.2实现Runnable接口
         一个类可以通过实现Runnable接口来实现多线程,重写接口中的run方法,然后将实现了接口的类的对象作为参数传递给Thread对象,再通过调用start()方法来开启线程:new Thread(Runnable对象)
    1.3.3实现Callable接口
         一个类可以通过实现Callable接口来实现多线程,重写接口的call方法,然后创建类的对象作为参数传递给FutureTask对象,然后将FutureTask对象作为参数传递给Thread对象:
           

  1. FutureTask ft = new FutrueTask(Call对象);
  2.            Thread t = new Thread(ft);
  3.            t.start();


        call方法是有一个返回值的,Callable是一个泛型接口,泛型的类型必须和call方法的返回值类型相同
            在线程运行结束后,可以通过FutureTask对象调用get方法来获取返回值
    1.3.4三种方式的比较
            继承Thread相比较起来编写代码少,但是该类无法去继承其他类,降低了可扩展性
            其他两种实现接口的方式提高了可扩展性,在实现多线程时还可以去继承其他类(以后用实现接口较多)

2.多线程的成员方法

3.1设置获取名字
   

  1. String getName();//获取线程的名字
  2.     Thread(String name);//通过构造方法给线程定义一个名字
  3.     setName(String name);//设置线程的名字


3.2获取当前线程对象
   

 public static Thread currentThread();//获取当前调用方法的线程对象


3.3睡眠方法
   

 public static void sleep(long time);//设置当前线程休眠time时间


3.4线程的优先级
    声明:线程的优先级的高低只是说明其抢占CPU时间片的概率的大小,并不能保证在CPU空闲时一定能抢到执行权

  1.   int getPriority();//获取当前线程的优先级
  2.     void setPriority(int priority)//给线程设置优先级
  3.     注:优先级默认为5,范围为MIN_PRIORITY(1)-MAX_PRIORITY(10)


3.5守护线程
   

void setDaemon(boolean false);//设置一个线程为守护线程


    守护线程时为了给其他线程做辅助工作,当普通线程结束时不管守护线程是否结束,它都会随之结束

3.多线程的安全问题

3.1产生安全问题原因
      当多个线程共享资源时,并且多个线程都对资源数据进行增删改操作时,会出现安全问题。

3.2解决方法
    3.2.1同步代码块
        同步代码块的格式:
        

synchronized(锁对象){//多线程对共享资源操作的代码 }


        注意:①锁对象可以是任意对象
              ②多线程必须用同一把锁
        同步代码块解决多线程安全问题的原理:使一个线程在对共享数据进行操作时,其他线程无法对其进行操作
    3.2.2同步方法
         同步方法的格式:
         

public [static] synchronized 返回值类型 方法名(){}


        static同步方法 锁对象是 this
         非static同步方法 锁对象是 类名.class
    3.2.3创建锁对象
         可以自己实例化一个锁对象:Lock lock = new ReenTrantLock
         在需要加锁的代码位置lock();需要解开锁的位置unlock();

相关代码:

这里是同步代码块写了一个经典的卖票的线程安全问题,同步方法不做演示

  1. public class MySynchronizedTest1 {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. Thread t1 = new Thread(ticket);
  5. Thread t2 = new Thread(ticket);
  6. Thread t3 = new Thread(ticket);
  7. t1.setName("窗口1");
  8. t2.setName("窗口2");
  9. t3.setName("窗口3");
  10. t1.start();
  11. t2.start();
  12. t3.start();
  13. }
  14. }
  15. class Ticket implements Runnable{
  16. //定义票的数量
  17. private int ticket = 100;
  18. private static Object obj = new Object();
  19. @Override
  20. public void run() {
  21. while(true){
  22. //设置同步代码块,将操作共享数据的代码放到synchronized(Object obj){}的大括号中
  23. synchronized (obj){
  24. //就是当前的线程对象.
  25. if(ticket <= 0){
  26. break;//说明票已经卖完了
  27. }else{
  28. //卖每一张票需要时间,所以这里让它睡眠一下再继续卖下一张
  29. try {
  30. Thread.sleep(150);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. ticket--;
  35. System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
  36. }
  37. }
  38. }
  39. }
  40. }

3.3死锁问题
    死锁:由于锁的嵌套导致线程A等待线程B的锁,线程B等待线程A的锁,所以导致程序无法正常执行

代码演示:

  1. //死锁
  2. public class MySynchronizedTest3 {
  3. public static void main(String[] args) {
  4. Object objA = new Object();
  5. Object objB = new Object();
  6. new Thread(()->{
  7. while(true){
  8. synchronized (objA){
  9. synchronized (objB){
  10. System.out.println("我在走路");
  11. }
  12. }
  13. }
  14. }).start();
  15. new Thread(()->{
  16. while(true){
  17. synchronized (objB){
  18. synchronized (objA){
  19. System.out.println("你在走路");
  20. }
  21. }
  22. }
  23. }).start();
  24. }
  25. }

3.4生产者-消费者
    这里举例说明可以更好的理解:
        ①顾客来到餐厅发现没有吃的,就会等待wait()
        ②顾客来到餐厅发现有吃的,那么就会吃掉吃的,然后通知厨师去做notify()
        ③厨师发现现在有吃的,那就等待wait()
        ④厨师发现没有吃的,那么就会做食物,然后通知顾客notify()
    生产者-消费者主要解决了让多个线程按照一定的次序来协作完成某个功能
    代码实现主要使用三个方法:wait() notify() notifyAll();
    注意:这三个方法的调用者都是锁对象

附上代码如下:

  1. //桌子类,存放共享资源
  2. public class Desk {
  3. //定义一个flag表示桌子上是否有食物
  4. private boolean flag;
  5. //定义食物数量
  6. private int FoodCount;
  7. //定义共用的锁
  8. private final Object Lock = new Object();
  9. public Desk() {
  10. this(false,10);
  11. }
  12. public Desk(boolean flag, int foodCount) {
  13. this.flag = flag;
  14. FoodCount = foodCount;
  15. }
  16. public boolean isFlag() {
  17. return flag;
  18. }
  19. public void setFlag(boolean flag) {
  20. this.flag = flag;
  21. }
  22. public int getFoodCount() {
  23. return FoodCount;
  24. }
  25. public void setFoodCount(int foodCount) {
  26. FoodCount = foodCount;
  27. }
  28. public Object getLock() {
  29. return Lock;
  30. }
  31. @Override
  32. public String toString() {
  33. return "Desk{" +
  34. "flag=" + flag +
  35. ", FoodCount=" + FoodCount +
  36. ", Lock=" + Lock +
  37. '}';
  38. }
  39. }
  40. //消费者类(顾客)
  41. public class Foodie extends Thread{
  42. private Desk desk;
  43. public Foodie(Desk desk) {
  44. this.desk = desk;
  45. }
  46. @Override
  47. public void run() {
  48. while(true){
  49. synchronized (desk.getLock()){
  50. if(desk.getFoodCount() == 0){
  51. break;
  52. }else{
  53. if(desk.isFlag()){//代表桌子上有食物
  54. System.out.println("吃货正在吃倒数第"+desk.getFoodCount()+"个食物");
  55. desk.setFlag( false);//吃完之后将flag变为flase表示桌子上没有食物了
  56. //Desk.FoodCount--;//食物数量减一
  57. desk.setFoodCount(desk.getFoodCount()-1);
  58. desk.getLock().notifyAll();//唤醒等待的线程
  59. }else{
  60. try {
  61. desk.getLock().wait();//如果为false,说明没有食物,那么进入等待状态,释放掉锁
  62. } catch (InterruptedException e) {
  63. e.printStackTrace();
  64. }
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. //生产者类(厨师)
  72. public class Cooker extends Thread{
  73. private Desk desk;
  74. public Cooker(Desk desk) {
  75. this.desk = desk;
  76. }
  77. @Override
  78. public void run() {
  79. while(true){
  80. synchronized (desk.getLock()){
  81. if(desk.getFoodCount() == 0){
  82. break;
  83. }else{
  84. if(!desk.isFlag()){
  85. System.out.println("厨师正在做倒数第"+desk.getFoodCount()+"个食物,还剩"+(desk.getFoodCount()-1)+"个食物");
  86. desk.setFlag(true);
  87. desk.getLock().notifyAll();
  88. }else{
  89. try {
  90. desk.getLock().wait();
  91. } catch (InterruptedException e) {
  92. e.printStackTrace();
  93. }
  94. }
  95. }
  96. }
  97. }
  98. }
  99. }
  100. //测试类
  101. public class Test {
  102. public static void main(String[] args) {
  103. Desk desk = new Desk();
  104. Foodie f = new Foodie(desk);
  105. Cooker c = new Cooker(desk);
  106. f.start();
  107. c.start();
  108. }
  109. }

3.5阻塞队列

     3.5.1阻塞队列的使用
        阻塞出现的原因:程序去完成一个功能时,由于某种原因,现在无法完成,程序会停住(后面的代码就不能再执行了),直到该功能完成为止。
        阻塞队列结合生产者-消费者使用可以大大提高代码效率,并且不需要加锁,因为阻塞队列的底层就是 锁+wait+notify
     3.5.2阻塞队列的创建和方法
        BlockingQueue:
            ArrayBlockingQueue(int capacity)
             LinkedBlockingQueue();
        方法:
            往阻塞队列中放元素:put
            从阻塞队列中取元素:take
     3.5.3分析
            在消费者-生产者机制中使用阻塞队列,相当于给生产者提供了一个可以存放多个产品的队列,它生产了产品就放到阻塞队列中,不必等待消费者消费了产品之后再唤醒它再去生产下一个产品;而消费者也同理,它可以直接从阻塞队列中取产品,不用等生产者一个一个生产。

附上代码如下:

  1. import java.util.concurrent.ArrayBlockingQueue;
  2. //生产者类
  3. public class Cooker extends Thread{
  4. private ArrayBlockingQueue<String> arrayBlockingQueue;
  5. public Cooker(ArrayBlockingQueue<String> arrayBlockingQueue) {
  6. this.arrayBlockingQueue = arrayBlockingQueue;
  7. }
  8. @Override
  9. public void run() {
  10. while (true){
  11. try {
  12. arrayBlockingQueue.put("汉堡包");
  13. System.out.println("往阻塞队列中放一个汉堡包");
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. }
  20. //消费者类
  21. import java.util.concurrent.ArrayBlockingQueue;
  22. public class Foodie extends Thread{
  23. private ArrayBlockingQueue<String> arrayBlockingQueue;
  24. public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue) {
  25. this.arrayBlockingQueue = arrayBlockingQueue;
  26. }
  27. @Override
  28. public void run() {
  29. while (true){
  30. try {
  31. String food = arrayBlockingQueue.take();
  32. System.out.println("从阻塞队列拿到了一个"+food);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. }
  39. //测试类
  40. import java.util.concurrent.ArrayBlockingQueue;
  41. public class Test {
  42. public static void main(String[] args) {
  43. ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
  44. Cooker c = new Cooker(arrayBlockingQueue);
  45. Foodie f = new Foodie(arrayBlockingQueue);
  46. c.start();
  47. f.start();
  48. }
  49. }

3.6线程的状态

        线程的状态有6种,分别是:
              new:Thread对象new好之后的状态
              runnable:Thread对象调用.start方法后
              blocked:阻塞
              waiting:等待
              timed_waiting:计时等待
             terminated:死亡

3.7线程池

    3.7.1概念
        线程池指存放线程的容器,大大提高了效率,不需要每需要执行一个任务就创建一个线程,执行完成后销毁线程;线程池的存在可以使线程任务结束后回到线程池,下次任务执行再直接使用
    3.7.2Executors默认线程池
      

  1.  Executors:
  2.         static ExecutorService newCachedThreadPool();
  3.         这是一个静态方法,可以通过Executors这个类直接调用,返回一个ExecutorService对象
  4.          ExecutorService提供了这些方法:
  5.             submit(Runable r);//接收一个Runable接口的实现类,可以使用Lambda表达式
  6.             submit(Callable c);//接收一个Callable接口的实现类,可以使用Lambda表达式
  7.             shutdown();//摧毁线程池


    3.7.3Executors创建指定上限的线程池
        要想创建指定线程池容量的方法:
            

  1. ExecutorService newFixedThreadPool(int nTheads);
  2.             ScheduledExecutorService newScheduledThreadPool(int corePoolSize)  
  3.             ScheduledExecutorService newSingleThreadScheduledExecutor()
  4.             ExecutorService newSingleThreadExecutor()

3.7.4自己直接创建线程池
        上面的创建都是先创建Service,我们也可以直接创建线程池对象:
          

  1.  public ThreadPoolExecutor(    
  2.                 int corePoolSize,//核心线程数量 2
  3.                 int maximumPoolSize,//最大线程数量 5
  4.                 long keepAliveTime,//空闲线程最大的空闲时间
  5.                 TimeUnit unit,//空闲时间单位
  6.                 BlockingQueue<Runnable> workQueue, //阻塞队列,存放等待执行的任务
  7.                 ThreadFactory threadFactory,//造线程的工厂  
  8.                 RejectedExecutionHandler handler//任务超过最大线程数+阻塞队列容量,拒绝的策略)


        3.7.4.1拒绝策略
                当任务超过最大线程数+阻塞队列容量会被拒绝,默认的拒绝策略是丢弃任务,并抛出异常
         3.7.4.2非默认任务拒绝策略
               

  1. ThreadPoolExecutor.DiscardPolicy:
  2.                 *丢弃任务;
  3.                 ThreadPoolExecutor.DiscardOldestPolicy:
  4.                 *丢弃任务队列中等待时间最长的任务,再把最新的任务添加到队列中
  5.                 ThreadPoolExecutor.CallerRunsPolicy:
  6.                 *谁提交的这个任务,谁来执行这个任务;

附上线程池相关代码:

  1. public class MyThreadPoolDemo1 {
  2. public static void main(String[] args) {
  3. //创建线程池对象
  4. ExecutorService executorService = Executors.newCachedThreadPool();
  5. //使用Lambda表达式来创建函数式接口的对象
  6. executorService.submit(()->{
  7. System.out.println(Thread.currentThread().getName()+"正在运行");
  8. });
  9. //使用Lambda表达式来创建函数式接口的对象
  10. executorService.submit(()->{
  11. System.out.println(Thread.currentThread().getName()+"正在运行");
  12. });
  13. executorService.submit(()->{
  14. System.out.println(Thread.currentThread().getName()+"正在运行");
  15. });
  16. executorService.shutdown();
  17. }
  18. }

上边是通过Executors默认创建线程池,下边是自己new的线程池:

  1. public class MyThreadPoolDemo3 {
  2. public static void main(String[] args) {
  3. ThreadPoolExecutor pool = new ThreadPoolExecutor(
  4. 3,//核心线程数量
  5. 5,//总线程数量
  6. 2,//临时线程的空闲时间
  7. TimeUnit.SECONDS,//空闲时间单位
  8. new ArrayBlockingQueue<>(10),//阻塞队列
  9. Executors.defaultThreadFactory(),//创建线程的线程工厂
  10. new ThreadPoolExecutor.AbortPolicy());//当等待的线程超过总量+阻塞队列时的拒绝策略
  11. pool.submit(()->{
  12. System.out.println(Thread.currentThread().getName()+"RUNNING....");
  13. });
  14. pool.shutdown();
  15. }
  16. }

3.8volatile-问题
        volatile关键字主要解决了内存不可见问题,就是当多个线程同时操作共享数据时,如果其中一个修改了共享数据,另外的线程无法及时获取到新的数据。
        使用volatile关键字修饰变量后,那么有关该变量操作的代码,就不会被机器指令替换
         内存不可见问题:jvm在执行java代码时,针对反复执行的代码,就会把这些代码标记为热点代码,这些热点代码在执行指定量次(10000),jvm会使用jit及时编译,把之前这些class指令替换为机器指令,反复执行的时候就再也不会读取新的数据了
        也可以使用synchronized解决内存不可见问题,因为synchronized中的代码会强制查看共享内存中的数据;
3.9原子性
        原子性指不可分割,举例:count++
            count++虽然看起来是一行代码,但是执行的时候对应3个指令:
                1.读取共享内存数据到变量副本;
                2.对变量副本中的数据进行自增1;
                3.把变量副本中的数据写入到共享内存;
                所以当多个线程执行时,count++可能造成线程安全问题,
                可以使用synchronized解决原子性导致的线程安全问题,将count++变成不可分的,但是synchronized是重量级锁,性能较低,也可以使用volatile+cas算法解决线程安全问题。

    3.9.1AtomicInteger
        jdk在并发包中提供了大量的Atomicxxx类,专门用于在多线程环境下编程,AtomicInteger是比较常用的一种:
      

  1. AtomicIneger();
  2.         int get(); //获取值
  3.         int getAndIncrement();//以原子方式将当前值加1,这里返回的是自增前的值
  4.         int incrementAndGet();// 以原子方式将当前值加1,这里返回的是自增后的值
  5.         int addAndGet(int delta);//以原子方式将参数与对象中的值相加,并返回结果
  6.         int getAndSet(int delta);//以原子方式设置为newValue的值,并返回旧值。


    3.9.2AtomicInteger-内存解析
        volatile关键字加CAS算法可以解决内存不可见问题
        CAS算法原理:在对共享数据进行操作时,把原来的旧值记录下来,在自己的栈内对数据进行操作之后要写入内存之前,比较现在内存中的值跟原来的旧值一样,那么说明没有其他线程对共享数据进行操作,那么对其进行修改(将修改的值写入内存);如果旧值不一样了,那么说明有其他线程对共享数据进行过其他操作了,那么需要再次获取内存的新值,再次进行操作,这个重新获取就是自旋。
   

  1. 伪代码:
  2.         while(true){
  3.             ①读取内存值,赋值给旧值;
  4.             ②给旧值加1,得到新值;
  5.             ③if(旧值==内存值){
  6.                 内存值=新值;
  7.                 break;
  8.             }
  9.         }

3.10并发工具类

    3.10.1HashTable
        HashTable是线程安全的,因为它的底层方法都加了synchronized关键字,在操作共享数据时,将整个hash表都锁
    3.10.2并发工具类-ConcurrentHashMap
        ConcurrentHashMap继承了HashMap,所以父类的方法都是可以使用的
        ConcurrentHashMap在JDK1.7中,有一个长度为16的不可变的数组,在每个数组位置上有一个可扩容小数组,该位置存储的数据超过小数组的加载因子,则自动扩容为原长度的2倍
        ConcurrentHashMap在JDK1.8中,采用了 CAS + synchronized 来保证并发安全性
        put方法:
        1.计算key的hash值
        2.如果当前table还没有初始化先调用initTable方法将tab进行初始化
        3.tab中索引为i的位置的元素为null,则直接使用CAS将值插入即可
        4.当前正在扩容
        6.若当前为红黑树,将新的键值对插入到红黑树中
        7.插入完键值对后再根据实际大小看是否需要转换成红黑树
        8.对当前容量大小进行检查,如果超过了临界值(实际大小*加载因子)就需要扩容
    3.10.3ConcurrentHashMap的高性能
        在操作数据时只锁住索引处的链表,不像HashTable锁住一整张表
    3.10.4并发工具类-CountDownLatch
        如果一个线程需要等待其他多个线程执行完毕以后才能执行,可以使用CountDownLatch
        public CountDownLatch(int count)://让当前线程等待,计数器为0时唤醒
        public void await():
        public void countDown():

       附上完整代码:

  1. //这里写了一个妈妈要等待孩子都吃完饺子才收拾碗筷的案例
  2. import java.util.concurrent.CountDownLatch;
  3. //孩子线程
  4. public class ChildrenRunnable implements Runnable{
  5. private CountDownLatch countDownLatch;
  6. private int number;
  7. public ChildrenRunnable(CountDownLatch countDownLatch, int number) {
  8. this.countDownLatch = countDownLatch;
  9. this.number = number;
  10. }
  11. @Override
  12. public void run() {
  13. for (int i = 1; i <= number; i++) {
  14. System.out.println(Thread.currentThread().getName()+"正在吃第"+i+"个饺子");
  15. }
  16. countDownLatch.countDown();
  17. }
  18. }
  19. import java.util.concurrent.CountDownLatch;
  20. //妈妈线程
  21. public class MotherRunnable implements Runnable{
  22. private CountDownLatch countDownLatch;
  23. public MotherRunnable(CountDownLatch countDownLatch) {
  24. this.countDownLatch = countDownLatch;
  25. }
  26. @Override
  27. public void run() {
  28. try {
  29. countDownLatch.await();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println("妈妈去收拾碗筷了");
  34. }
  35. }
  36. import java.util.concurrent.CountDownLatch;
  37. public class MyCountDownLatchDemo {
  38. public static void main(String[] args) {
  39. CountDownLatch countDownLatch = new CountDownLatch(3);//这里表示有三个线程要等待
  40. MotherRunnable mr = new MotherRunnable(countDownLatch);
  41. new Thread(mr).start();
  42. new Thread(new ChildrenRunnable(countDownLatch,7),"小黑").start();
  43. new Thread(new ChildrenRunnable(countDownLatch,9),"小红花").start();
  44. new Thread(new ChildrenRunnable(countDownLatch,11),"小狗").start();
  45. }
  46. }

    3.10.5并发工具类-Semaphore

         限制资源的执行量时 使用Semaphore
         public Semaphore(int permits)
         public void acquire()//获取信号量----阻塞方法
         public void release()//释放信号量

完整代码:

  1. //这里模拟一个路口同时只允许三辆车通过,必须拿到通行证才能通过,通过后归还通行证,然后别的车才能拿到通行证
  2. public class MySemaphoreRunnable implements Runnable{
  3. //获取管理员对象
  4. private Semaphore semaphore = new Semaphore(3);
  5. @Override
  6. public void run() {
  7. try {
  8. semaphore.acquire();//相当于获取通行证
  9. System.out.println("拿到通行证,可以通行");
  10. Thread.sleep(2000);
  11. System.out.println("归还通行证");
  12. semaphore.release();
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. public class MySemaphoreDemo {
  19. public static void main(String[] args) {
  20. MySemaphoreRunnable msr = new MySemaphoreRunnable();
  21. for (int i = 0; i < 100; i++) {
  22. new Thread(msr).start();
  23. }
  24. }
  25. }

 

原文链接:https://blog.csdn.net/liuhang_1996/article/details/114677232



所属网站分类: 技术文章 > 博客

作者:Hdhhd

链接:http://www.javaheidong.com/blog/article/114285/5b518fa69e3134f9e57f/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

6 0
收藏该文
已收藏

评论内容:(最多支持255个字符)