美文网首页
Java多线程与Android线程性能优化

Java多线程与Android线程性能优化

作者: 瑜小贤 | 来源:发表于2020-04-07 14:36 被阅读0次

1. 并发编程的基础概念

- CPU核心数和线程数的关系

核心数就是同时能运行的线程数,4核CPU:能同时运行4个线程
Intel超线程技术(1:2):把1个物理CPU模拟成2个逻辑CPU,所以4核能同时运行8个线程

- CPU时间片轮转机制

RR调度,即使只有1个CPU核心数,100个线程,也感觉像是同时运行,因为CPU将时间分片,每个线程循环在时间片内被CPU执行,各个线程极快切换,用户感知上以为是同时在运行。

- 进程和线程

进程:资源分配的最小单位。操作系统要为进程分配CPU,内存资源、磁盘IO等等资源。进程和CPU没有任何关系。
线程:CPU调度的最小单位。线程要依附于进程而存在。

- 并行和并发

并行:一个启动了超线程技术的4核CPU,并行度是 8,可以同时运行8个线程。
并发:在xx时间内,并发量是xxx。一定和时间相关,脱离了时间就无意义了。实现并发最常用的机制就是 时间片轮转机制。例:1s内执行100个时间片:并发量100;1s内执行200个时间片:并发量200。

- 高并发编程的意义、好处和注意事项

模块化、异步化、简单化
注意:安全
并不是线程越多越好,假设1000个线程抢夺8个核心来运行,就会不停的进行上下文切换(即CPU对于需要切换的线程的资源的存取):每次上下文切换大概占用20000个cpu时间周期

2. 天生就是多线程的Java程序

public class OnlyMain{
    public static void main(String[] args){
        //虚拟机线程管理的接口
        ThreadMxBean threadMxBean = ManagementFactory.getThreadMxBean();
        //取得线程信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        for(ThreadInfo threadInfo : threadInfos){
            System.out.println("["+threadInfo.getThreadId()+"]"+" "+threadInfo.getThreadName());
        }
    }
}

测试会新建6个线程:

[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
如何启动线程
  • 类Thread
  • 接口Runnable
  • 接口Callable
//方式1:拓展Thread类,new Thread后start()启动
public class NewThread{
    private static class UseThread extends Thread{
        @Override
        public void run(){
            super.run();
            //do my work
            System.out.println("I am in UseThread");
        }
    }


    //方式2 实现Runnable接口
    private static class UseRun implements Runnable{
        @Override
        public void run(){
          System.out.println("I am in UseRun");
        }
    }

    //方式3 实现Callable接口,允许有返回值
    private static class UseCall implements Callable<String>{
        @Override
        public String call() throws Exception{
            System.out.println("I am in UseCall");
            return "CallRequest";
        }
    }

    public static void main(String[] args){
        UseThread useThread = new UseThread();
        useThread.start();

        UseRun useRun = new UseRun();
        new Thread(useRun).start();

        UseCall useCall = new UseCall();
        //包装一下call,FutureTask实现了RunnableFuture接口,
        //RunnableFuture接口继承了Runnable和Future接口(java接口可以多继承!)
        //Future接口可以获取任务状态、取消任务。
        FutureTask<String> futureTask = new FutureTask<>(useCall);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}
如何让线程停止
  • stop() 废弃
  • suspend() 废弃
  • resume() 废弃
  • destroy() 废弃
  • interrupt(); //协作式 而非 抢占式
  • static方法interrupted(); //判断是否中断
  • isInterrupted() //判断是否中断
public  class EndThread{
    private static class UseThread extends Thread{
        public UseThread(String name){
            super(name);
        }
        @Override
        public void run(){
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " interrupt flag = "+isInterrupted());

            //测试1
            while(true){
                System.out.println(threadName + "is running");
            }

            //测试2
            while(!isInterrupted()){
                System.out.println(threadName + "is running");
            }

            //测试3  
            //当线程被interrupt()中断,flag被设为true,退出while循环,但Thread.interrupted()会修改标志位,所以while外的log会打印出false
            while(!Thread.interrupted()){
                System.out.println(threadName + "is running");
                System.out.println(threadName + "inner interrupt flag = "+isInterrupted());
            }

            System.out.println(threadName + " interrupt flag = "+isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread endThread  = new UseThread("endThread");
        endThread.start();
        Thread.sleep(5);
        endThread.interrupt(); //对interrupt没做处理,则不起作用
    }
}
public class HasInterrputException {
    private static SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");
    
    private static class UseThread extends Thread{
        public UseThread(String name) {
            super(name);
        }
        
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            while(!isInterrupted()) {
                try {
                    System.out.println("UseThread:"+formater.format(new Date()));
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    System.out.println(threadName+" catch interrput flag is "
                            +isInterrupted()+ " at "
                            +(formater.format(new Date())));

                    //TODO 这句很关键
                    //虽然这里异常被捕获了,但flag也被设置为了false
                    interrupt();
                    e.printStackTrace();
                }
                System.out.println(threadName);             
            }
            System.out.println(threadName+" interrput flag is " + isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread useThread = new UseThread("HasInterrputEx");
        useThread.start();
        System.out.println("Main:"+formater.format(new Date()));
        Thread.sleep(800);
        System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
        useThread.interrupt();      
    }
}

补充:

  • Thread的start()方法调用了start0()方法
  • start()之后线程进入就绪状态,也就是可运行状态,至于线程何时从就绪状态进入到运行状态,是操作系统决定的,操作系统给线程分配了时间和资源,线程就进入运行状态,不然就是就绪状态(例如start()interrupt()sleep时间到notify()notifyAll() 之后)。
  • Thread的start()方法不能执行多次,不然会报错
  • Thread的run()方法就相当于普通类的成员方法,单纯的调用不会新建线程
  • join()方法 把指定的线程加入到当前线程中,插队执行。
    【面试:如何让两个线程顺序执行 ---> 使用join
  • yield()方法 当前的线程让出CPU执行权,将线程从运行转到可运行状态。所以也有可能让出执行权之后马上被CPU执行了
  • setDeamon() 将线程在启动之前变成守护线程

3. 线程的生命周期

4. 线程间的共享

不同线程操作相同的变量或代码,为了控制顺序

同步机制

  • synchronized内置锁,因为是Java内置的
    能保证同一时刻,只能有一个线程处于方法或者同步块里面。
public class SynTest {
    private long count =0;
    private Object obj = new Object();//作为一个锁

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    //方法1:对方法进行加锁
    public synchronized void incCount(){
        count++;
    }

    //方法2:对方法中的对象加锁
    public void incCount2(){
        synchronized (obj){
            count++;
        }
    }
   /**
    * 方法1、2都是对象锁
    * 方法1锁的是当前类的对象(this),方法2锁的是obj对象
    */

    public void incCount3(){
        synchronized (this){
            count++;
        }
    }

    //线程
    private static class Count extends Thread{
        private SynTest simplOper;

        public Count(SynTest simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();//count = count+10000
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynTest simplOper = new SynTest();
        //启动两个线程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);//20000
    }
}
演示类锁和对象锁
  • 类锁的说法不是很规范,jdk中没有描述,这只是一个概念,因为类锁锁的是类的class对象。
  • 所以synchronized还是锁的对象
  • 而class对象是什么?是把jdk里的类加载到虚拟机里的时候,虚拟机为这个类产生的一个class对象。
  • 如果对静态类静态变量上锁,不管多少线程执行,都会同步执行,因为静态变量在虚拟机里有且只有一个

总结

  • synchronized 普通方法 ---》 锁当前类的实例
  • synchronized 静态方法 ---》 锁虚拟机只有一份的当前类的class对象
  • synchronized obj ---》 锁当前对象
  • synchronized 静态obj --》 锁虚拟机只有一份的obj静态变量
public class SynClzAndInst {
    //使用类锁的线程
    private static class SynClass extends Thread{
        @Override
        public void run() {
            System.out.println("TestClass is running...");
            synClass();
        }
    }

    //类锁,实际是锁类的class对象,
    //因为static表示静态,与类的对象实例没关系
    private static synchronized void synClass(){
        SleepTools.second(1);
        System.out.println("synClass going...");
        SleepTools.second(1);
        System.out.println("synClass end");
    }

    private static Object obj = new Object(); //静态的,虚拟机内有且只有一个

    private void synStaticObject(){
        synchronized (obj){ //类似于类锁,Obj在全虚拟机只有一份
            SleepTools.second(1);
            System.out.println("synClass going...");
            SleepTools.second(1);
            System.out.println("synClass end");
        }
    }

    //使用对象锁
    private static class SynObject implements Runnable{
        private SynClzAndInst synClzAndInst;

        public SynObject(SynClzAndInst synClzAndInst) {
            this.synClzAndInst = synClzAndInst;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..."+synClzAndInst);
            synClzAndInst.instance();
        }
    }

    //使用对象锁
    private static class SynObject2 implements Runnable{
        private SynClzAndInst synClzAndInst;

        public SynObject2(SynClzAndInst synClzAndInst) {
            this.synClzAndInst = synClzAndInst;
        }
        @Override
        public void run() {
            System.out.println("TestInstance2 is running..."+synClzAndInst);
            synClzAndInst.instance2();
        }
    }

    //锁对象
    private synchronized void instance(){
        SleepTools.second(3);
        System.out.println("synInstance is going..."+this.toString());
        SleepTools.second(3);
        System.out.println("synInstance ended "+this.toString());
    }
    
    //锁对象
    private synchronized void instance2(){
        SleepTools.second(3);
        System.out.println("synInstance2 is going..."+this.toString());
        SleepTools.second(3);
        System.out.println("synInstance2 ended "+this.toString());
    }

    public static void main(String[] args) {
        SynClzAndInst synClzAndInst = new SynClzAndInst();
        Thread t1 = new Thread(new SynObject(synClzAndInst));

        SynClzAndInst synClzAndInst2 = new SynClzAndInst();
        Thread t2 = new Thread(new SynObject2(synClzAndInst2));

        Thread t3 = new Thread(new SynObject2(synClzAndInst));

-------------------------------------------------------------------------------------
        t1.start(); //t1锁的是synClzAndInst这个对象
        t2.start(); //t2锁的是synClzAndInst2这个对象
        //两个线程不会相互影响,因为两个线程传入的对象不是同一个,各自被锁对于线程执行没有影响

-------------------------------------------------------------------------------------
        t1.start();
        t3.start();
        //t1执行完,才会执行t3,因为两个线程中传的是同一个对象,而对象又被锁了

-------------------------------------------------------------------------------------
       
        t1.start();
        t3.start();
        SynClass synClass = new SynClass();
        synClass.start();
        SleepTools.second(1);
        //t3仍然等待t1执行完才执行,但是类锁的synClass与对象锁互不影响
    }
}

5. 线程间的协作

等待和通知

- wait()

让当前线程进入等待状态
不是线程的,是Object的

- notify/notifyAll

通知当前等待的线程
notify只会通知一个等待线程,notifyAll通知所有在对象上等待的线程
不是线程的,是Object的

- 等待和通知的标准范式

等待方:

  1. 获取对象的锁
  2. 检查条件,条件不满足 wait
  3. 条件满足,执行业务代码
syn(obj){
   while(条件不满足){
       obj.wait(); //调用wait后会释放锁,便于通知方获取锁
   }
   执行业务代码
}

通知方:

  1. 获取对象的锁
  2. 修改条件
  3. 通知等待方
syn(obj){
   执行业务代码,修改条件
   obj.notify()/notifyAll(); //这里不会释放锁
} //这里执行完会释放锁

补充:yield、sleep都不会释放锁

  • notify和notifyAll应该用谁
    一般情况下尽量用notifyAll
    如果业务条件满足的话,用notify也没有问题
    jdk中没有指定唤醒某个线程的api

6. 线程隔离的ThreadLocal

7. Java里的显式锁

相关文章

网友评论

      本文标题:Java多线程与Android线程性能优化

      本文链接:https://www.haomeiwen.com/subject/erihphtx.html