精华内容
下载资源
问答
  • 线程(一):创建线程线程的常用方法

    万次阅读 多人点赞 2018-09-01 19:14:23
    一:为什么要学多线程 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也...
    分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。

    一:为什么要学多线程

    1. 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。
    2. 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),虽然代码中很少会自己去创建线程,但是实际环境中每行代码却都是并行执行的,同一时刻大量请求同一个接口,并发可能会产生一些问题,所以也需要掌握一定的并发知识

    二:进程与线程

    1. 进程

    进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

    2. 线程

    线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

    一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。

    线程是一条可以执行的路径。

    • 对于单核CPU而言:多线程就是一个CPU在来回的切换,在交替执行。
    • 对于多核CPU而言:多线程就是同时有多条执行路径在同时(并行)执行,每个核执行一个线程,多个核就有可能是一块同时执行的。

    3. 进程与线程的关系

    一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:

    • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。

    • 处理机分给线程,即真正在处理机上运行的是线程。

    • 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

    如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答,此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)

    4. 为什么要使用多线程

    多线程可以提高程序的效率。

    实际生活案例:村长要求喜洋洋在一个小时内打100桶水,可以喜洋洋一个小时只能打25桶水,如果这样就需要4个小时才能完成任务,为了在一个小时能够完成,喜洋洋就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样4只羊同时干活,在一小时内完成了任务。原本用4个小时完成的任务现在只需要1个小时就完成了,如果把每只羊看做一个线程,多只羊即多线程可以提高程序的效率。

    5. 多线程应用场景

    • 一般线程之间比较独立,互不影响
    • 一个线程发生问题,一般不影响其它线程

    三:多线程的实现方式

    1. 顺序编程

    顺序编程:程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。

    public class Main {
        // 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚才能喝酒
        public static void main(String[] args) throws Exception {
    		// 先吃饭再喝酒
            eat();
            drink();
        }
    
        private static void eat() throws Exception {
            System.out.println("开始吃饭?...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束吃饭?...\t" + new Date());
        }
    
        private static void drink() throws Exception {
            System.out.println("开始喝酒?️...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    这里写图片描述

    2. 并发编程

    并发编程:多个任务可以同时做,常用与任务之间比较独立,互不影响。
    线程上下文切换:

    同一个时刻一个CPU只能做一件事情,即同一时刻只能一个线程中的部分代码,假如有两个线程,Thread-0和Thread-1,刚开始CPU说Thread-0你先执行,给你3毫秒时间,Thread-0执行了3毫秒时间,但是没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,当时的变量的值是多少,然后CPU说Thread-1你可以执行了,给你2毫秒的时间,Thread-1执行了2毫秒也没执行完,此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,当时的变量的值是多少,此时CPU又说Thread-0又该你,这次我给你5毫秒时间,去执行吧,此时CPU就找出上次Thread-0线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续执行,结果用了2毫秒就Thread-0就执行完了,就终止了,然后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续开始执行,结果Thread-1在4毫秒内也执行结束了,Thread-1也结束了终止了。CPU在来回改变线程的执行机会称之为线程上下文切换。

    public class Main {
        public static void main(String[] args) {
    	    // 一边吃饭一边喝酒
            new EatThread().start();
            new DrinkThread().start();
        }
    }
    
    class EatThread extends Thread{
        @Override
        public void run() {
            System.out.println("开始吃饭?...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束吃饭?...\t" + new Date());
        }
    }
    
    class DrinkThread extends Thread {
        @Override
        public void run() {
            System.out.println("开始喝酒?️...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    并发编程,一边吃饭一边喝酒总共用时5秒,比顺序编程更快,因为并发编程可以同时运行,而不必等前面的代码运行完之后才允许后面的代码

    这里写图片描述

    本示例主要启动3个线程,一个主线程main thread、一个吃饭线程(Thread-0)和一个喝酒线程(Thread-1),共三个线程, 三个线程并发切换着执行。main线程很快执行完,吃饭线程和喝酒线程会继续执行,直到所有线程(非守护线程)执行完毕,整个程序才会结束,main线程结束并不意味着整个程序结束。
    这里写图片描述

    • 顺序:代码从上而下按照固定的顺序执行,只有上一件事情执行完毕,才能执行下一件事。就像物理电路中的串行,假如有十件事情,一个人来完成,这个人必须先做第一件事情,然后再做第二件事情,最后做第十件事情,按照顺序做。

    • 并行:多个操作同时处理,他们之间是并行的。假如十件事情,两个人来完成,每个人在某个时间点各自做各自的事情,互不影响

    • 并发:将一个操作分割成多个部分执行并且允许无序处理,假如有十件事情,如果有一个人在做,这个人可能做一会这个不想做了,再去做别的,做着做着可能也不想做了,又去干其它事情了,看他心情想干哪个就干哪个,最终把十件事情都做完。如果有两个人在做,他们俩先分一下,比如张三做4件,李四做6件,他们各做自己的,在做自己的事情过程中可以随意的切换到别的事情,不一定要把某件事情干完再去干其它事情,有可能一件事做了N次才做完。

    通常一台电脑只有一个cpu,多个线程属于并发执行,如果有多个cpu,多线程并发执行有可能变成并行执行。
    这里写图片描述

    3. 多线程创建方式

    • 继承 Thread
    • 实现 Runable
    • 实现 Callable
    ①:继成java.lang.Thread, 重写run()方法
    public class Main {
        public static void main(String[] args) {
            new MyThread().start();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    Thread 类

    package java.lang;
    public class Thread implements Runnable {
    	// 构造方法
    	public Thread(Runnable target);
    	public Thread(Runnable target, String name);
    	
    	public synchronized void start();
    }
    

    Runnable 接口

    package java.lang;
    
    @FunctionalInterface
    public interface Runnable {
        pubic abstract void run();
    }
    

    ②:实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装

    public class Main {
        public static void main(String[] args) {
        	 // 将Runnable实现类作为Thread的构造参数传递到Thread类中,然后启动Thread类
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable).start();
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    可以看到两种方式都是围绕着Thread和Runnable,继承Thread类把run()写到类中,实现Runnable接口是把run()方法写到接口中然后再用Thread类来包装, 两种方式最终都是调用Thread类的start()方法来启动线程的。
    两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式

    其它变体写法:

    public class Main {
        public static void main(String[] args) {
            // 匿名内部类
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }).start();
    
            // 尾部代码块, 是对匿名内部类形式的语法糖
            new Thread() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }.start();
    
            // Runnable是函数式接口,所以可以使用Lamda表达式形式
            Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());};
            new Thread(runnable).start();
        }
    }
    

    ③:实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread

    Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

    public class Main {
        public static void main(String[] args) throws Exception {
        	 // 将Callable包装成FutureTask,FutureTask也是一种Runnable
            MyCallable callable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
    
            // get方法会阻塞调用的线程
            Integer sum = futureTask.get();
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
        }
    }
    
    
    class MyCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
    
            int sum = 0;
            for (int i = 0; i <= 100000; i++) {
                sum += i;
            }
            Thread.sleep(5000);
    
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
            return sum;
        }
    }
    

    Callable 也是一种函数式接口

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    FutureTask

    public class FutureTask<V> implements RunnableFuture<V> {
    	// 构造函数
    	public FutureTask(Callable<V> callable);
    	
    	// 取消线程
    	public boolean cancel(boolean mayInterruptIfRunning);
    	// 判断线程
    	public boolean isDone();
    	// 获取线程执行结果
    	public V get() throws InterruptedException, ExecutionException;
    }
    

    RunnableFuture

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

    三种方式比较:

    • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
    • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
    • Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
    • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
    • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

    四:线程的状态

    1. 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
    2. 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
    3. 运行(running)状态: 执行run()方法
    4. 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
    5. 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

    这里写图片描述

    这里写图片描述

    这里写图片描述

    五:Thread常用方法

    Thread

    public class Thread implements Runnable {
        // 线程名字
        private volatile String name;
        // 线程优先级(1~10)
        private int priority;
        // 守护线程
        private boolean daemon = false;
        // 线程id
        private long tid;
        // 线程组
        private ThreadGroup group;
        
        // 预定义3个优先级
        public final static int MIN_PRIORITY = 1;
        public final static int NORM_PRIORITY = 5;
        public final static int MAX_PRIORITY = 10;
        
        
        // 构造函数
        public Thread();
        public Thread(String name);
        public Thread(Runnable target);
        public Thread(Runnable target, String name);
        // 线程组
        public Thread(ThreadGroup group, Runnable target);
        
        
        // 返回当前正在执行线程对象的引用
        public static native Thread currentThread();
        
        // 启动一个新线程
        public synchronized void start();
        // 线程的方法体,和启动线程没毛关系
        public void run();
        
        // 让线程睡眠一会,由活跃状态改为挂起状态
        public static native void sleep(long millis) throws InterruptedException;
        public static void sleep(long millis, int nanos) throws InterruptedException;
        
        // 打断线程 中断线程 用于停止线程
        // 调用该方法时并不需要获取Thread实例的锁。无论何时,任何线程都可以调用其它线程的interruptf方法
        public void interrupt();
        public boolean isInterrupted()
        
        // 线程是否处于活动状态
        public final native boolean isAlive();
        
        // 交出CPU的使用权,从运行状态改为挂起状态
        public static native void yield();
        
        public final void join() throws InterruptedException
        public final synchronized void join(long millis)
        public final synchronized void join(long millis, int nanos) throws InterruptedException
        
        
        // 设置线程优先级
        public final void setPriority(int newPriority);
        // 设置是否守护线程
        public final void setDaemon(boolean on);
        // 线程id
        public long getId() { return this.tid; }
        
        
        // 线程状态
        public enum State {
            // new 创建
            NEW,
    
            // runnable 就绪
            RUNNABLE,
    
            // blocked 阻塞
            BLOCKED,
    
            // waiting 等待
            WAITING,
    
            // timed_waiting
            TIMED_WAITING,
    
            // terminated 结束
            TERMINATED;
        }
    }
    
    public static void main(String[] args) {
        // main方法就是一个主线程
    
        // 获取当前正在运行的线程
        Thread thread = Thread.currentThread();
        // 线程名字
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否存活
        boolean alive = thread.isAlive();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
    
        // Thread[name=main, id=1 ,priority=5 ,alive=true ,daemon=false]
        System.out.println("Thread[name=" + name + ", id=" + id + " ,priority=" + priority + " ,alive=" + alive + " ,daemon=" + daemon + "]");
    }
    
    0. Thread.currentThread()
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        // 线程名称
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程已经启动且尚未终止
        // 线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的
        boolean alive = thread.isAlive();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
        
        // Thread[name=main,id=1,alive=true,priority=5,daemon=false]
        System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");
    }
    
    1. start() 与 run()
    public static void main(String[] args) throws Exception {
       new Thread(()-> {
           for (int i = 0; i < 5; i++) {
               System.out.println(Thread.currentThread().getName() + " " + i);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-A").start();
    
       new Thread(()-> {
           for (int j = 0; j < 5; j++) {
               System.out.println(Thread.currentThread().getName() + " " + j);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-B").start();
    }
    

    start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
    这里写图片描述

    public static void main(String[] args) throws Exception {
        new Thread(()-> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-A").run();
    
        new Thread(()-> {
            for (int j = 0; j < 5; j++) {
                System.out.println(Thread.currentThread().getName() + " " + j);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-B").run();
    }
    

    注意:执行结果都是main主线程
    这里写图片描述

    run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。

    有些面试官经常问一些启动一个线程是用start()方法还是run()方法,为了面试而面试。

    2. sleep() 与 interrupt()
    public static native void sleep(long millis) throws InterruptedException;
    public void interrupt();
    

    sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。

    • sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
    • interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
    public static void main(String[] args) throws Exception {
        Thread thread0 = new Thread(()-> {
            try {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,让我睡10秒,中间有事叫我,zZZ。。。");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要继续干活了");
            }
        });
        thread0.start();
    
        // 这里睡眠只是为了保证先让上面的那个线程先执行
        Thread.sleep(2000);
    
        new Thread(()-> {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,别睡了,起来干活了!!!");
            // 无需获取锁就可以调用interrupt
            thread0.interrupt();
        }).start();
    }
    

    这里写图片描述

    3. wait() 与 notify()

    wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()

    • wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
    • notify(): 该方法只能在同步方法或同步块内部调用, 随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
    • notifyAll(): 唤醒所有的wait对象

    注意:

    • Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部
    • 让哪个对象等待wait就去通知notify哪个对象,不要让A对象等待,结果却去通知B对象,要操作同一个对象

    Object

    public class Object {
    	public final void wait() throws InterruptedException;
    	public final native void wait(long timeout) throws InterruptedException;
    	public final void wait(long timeout, int nanos) throws InterruptedException;
    	
    	
    	public final native void notify();
    	public final native void notifyAll();
    }
    

    WaitNotifyTest

    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡觉1秒中,目的是让上面的线程先执行,即先执行wait()");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                waitNotifyTest.notifyPrint();
            }).start();
        }
    
        private synchronized void printFile() throws InterruptedException {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
            this.wait();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
        }
    
        private synchronized void notifyPrint() {
            this.notify();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
        }
    }
    
    

    这里写图片描述

    wait():让程序暂停执行,相当于让当前,线程进入当前实例的等待队列,这个队列属于该实例对象,所以调用notify也必须使用该对象来调用,不能使用别的对象来调用。调用wait和notify必须使用同一个对象来调用。
    这里写图片描述

    this.notifyAll();
    这里写图片描述

    4. sleep() 与 wait()
    ① Thread.sleep(long millis): 睡眠时不会释放锁
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    try { Thread.sleep(1000); } catch (InterruptedException e) { }
                }
            }
        }).start();
    
        Thread.sleep(1000);
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                }
            }
        }).start();
    }
    

    因main方法中Thread.sleep(1000)所以上面的线程Thread-0先被执行,当循环第一次时就会Thread.sleep(1000)睡眠,因为sleep并不会释放锁,所以Thread-1得不到执行的机会,所以直到Thread-0执行完毕释放锁对象lock,Thread-1才能拿到锁,然后执行Thread-1;
    这里写图片描述

    5. wait() 与 interrupt()

    wait(): 方法的作用是释放锁,加入到等待队列,当调用interrupt()方法后,线程必须先获取到锁后,然后才抛出异常InterruptedException 。注意: 在获取锁之前是不会抛出异常的,只有在获取锁之后才会抛异常

    所有能抛出InterruptedException的方法都可以通过interrupt()来取消的

    public static native void sleep(long millis) throws InterruptedException;
    public final void wait() throws InterruptedException;
    public final void join() throws InterruptedException;
    public void interrupt()

    notify()和interrupt()
    从让正在wait的线程重新运行这一点来说,notify方法和intterrupt方法的作用有些类似,但仍有以下不同之处:

    • notify/notifyAll是java.lang.Object类的方法,唤醒的是该实例的等待队列中的线程,而不能直接指定某个具体的线程。notify/notifyAll唤醒的线程会继续执行wait的下一条语句,另外执行notify/notifyAll时线程必须要获取实例的锁

    • interrupte方法是java.lang.Thread类的方法,可以直接指定线程并唤醒,当被interrupt的线程处于sleep或者wait中时会抛出InterruptedException异常。执行interrupt()并不需要获取取消线程的锁。

    • 总之notify/notifyAll和interrupt的区别在于是否能直接让某个指定的线程唤醒、执行唤醒是否需要锁、方法属于的类不同

    6. interrupt()

    有人也许认为“当调用interrupt方法时,调用对象的线程就会InterruptedException异常”, 其实这是一种误解,实际上interrupt方法只是改变了线程的“中断状态”而已,所谓中断状态是一个boolean值,表示线程是否被中断的状态。

    public class Thread implements Runnable {
    	public void interrupt() {
    		中断状态 = true;
    	}
    	
    	// 检查中断状态
    	public boolean isInterrupted();
    	
    	// 检查中断状态并清除当前线程的中断状态
    	public static boolean interrupted() {
    		// 伪代码
    		boolean isInterrupted = isInterrupted();
    		中断状态 = false;
    	}
    }	
    

    假设Thread-0执行了sleep、wait、join中的一个方法而停止运行,在Thread-1中调用了interrupt方法,此时线程Thread-0的确会抛出InterruptedException异常,但这其实是sleep、wait、join中的方法内部会对线程的“中断状态”进行检查,如果中断状态为true,就会抛出InterruptedException异常。假如某个线程的中断状态为true,但线程体中却没有调用或者没有判断线程中断状态的值,那么线程则不会抛出InterruptedException异常。

    isInterrupted() 检查中断状态
    若指定线程处于中断状态则返回true,若指定线程为非中断状态,则反回false, isInterrupted() 只是获取中断状态的值,并不会改变中断状态的值。

    interrupted()
    检查中断状态并清除当前线程的中断状态。如当前线程处于中断状态返回true,若当前线程处于非中断状态则返回false, 并清除中断状态(将中断状态设置为false), 只有这个方法才可以清除中断状态,Thread.interrupted的操作对象是当前线程,所以该方法并不能用于清除其它线程的中断状态。

    interrupt()与interrupted()

    • interrupt():打断线程,将中断状态修改为true
    • interrupted(): 不打断线程,获取线程的中断状态,并将中断状态设置为false

    这里写图片描述

    public class InterrupptTest {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
            boolean interrupted = thread.isInterrupted();
            // interrupted=false
            System.out.println("interrupted=" + interrupted);
    
            thread.interrupt();
    
            boolean interrupted2 = thread.isInterrupted();
            // interrupted2=true
            System.out.println("interrupted2=" + interrupted2);
    
            boolean interrupted3 = Thread.interrupted();
            // interrupted3=false
            System.out.println("interrupted3=" + interrupted3);
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // InterruptedException	false
                    System.out.println("InterruptedException\t" + Thread.currentThread().isInterrupted());
                }
            }
        }
    }
    
    

    这里写图片描述

    ② object.wait(long timeout): 会释放锁
    public class SleepWaitTest {
        public static void main(String[] args) throws InterruptedException {
            SleepWaitTest object = new SleepWaitTest();
    
            new Thread(() -> {
                synchronized (object) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
                    try {
                        object.wait(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
                }
            }).start();
    
    		 // 先上面的线程先执行
            Thread.sleep(1000);
    
            new Thread(() -> {
                synchronized (object) {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    }
                }
            }).start();
        }
    }
    

    因main方法中有Thread.sleep(1000)所以上面的线程Thread-0肯定会被先执行,当Thread-0被执行时就拿到了object对象锁,然后进入wait(5000)5秒钟等待,此时wait释放了锁,然后Thread-1就拿到了锁就执行线程体,Thread-1执行完后就释放了锁,当等待5秒后Thread-0就能再次获取object锁,这样就继续执行后面的代码。wait方法是释放锁的,如果wait方法不释放锁那么Thread-1是拿不到锁也就没有执行的机会的,事实是Thread-1得到了执行,所以说wait方法会释放锁

    这里写图片描述

    ③ sleep与wait的区别
    • sleep在Thread类中,wait在Object类中
    • sleep不会释放锁,wait会释放锁
    • sleep使用interrupt()来唤醒,wait需要notify或者notifyAll来通知
    5.join()

    让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

    将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用

    join() 方法:

    public final void join() throws InterruptedException {
            join(0);
    }
    
    
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
        	 // 循环检查线程的状态是否还活着,如果死了就结束了,如果活着继续等到死
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    
    public final synchronized void join(long millis, int nanos) throws InterruptedException {
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
    
        join(millis);
    }
    
    

    JoinTest

    public class JoinTest {
        public static void main(String[] args) {
            new Thread(new ParentRunnable()).start();
        }
    }
    
    class ParentRunnable implements Runnable {
        @Override
        public void run() {
            // 线程处于new状态
            Thread childThread = new Thread(new ChildRunable());
            // 线程处于runnable就绪状态
            childThread.start();
            try {
                // 当调用join时,parent会等待child执行完毕后再继续运行
                // 将某个线程加入到当前线程
                childThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程 running");
            }
        }
    }
    
    class ChildRunable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程 running");
            }
        }
    }
    
    

    程序进入主线程,运行Parent对应的线程,Parent的线程代码分两段,一段是启动一个子线程,一段是Parent线程的线程体代码,首先会将Child线程加入到Parent线程,join()方法会调用join(0)方法(join()方法是普通方法并没有加锁,join(0)会加锁),join(0)会执行while(isAlive()) { wait(0);} 循环判断线程是否处于活动状态,如果是继续wait(0)知道isAlive=false结束掉join(0), 从而结束掉join(), 最后回到Parent线程体中继续执行其它代码。

    在Parent调用child.join()后,child子线程正常运行,Parent父线程会等待child子线程结束后再继续运行。
    这里写图片描述

    • join() 和 join(long millis, int nanos) 最后都调用了 join(long millis)。

    • join(long millis, int nanos)和join(long millis)方法 都是synchronized。

    • join() 调用了join(0),从源码可以看到join(0)不断检查当前线程是否处于Active状态。

    • join() 和 sleep() 一样,都可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了wait(),会出让锁,而 sleep() 会一直保持锁。

    6. yield()

    交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间,yield就像一个好人似的,当CPU轮到它了,它却说我先不急,先给其他线程执行吧, 此方法很少被使用到,

    /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();
    

    这里写图片描述

    public static void main(String[] args) {
        new Thread(new Runnable() {
            int sum = 0;
            @Override
            public void run() {
                long beginTime=System.currentTimeMillis();
                for (int i = 0; i < 99999; i++) {
                    sum += 1;
                    // 去掉该行执行用2毫秒,加上271毫秒
                    Thread.yield();
                }
                long endTime=System.currentTimeMillis();
                System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");
            }
        }).start();
    }
    

    sleep(long millis) 与 yeid()

    • sleep(long millis): 需要指定具体睡眠的时间,不会释放锁,睡眠期间CPU会执行其它线程,睡眠时间到会立刻执行
    • yeid(): 交出CPU的执行权,不会释放锁,和sleep不同的时当再次获取到CPU的执行,不能确定是什么时候,而sleep是能确定什么时候再次执行。两者的区别就是sleep后再次执行的时间能确定,而yeid是不能确定的
    • yield会把CPU的执行权交出去,所以可以用yield来控制线程的执行速度,当一个线程执行的比较快,此时想让它执行的稍微慢一些可以使用该方法,想让线程变慢可以使用sleep和wait,但是这两个方法都需要指定具体时间,而yield不需要指定具体时间,让CPU决定什么时候能再次被执行,当放弃到下次再次被执行的中间时间就是间歇等待的时间
    7. setDaemon(boolean on)

    线程分两种:

    • 用户线程:如果主线程main停止掉,不会影响用户线程,用户线程可以继续运行。
    • 守护线程:如果主线程死亡,守护线程如果没有执行完毕也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程执行结束,子线程仍然继续执行,主线程和用户线程的生命周期各自独立。");
    }
    

    这里写图片描述

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.setDaemon(true);
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程死亡,子线程也要陪着一块死!");
    }
    

    这里写图片描述

    六 线程组

    可以对线程分组,分组后可以统一管理某个组下的所有线程,例如统一中断所有线程

    public class ThreadGroup implements Thread.UncaughtExceptionHandler {
        private final ThreadGroup parent;
        String name;
        int maxPriority;
        
        Thread threads[];
        
        private ThreadGroup() {
            this.name = "system";
            this.maxPriority = Thread.MAX_PRIORITY;
            this.parent = null;
        }
        
        public ThreadGroup(String name) {
            this(Thread.currentThread().getThreadGroup(), name);
        }
        
        public ThreadGroup(ThreadGroup parent, String name) {
            this(checkParentAccess(parent), parent, name);
        }
        
        // 返回此线程组中活动线程的估计数。 
        public int activeGroupCount();
        
        // 中断此线程组中的所有线程。
        public final void interrupt();
    }
    
    public static void main(String[] args) {
        String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
        System.out.println(mainThreadGroupName);
        // 如果一个线程没有指定线程组,默认为当前线程所在的线程组
        new Thread(() -> { }, "my thread1").start();
    
        ThreadGroup myGroup = new ThreadGroup("MyGroup");
        myGroup.setMaxPriority(5);
    
        Runnable runnable = () -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            String groupName = threadGroup.getName();
            ThreadGroup parentGroup = threadGroup.getParent();
            String parentGroupName = parentGroup.getName();
            ThreadGroup grandpaThreadGroup = parentGroup.getParent();
            String grandpaThreadGroupName = grandpaThreadGroup.getName();
            int maxPriority = threadGroup.getMaxPriority();
            int activeCount = myGroup.activeCount();
    
            // system <- main <- MyGroup(1) <- my thread2
            System.out.println(MessageFormat.format("{0} <- {1} <- {2}({3}) <- {4}",
                    grandpaThreadGroupName,
                    parentGroupName,
                    groupName,
                    activeCount,
                    Thread.currentThread().getName()));
        };
    
        new Thread(myGroup, runnable, "my thread2").start();
    }
    

    线程组与线程组之间是有父子关系的,自定义线程组的父线程组是main线程组,main线程组的父线程组是system线程组。
    这里写图片描述

    展开全文
  • 多核CPU几个线程最好

    千次阅读 2015-05-18 10:03:37
    IO开销较多的应用其CPU线程等待时间会比较长,所以线程数量可以的多一些,相反则线程数量要少一些,其实有两种极端,纯IO的应用,比如proxy,则线程数量可以到非常大(实在太大了则需要考虑线程切换的开销)
    

    影响最佳线程数的主要因素:

    1、IO

    2、CPU

    根据公式:服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) * cpu数量

    一般来说是IO和CPU。IO开销较多的应用其CPU线程等待时间会比较长,所以线程数量可以开的多一些,相反则线程数量要少一些,其实有两种极端,纯IO的应用,比如proxy,则线程数量可以开到非常大(实在太大了则需要考虑线程切换的开销),这种应用基本上后端(比如这个proxy是代理搜索的)的QPS能有多少,proxy就有多少。

    另一种是耗CPU的计算,这种情况一般来讲只能开到CPU个数的线程数量。但是并不是说这种应用的QPS就不高,往往这种应用的QPS可以很高。

    上面的有点难计算,简便公式是CPU数量 *2 + 2

    但是你要知道一台电脑上不可能只跑你一个程序,所以别太认真了

    展开全文
  • 最近在做一个项目,项目要用到多线程,但是我不知道应该多少个线程,听同事介绍说这种情况可以做一个压力测试,您能帮我解释一下压力测试吗?除此之外还有其他的办法吗?
  • 线程

    千次阅读 多人点赞 2021-04-14 14:24:30
    线程一、进程、线程1.1 进程1.2 线程1.3 java程序的进程和线程1.4 主线程、子线程 一、进程、线程 java语言之所以有多线程,目的是提高程序的运行效率。 1.1 进程 进程是一个应用程序(一个进程是一个软件...

    一、进程、线程

    java语言之所以有多线程,目的是提高程序的运行效率。
    

    1.1 进程

     进程是一个应用程序(一个进程是一个软件)。
    

    1.2 线程

    线程是一个进程中的执行场景、执行单元。一个进程可以启动多个线程
    

    1.3 java程序的进程和线程

    对于java程序来说,当在DOS命令窗口中输入:
    java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。

    JVM再启动一个主线程调用主方法(main),同时在启动一个垃圾回收的线程负责看护,回收垃圾。

    所以,目前java程序至少有2个线程并发,一个是执行main方法的主线程。一个是垃圾回收线程。

    两个进程内存独立不会共享,同一个进程的两个线程堆内存和方法区共享,但是栈内存独立,一个线程一个栈。

    假设有10个线程就有十个栈。每个栈直接互不干扰,各自执行,这就是多线程并发

    1.4 主线程、子线程

    主线程结束了,子线程还会执行吗?

    	Thread t1=new Thread(new Runnable() {
    			
    			
    			public void run() {
    				for (int i = 0; i <= 100; i++) {
    					if(i==100) {
    						System.out.println("子线程执行结束了!");
    					}
    					
    				}
    				
    			}
    		});
    		
    		t1.start();
    		System.out.println("主线程执行结束了!");
    

    运行结果:
    主线程执行结束了!
    子线程执行结束了!
    在这里插入图片描述

    通过测试可以看出主线程结束了,子线程仍然还在运行

    有没有函数可以让主线程等子线程结束了才结束运行主线程
    有的。
    通过在主线程的任务中用子线程调用join()就可。
    join解释成:等待线程死亡。

    public static void main(String[] args) throws InterruptedException {
    		test t2=new test();
    		
    		t2.start();
    		t2.join();
    		
    		System.out.println("主线程执行");
    
    	}
    
    }
    class test extends Thread{
    	public void run() {
    		System.out.println("子线程执行");
    	}
    }
    

    输出结果:
    子线程执行
    主线程执行

    二、多线程

    2.1 单核cpu可以做到多线程并发吗?

    什么是真正的多线程并发?对于多核cpu电脑真正的多线程并发肯定没有问题。

    t1线程执行t1的,t2线程执行t2的,t1不会影响t2的,t2也不会影响t1的,这就做真正的多线程并发。
    单核的cup只有一个‘大脑’,不能做到真正的多线程并发,但是可以给人一种多线程并发的感觉。
    对于单核的cup来说,在某个时间点上实际只能处理一件事情,由于cup速度极快,多个线程频繁切换,给人造成多
    线程并发的错觉。
    

    举个例子:

    网上最近很火的小书,通过大拇指的松动给人一种画面在动的错觉。

    还有很著名的”膝跳反应原理“,拿小锤在你膝盖上敲,到感到痛觉,对于人的反应来说已经很快了,觉得就是同步的,其实不是,它会有个反应时间,这个反应时间对于我们人来说感觉不到,但是对于计算机来说,可以进行数亿次运算了,人类的大脑就好比单核计算机的cpu。

    2.2 实现多线程方式

    2.2.1 子类继承父类(Thread)

    直接通过子类去继承父类(Thread),重写里面的run()方法
    
    public static void main(String[] args) {
    		//main方法在主线程中,在主栈运行
    		//创建一个线程的对象
    		MyThread myThread=new MyThread();
    		
    		//启动线程
    		myThread.start();
    		
    		for (int i = 0; i < 10; i++) {
    			System.out.println("主线程----->"+i);
    		}
    
    	}
    
    }
    class MyThread extends Thread{
    	@Override
    	public void run() {
    //		编写程序,运行在分支线程(栈)中
    		for (int i = 0; i < 10; i++) {
    			System.out.println("分支线程---->"+i);
    		}
    		
    	}
    }
    
    

    2.2.2 start()方法

    start()方法的作用是:在JVM中开辟一块新的栈空间,开启后start()方法瞬间结束。
    只要空间开出来,线程就启动成功了,分支线程自动调用run()方法执行程序,并且start()方法会在分支栈的最底部,和主线程的main方法差不多。
    在这里插入图片描述
    下面代码有什么区别?

    myThread.run();
    myThread.start();
    

    直接调用run方法,分支栈并没有开辟出来,所以还是单线程。
    而调用start方法,分支栈开辟出来,启动了多线程。

    2.2.3 实现Runnable接口

    Runnable并不是一个线程类,而是一个可运行的类,在主线程中创建一个可运行的对象,将可运行的对象封装成一个线程对象。

    public static void main(String[] args) {
    		
    		MyRunnable myRunnable=new MyRunnable();
    		Thread thread=new Thread(myRunnable);
    		thread.start();
    		
    		for (int i = 0; i < 10; i++) {
    			System.out.println("主线程----->"+i);
    		}
    
    	}
    
    }
    
    class MyRunnable implements Runnable{
    
    	
    	public void run() {
    //		编写程序,运行在分支线程(栈)中
    		for (int i = 0; i < 10; i++) {
    			System.out.println("分支线程---->"+i);
    		}
    		
    	}
    
    

    2.2.4 采用匿名内部类

    public static void main(String[] args) {
    
    
    		
    		Thread t=new Thread(new Runnable() {
    			
    			
    			public void run() {
    				System.out.println("支线程运行");
    				
    			}
    		});
    		
    		t.start();
    		System.out.println("主线程执行");
    
    	}
    

    2.2.5 实现callable接口

    众所周知,前面两种线程不会有返回值,还有另一种线程的实现方法可以有返回值
    
    public static void main(String[] args) throws InterruptedException, ExecutionException {
    		// 创建未来任务对象
    		FutureTask <Integer>future=new FutureTask<Integer>(new Callable<Integer>() {
    		
    			
    			public Integer call() throws Exception {
    				int a=10;
    				int b=20;
    				return a+b;
    			}
    		});
    		
    		
    			Thread t1=new Thread(future);
    			t1.start();
    			
    			System.out.println(future.get());
    	}
    

    小结:这种线程实现的方式可以获取到线程结束后返回的值,call方法类似于run方法,不同的是:call方法会有返回值。

    这让我想起来前一段时间的一道考试题,这道题我就卡在了不能获取线程的返回结果,学习到这,我重新想到那题的解决办法:

    问题是:创建2个线程,一个线程求100内的偶数,一个线程求100内余数,并求和。
    
    public static void main(String[] args) throws InterruptedException, ExecutionException {
    	    	
    	    	
    	    	FutureTask<Integer> future=new FutureTask<Integer>(new Callable<Integer>() {
    			
    	    	
    	    	public Integer call() throws Exception {
    	    		int num=0;
    	    		for (int i = 1; i <=100; i++) {
    					if(i%2==0) {
    						num+=i;
    					}
    				}
    	    		return num;
    	    		
    	    	}
    	    	
    	    	
    	    	});
    	    	
    	    	FutureTask<Integer> future2=new FutureTask<Integer>(new Callable<Integer>() {
    				
    		    	
    		    	public Integer call() throws Exception {
    		    		int num2=0;
    		    		for (int i = 1; i <=100; i++) {
    						if(i%2!=0) {
    							num2+=i;
    						}
    					}
    		    		return num2;
    		    		
    		    	}
    		    	
    		    	
    		    	});
    	    	
    	    	Thread t1=new Thread(future);
    	    	Thread t2=new Thread(future2);
    	    	t1.start();
    	    	t2.start();
    	    	System.out.println(future.get()+future2.get());
    

    小结:这种方法优点是:可以获取到线程的返回值。缺点是,效率低,在获取线程返回值的时候,当前线程会阻塞。

    2.3 获取和修改线程的名字

    线程有默认的名字 ---->Thread-0
    可以通过setName方法进行修改线程名字。

    获取到当前线程对象
    Thread t=Thread.currentThread();
    返回值t就是当前线程。如果出现在主线程中当前线程就是主线程。

    三、 线程生命周期

    在这里插入图片描述

    3.1 新建状态

    新建状态是新new出来的线程对象。
    

    3.2 就绪状态

    就绪状态又叫可运行状态,表示当前的线程具有抢夺cpu时间片的权力(CPU时间片就是执行权)。
    当一个线程抢夺到cpu时间片之后,就会开始执行run方法,run方法执行代表着线程进入运行状态。
    

    3.3 运行状态

    run方法开始执行标志着这个线程进入运行状态,当之前占有的cpu时间片用完之后,会重新回到就绪状态继续抢夺cpu时间片,当再次抢到cpu时间片之后,会重新进入run方法接着上一次的代码继续往下执行。

    3.4 死亡状态

    当run方法执行完,线程死亡。
    

    3.5 阻塞状态

    当一个线程进入到阻塞事件,例如接受用户的键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的
    cpu时间片。重新进入到就绪状态继续抢cpu时间片。
    

    3.5.1 线程sleep(休眠方法)

    static void sleep(long millis);
    

    sleep是一个静态的方法,参数是毫秒,作用是让当前线程进入休眠,进入’阻塞状态‘,放弃占有的cpu时间片,让给其他线程使用。

    public static void main(String[] args) {
    		System.out.println("主线程执行");
    		try {
    			Thread.sleep(5000);
    		} catch (Exception e) {
    			e.getStackTrace();
    		}
    		
    		System.out.println("线程休眠5秒后输出了");
    	}
    

    在这里插入图片描述

    5秒后:

    在这里插入图片描述

    面试题:判断下面代码运行效果
    
    public static void main(String[] args) {
    		
    		Thread t=new MyThread2();
    		t.setName("t");
    		t.start();
    		
    		try {
    			
    			//t线程会休眠吗??
    			t.sleep(5000);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		
    		System.out.println("主线程");
    	}
    
    }
    class MyThread2 extends Thread{
    	
    	public void run() {
    		for (int i = 0; i < 10; i++) {
    			System.out.println("当前线程"+Thread.currentThread().getName()+"\t"+i);
    		}
    	}
    }
    

    分析:t.sleep()出现在主线程中,虽然它是由线程对象点出来的,但是它是静态方法和引用没有任何关系,出现在哪个地方,就会让哪个线程进入休眠,所有说这个程序会先执行分线程,5秒后输出”主线程“。

    3.5.2 线程interrupt(“叫醒线程方法”)

    interrupt方法叫”干扰“,会中断线程的睡眠,靠的是java的异常处理机制。

    public static void main(String[] args) {
    		Thread t=new MyThread2();
    		//分支线程开启
    		t.start();
    		try {
    			Thread.sleep(2000);//主线程休息2秒
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		
    		
    		t.interrupt();//让分支线程型“醒过来”
    	}
    
    }
    class MyThread2 extends Thread{
    	
    	public void run() {
    		try {
    			Thread.sleep(20000000);//线程休眠很长时间
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		System.out.println("分支线程");
    	}
    }
    

    在这里插入图片描述
    理解:当开始运行run方法的时候,线程会进入长时间休眠状态,通过执行interrupt方法会让分支线程出异常,直接执行catch语句。

    3.5.3 线程stop(强行中断线程)

    public static void main(String[] args) {
    		Thread t=new MyThread2();
    		t.start();
    		
    		//主线程休息5秒钟直接后干死分支线程
    		try {
    			Thread.sleep(5000);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		
    		
    		t.stop();//强行中断线程
    	}
    
    }
    class MyThread2 extends Thread{
    	
    	public void run() {
    		
    		//这个程序会运行10秒钟
    		for (int j = 0; j <10; j++) {
    			System.out.println(Thread.currentThread().getName()+j);
    			try {
    				Thread.sleep(1000);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    		
    		
    	}
    }
    

    在这里插入图片描述
    stop的缺点:容易丢失数据,线程没有保存的数据会丢失,所以过时了。

    四、线程的调度

    4.1 常见的调度模型

    抢占式:哪个线程的优先级比较高,抢到的cpu时间片的概率就多一些。java采用的就是抢占式调度模型。
    均分式:平均分配cpu时间片,每个线程占有的cpu时间片时间长度一样,平均分配

    4.2 java中的线程调度方法

    实例方法:

    void setpriority(int newPriority)//设置线程优先级
    int getPriority()//获取线程优先级
    

    静态方法:

    static void yield()//暂停当前正在执行的线程,并执行其他线程
    

    yield方法不是阻塞,会让当前线程”运行状态“回到”就绪状态“。
    最低优先级是1,默认是5,最高是10.

    五、多线程安全(重点)

    5.1 什么时候会出现多线程并发的安全问题

    需要满足三个条件

    1.多线程并发环境下。
    2.有共享数据。
    3.共享的数据有修改行为。
    

    5.2 解决方法(线程同步机制)

    线程排队执行(不能并发),用排队执行解决线程安全问题,会牺牲一部分效率。
    这种机制被成为:线程同步机制。

    异步编程模型:线程t1和t2,各自执行,就做异步编程模型。就是多线程并发。

    同步线程模型:线程t1执行的时候,必须等待线程2执行结束,两个线程之间出现了等待关系,这就是同步线程模型。

    怎么实现同步机制呢,synchronized代码块

    每一个堆中的对象都有一把锁,这把锁只是一个标记。
    假设t1和t2线程并发,线程1先执行了,遇到了synchronized,这个时候自动找后面线程共享对象的对象锁,找到之后并占有一把锁,然后执行
    同步代码块中的程序,在执行过程中会、一直占有这把锁,直到同步代码块结束,这把锁才会释放。
    总而言之,一定时间只有一个线程会执行程序,下个线程在同步代码块外等待。
    

    注意事项:需要同步线程一定要有个共享对象。

    5.3 变量的线程安全问题

    在java中有三大变量:
    实例变量:在堆中
    静态变量:在方法区
    局部变量:在栈中

    在这三者中只有局部变量永远不会有线程安全问题。原因在于局部变量在栈中,永远都不会共享。
    

    解决方法:

    1.使用局部变量代替实例和静态变量
    2.如果用实例变量,多new对象。
    3.迫不得已用synchronized,用线程同步机制。

    5.4 死锁

    就是多个线程在运行状态中因争夺资源造成的一种僵局。
    在这里插入图片描述

    六、 守护线程

    6.1 守护线程的实现

    在Java中,有两大类线程:

    第一种是用户线程,第二种是守护线程(也称作后台线程)。
    守护线程具有代表是java中的垃圾回收线程。
    主线程是一个用户线程。
    

    守护线程的特点:

    1.一般守护线程都是死循环。
    2.用户线程结束,守护线程自动自动结束。
    
    public static void main(String[] args) throws InterruptedException {
    		
    		Thread t1=new Thread(new Runnable() {
    			
    			
    			//死循环线程
    			public void run() {
    				int i=0;
    				while(true) {
    					try {
    						Thread.sleep(1000);
    						System.out.println(Thread.currentThread().getName()+(i++));
    					} catch (InterruptedException e) {
    						
    						e.printStackTrace();
    					}
    					
    				}
    			}
    		});
    		
    		t1.setName("分支线程");
    		
    		t1.setDaemon(true);
    		
    		t1.start();
    		
    		//主线程执行代码
    		
    		for (int i = 0; i < 10; i++) {
    
    			System.out.println(Thread.currentThread().getName()+i);
    			Thread.sleep(1000);
    		}
    		
    
    	}
    
    }
    

    执行结果:在主线程输出完之后,守护线程死循环也跟着结束了。

    6.2 定时器

    定时器有什么用呢?

    目的是控制程序根据你设定的时间间隔去执行程序。
    
    public static void main(String[] args) throws ParseException {
    		Timer timer=new Timer();
    		
    		//日期类
    		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    		
    		//获取当前时间
    		Date firstTime= sdf.parse("2021-4-22 15:10:00");
    		
    		
    		timer.schedule(new LogTimerTask(),firstTime,1000);
    
    	}
    
    }
    
    //定时任务类
    
    class LogTimerTask extends TimerTask{
    
    	
    	public void run() {
    		
    		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    		
    		String time=sdf.format(new Date());
    		
    		System.out.println(time+"完成任务!");
    	}
    	
    }
    

    在这里插入图片描述

    小结:计时器可采用线程守护的方式,可以用上方法,还能用匿名内部类形式。

    七、notify和wait方法

    7.1 wait方法

    Object o=new Object();
    o.wait();
    

    wait方法的作用是,让o对象中正在运行的线程进入等待状态并释放掉o对象的锁,无限期等待,直到线程被再次唤醒。

    7.2 notify方法

    Object o=new Object();
    o.notify();
    

    notify的方法的作用是:让o对象进入等待状态的线程“苏醒”过来继续执行。
    还有一个,notifyAll方法是让所有o对象的线程”苏醒“过来。

    notify和wait方法都是在synchronized基础之上进行的。

    7.3 两种方法的使用

    交替打印奇数偶数

    public static void main(String[] args) {
    		Num num=new Num();
    		Thread t1=new Thread(new Os(num));
    		Thread t2=new Thread(new Js(num));
    		t1.start();
    		t2.start();
    
    	}
    
    }
    
    class Num{
    	int num=1;
    
    	public int getNum() {
    		return num;
    	}
    
    	public int printNum() {
    		return num++;
    	}
    	
    }
    
    class Os implements Runnable{
    	Num num;
    	public Os(Num num) {
    		this.num=num;
    	}
    	
    	public void run() {
    		while(num.getNum()<100) {
    			synchronized (num) {
    				if(num.getNum()%2==0) {
    					
    					try {
    						num.wait();
    					} catch (InterruptedException e) {
    					
    						e.printStackTrace();
    					}
    				System.out.println(Thread.currentThread().getName()+"\t"+num.printNum());
    				num.notify();	
    			}
    		}
    	}
    }
    
    class Js implements Runnable{
    	Num num;
    	
    	public Js(Num num) {
    		this.num=num;
    	}
    	
    	public void run() {
    		while(num.getNum()<100) {
    			synchronized (num) {
    				if(num.getNum()%2!=0) {
    					try {
    						num.wait();
    					} catch (InterruptedException e) {	
    						e.printStackTrace();
    					}
    				}
    				System.out.println(Thread.currentThread().getName()+"\t"+num.printNum());
    				num.notify();
    				
    			}
    			
    		}
    		
    	}
    }
    

    小结:01线程输出奇数,02线程输出.当数字1进入到Thread-0线程,synchrozed会锁住Num对象,进行判断为false,输出1,唤醒等待的thread-1线程执行。

    展开全文
  • Android中直接开线程与使用服务(Service)开线程两种办法都可以执行长时间任务,它们的区别是什么呢?什么时候使用直接开线程,又什么时候启动服务后,再服务中开线程呢? 一个service是一个在用户不与你的应用交互...

    Android中直接开线程与使用服务(Service)开线程两种办法都可以执行长时间任务,它们的区别是什么呢?什么时候使用直接开线程,又什么时候启动服务后,再服务中开线程呢?

    一个service是一个在用户不与你的应用交互时依然可以运行于后台的简单组件.所以,只有你需要这样做时才创建一个service.如果你需要执行的工作不在主线程中,但是只有用户在与你的应用交互时才进行,那么你可能应该创建一个新的线程而不是一个service.例如,如果你想播放一些音乐,但是只在你的activity运行时才播放,你应该在onCreate()中创建一个线程,在onStart()运行这个线程,然后在onStop()中停止它.也可以考虑使用AsyncTask或HandlerThread,来代替传统的线程类.

    举例:
    项目1)点按钮开服务,服务中开线程播放map3
    项目2)点按钮开线程播放mp3

    项目3)能通过开辟项目1中的服务播放mp3(服务可以被其它项目直接启动)
    项目3)不能通过开辟项目2中的线程播放mp3(线程不能被其它项目直接启动)

    展开全文
  • Java多线程:创建多少线程才合适?

    千次阅读 2019-09-05 13:25:46
    1、线程执行 线程的执行是由CPU进行调度的,一个CPU在同一时刻只会执行一个线程 操作系统利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,再加载下一个任务的状态后,继续...
  • android 线程里面再新的的线程

    千次阅读 2016-03-04 11:36:18
    当 一个线程里面另外一个线程的时候,需要注意的地方,例如:  @Override  public void run() {  // TODO Auto-generated method stub  mTimeCount++;  mProgres
  • Java应用中线程是不是的越多越多少合适,如何减少上下文切换开销?,如何写个shell脚本获取上下文切换的开销?
  • 现在需求是 固定打开5个浏览器(5个线程),哪个浏览器没有被占用,就接受后边的url,如果5个浏览器都被占用,则后边的url一直等,等到某个浏览器结束前一条url访问位置。 请大神指点一下,谢谢。
  • 万字图解Java多线程

    万次阅读 多人点赞 2020-09-06 14:45:07
    文章篇幅较长,大家也可以有选择的看具体章节,建议多线程的代码全部手敲,永远不要相信你看到的结论,自己编码后运行出来的,是自己的。 什么是java多线程? 进程与线程 进程 当一个程序被运行,就开启了一个进程...
  • 进程和线程一样,可以多个。进程是静态的,一个进程里可以有多个线程。   起动线程有两个方法:一个是接口RUNABLE,一个是继承THERAD   public class testthread{ public static void main(){ Runner1 r = ...
  • 比较System.Threading.Timer定义线程定时器,和 System.Thread定义线程+while(true)循环,有什么区别,哪个性能更优?
  • c++中多线程编程是不是线程数越多越

    千次阅读 多人点赞 2013-10-05 12:41:26
    线程编程可以提高程序的并发执行能力,那是不是线程越多越呢?
  • Java线程详解(深度文)

    万次阅读 多人点赞 2018-09-16 21:03:21
    Java线程:概念与原理 一、进程与线程    进程是指一个内存中运行的应用程序,每个进程都有...进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。 ...
  • 进程-线程-多线程 1、进程(process) 狭义定义:进程就是一段程序的执行过程 简单的来讲进程的概念主要有两点: 第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)...
  • 什么是多线程?如何实现多线程

    万次阅读 多人点赞 2019-04-09 09:53:36
    【转】什么是线程安全?怎么实现线程安全?什么是进程?什么是线程?什么是线程安全?添加一个状态呢?如何确保线程安全?synchronizedlock 转自:https://blog.csdn.net/csdnnews/article/details/82321777 什么是...
  • # 多个线程 是否跟CPU有关? 我本机60个线程看起来没有起多大作用,这是怎么回事 求大神解答
  • 一个并发程序多少线程合适?

    万次阅读 2015-10-05 19:16:20
    最近在和朋友讨论的过程中,朋友说线程开得多性能,理由是系统中已经有成百上千的线程,并发程序开的线程越多分配到CPU的概率越高。我不认可该观点,下面来验证。 最好起CPU核心数量*2 个线程 或者 CPU核心数量*...
  • 线程创建CreateThread,线程挂起SuspendThread,线程恢复ResumeThread,线程销毁TerminateThread,都是kernel32的命令,我们自己封装这些线程的命令,就是便于我们理解他们的工作机制。 API源码: .版本 2 ....
  • 秒杀多线程第六篇 经典线程同步 事件Event

    万次阅读 多人点赞 2012-04-11 09:06:57
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇 一个经典的多线程同步问题》《秒杀多线程第五篇 经典线程同步关键段CS》 上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性...
  • C# 线程中打开新窗体

    千次阅读 2017-06-29 17:53:52
    方法一: MethodInvoker MethInvk = new MethodInvoker(ShowForm5); BeginInvoke(MethInvk); private void ShowForm5() { Form5 form5 = new Form5(); form5.Show();...方法二:带参数的委托
  • Python 多进程下线程满负荷工作

    千次阅读 2018-08-28 16:58:51
    本文只是写一个例子,实现了多个进程,每个进程下面线程,每个进程执行单独一个任务,并行执行,进程下的多线程执行任务,并发执行,有效利用CPU资源. import multiprocessing import threading import time def m...
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • 多核 vs 多线程:合适的是最好的

    千次阅读 2013-07-13 08:43:33
    多核与多线程都是提升处理器处理性能的重要手段,如今多核处理器随处可见,多线程处理器似乎鲜有提及,其实多线程并不是一个新鲜的概念,在很多地方也有广泛的应用。到底多核处理器与多线程处理两者之间有何差异?各...
  • Runnable并不一定新开线程

    千次阅读 2014-12-13 18:52:20
    extends Thread一定新一个线程,但Runnable不一定新开线程(所谓新开线程,指非UI线程): ① 如果是implements Runnable,像extends Thread一样,是新开了线程。 ②如果是匿名Runnable对象new Runnable() {...
  • 线程基础:线程(1)——操作系统和线程原理

    万次阅读 多人点赞 2015-12-21 17:41:18
    本人不才,应读者要求新专栏,与各位读者分享自己对JAVA线程技术的理解和使用经验。这个专栏将分成两个部分:线程基础知识和锁知识。专栏的难度应该是我所专栏中难度最低的一个,着重于线程基础知识的讲解,更...
  • fork/join 和开线程数量

    千次阅读 2019-01-18 13:44:31
    //四个线程同时去执行 ForkJoinPool myPool = new ForkJoinPool(4); myPool.submit(() -&gt; {}) 打开的线程数 public class ThreadPoolHelper { public static ThreadPool instance; private ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 711,795
精华内容 284,718
关键字:

线程怎么开才好