Java多线程使用总结

  • 什么是多线程
    线程是操作系统能够进行运算调度的最小单位,而多线程就是指并发执行多个线程。多线程是为了同步完成多项任务,为了提高资源使用效率来提高系统的效率,也可以发挥多核处理器的优势。

  • 多线程的缺点

    • 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
    • 线程可能会给程序带来更多“bug”,需要注意线程安全的问题。
    • 多线程情况下可能出现线程死锁情况。
    • 线程的中止需要考虑其对程序运行的影响。
  • 创建线程有哪些方式
    常用的有两种

    • 1.继承Thread类覆写run方法
    • 2.实现Runnable接口,并实现run方法。(启动线程需要以Runnable作为参数创建Thread对象调用start方法)

    还可以通过Callable或者基于线程池(Executors)创建

  • 多线程怎么同步

    • synchronized
      通常使用synchronized关键字来保证方法或代码块在多线程场景下的同步调用,但是使用synchronized关键字的时候需要注意死锁问题。

      private synchronized void init() {
          //TODO
      }
      
    • ReentrantLock
      使用ReentrantLock时需要注意及时释放锁,否则可能会产生死锁的问题。通常在finally关键字中释放,来保障即使出现异常情况,锁也可以正常释放。

      public class Task {
          private static final String TAG = "ThreadTask";
          private ReentrantLock lock = new ReentrantLock();
      
          /**
          * 消费
          */
          private void expense() {
              lock.lock();//获得锁
              try {
                  Log.d(TAG, "expense");
                  //TODO
              } finally {
                  lock.unlock();//释放锁
              }
          }
      }  
      
  • 4.什么是死锁?怎么解决?

    • 死锁
      死锁是至多个线程在运行时因争夺资源进入一种互相等待的状态。
      如下代码,当有多个线程分别执行方法runArunB时就容易产生死锁的情况,当A线程进入到runA方法获取到object1对象锁的时候,如果此时正好有线程B进入到runB方法且获取到了object2对象锁,此时线程A将等待线程B释放object2对象锁,而线程B等待线程A释放object1对象锁,导致谁都无法继续执行,进入死锁状态。

      private Object object1 = new Object();
      private Object object2 = new Object();
      private void runA() {
          synchronized (object1) {
              synchronized (object2) {
                  Log.d(TAG, "runA");
              }
          }
      }
      
      private void runB() {
          synchronized (object2) {
              synchronized (object1) {
                  Log.d(TAG, "runB ");
              }
          }
      }
      
      
    • 如何解决死锁问题

      • 1.尽量不使用嵌套锁
      • 2.不得已情况下使用嵌套锁时,使用顺序锁,不同的方法内即锁定的多个对象的顺序保持一致。
  • volatile
    它的原理是每次要线程要访问volatile修饰 的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

  • 终止线程
    线程不能或者不建议直接终止,Thread类的stop()方法已废弃,在Android中调用会直接抛出异常。线程不应该粗暴的被停止,因为粗暴的停止那一刻,线程的可能还正在处理任务,持有的资源还未释放,会导致程序运行异常。

     @Deprecated
     public final void stop() {
         stop(new ThreadDeath());
     }
     
     @Deprecated
     public final void stop(Throwable obj) {
         throw new UnsupportedOperationException();
     }
    

    正确的尝试终止或者中断线程的方法是调用interrupt()方法,或者直接调用Thread
    类的静态方法Thread.interrupt()interrupt()方法会给线程设置标记位,调用过该方法后,线程的interrupted()方法将会返回true。需要注意的是在调用interrupted()方法后,线程的interrupted标记将会被重置,即再次调用interrupt()方法将会返回false,安全的结束线程的方法可参考如下实现

     class Demo {
    
         public void run() {
             ThreadTask threadTask = new ThreadTask()
             threadTask.start()
    
             try {
                 Thread.sleep(500);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
    
             threadTask.interrupt();
         }
    
         class ThreadTask extends Thread {
             @Override
             public void run() {
                 for (int i = 0; i < 1_000_000; i++) {
                     if (interrupted()) {
                         Log.d(TAG, "ThreadTask interrupted");
                         return;
                     }
                     //TODO
                 }
             }
         }
     }
    
  • 为什么Thread.sleep()方法需要添加try/catch?

    try {
        Thread.sleep(300);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    是因为在线程sleep的时候,该线程的interrupt方法可能会被调用,当该线程的interrupt方法被调用的时候就会抛出这个异常。抛出异常之后线程的interrupted标记将会被重置,即再次调用interrupt()方法将会返回false。如果在调用sleep之前线程已经被调用interrupt方法,则执行到Thread.sleep()时会直接抛出异常。

  • wait()notifyAll()怎么用?

    wait()方法可以让当前线程释放锁,进入等待队(等待被激活的线程队列), 当notifyAll()被调用的时候,进入wait的线程会被激活,然后继续执行wait()之后的代码。
    通常wait()会和notifyAll()配合使用,一般是涉及到多个线程使用共享资源的时候,notifyAll()可以理解为通知其他线程共享资源已经准备好了,notify()只会激活一个线程,notifyAll()可以激活所有该对象被调用wait()的线程。多个线程调用某个对象的wait()方法后都会进入等待队列,等待notifyAll()的调用。另外wait()notifyAll()都是Object的方法,因为这里涉及到的是线程锁,而这里锁住的是对象,不是线程,而waitnotifyAll的线程一般不是同一个线程,使用时wait()notifyAll()方法被调用的对象一定要是同一个对象。以下代码为wait()notifyAll()常规使用方法。

    public class Task {
    
        private String name;
        private static final String TAG = "ThreadTask";
    
        private synchronized void init() {
            name = "AA";
            notifyAll();
        }
    
        private synchronized void logName() {
            //使用while,线程被激活后再次进行判断,因为线程被激活后name字段不一定是非空
            while (name == null){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.d(TAG, "ThreadTask name is " + name);
        }
    
        public void start() {
            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    logName();
                }
            };
    
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    init();
                }
            };
    
            thread1.start();
            thread2.start();
        }
    }
    
  • join
    通常是在某个线程(A)内调用另一个线程(B)的join()方法,相当于当前线程进入了一个不需要synchronizedwait状态,表示当前线程A执行到此接下来让B线程来执行,即把时间片交给了B线程,当B线程结束后,join()方法之后的代码才会继续执行,如下代码

    public class Task {
    
        private String name;
        private static final String TAG = "ThreadTask";
    
        private synchronized void init() {
            name = "AA";
        }
    
        private synchronized void logName() {
            Log.d(TAG, "ThreadTask name is " + name);
        }
    
        public void start() {
            final Thread thread1 = new Thread() {
                @Override
                public void run() {
                    try {
                     Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    init();
                }
            };
    
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    try {
                        thread1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    logName();
                }
            };
    
            thread1.start();
            thread2.start();
        }
    
    }
    
    
  • yield
    yield()表示把当前线程的时间片交出去,给其他同优先级或者优先级较高的线程执行,保证其他线程的执行。代码如下:

    Thread thread = new Thread() {
        @Override
        public void run() {
            Thread.yield();
            //TODO
        }
    };
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容