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

2020-04-07  本文已影响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
如何启动线程
//方式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());
    }
}
如何让线程停止
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();      
    }
}

补充:

3. 线程的生命周期

4. 线程间的共享

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

同步机制

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
    }
}
演示类锁和对象锁

总结

  • 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都不会释放锁

6. 线程隔离的ThreadLocal

7. Java里的显式锁

上一篇 下一篇

猜你喜欢

热点阅读