【总结】并发编程(一:基础)

1.线程状态

笼统的可以分为 新建 就绪 运行 阻塞 消亡五个状态
(1)新建:当需要新起一个线程来执行某个子任务时,就创建了一个线程
(2)就绪:线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态(java中调用start()方法进入就绪状态)
(3)运行:当得到CPU执行时间之后,线程便真正进入运行状态
(4)阻塞:运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)
(5)消亡:突然中断或者子任务执行完毕,线程就会被消亡

2.线程切换

对于单核CPU来说,CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。因此需要记录线程A的运行状态,因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素

3.Thread类中的方法

1.start():start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源
2.run():run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务
3.sleep():sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:
如果调用Thread.currentThread().sleep(10000); t1阻塞10秒后,执行完run方法,t2才会进入同步块执行
如果调用object.wait();当前线程回释放这个对象锁,所以会连续打印两句:t1获取对象锁xx t2获取所对象xx

public class Test implements Runnable{
    Object object = new Object();
    public static void main(String[] args) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
    }


    @Override
    public void run() {
        synchronized (object){
            System.out.println(Thread.currentThread().getName()+"获取对象锁,接下来调用sleep阻塞");
            try {
//                Thread.currentThread().sleep(10000);
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        }
    }
}

4.sleep()和wait()的区别?
(1)sleep()是Thread的方法,wait()是Object的方法
(2)sleep()可以用在任意位置,调用wait()方法之前,线程必须要获得该对象的对象级锁;换句话说就是该方法只能在同步方法或者同步块中调用
(3)sleep()阻塞线程的同时不释放锁对象,wait()阻塞线程,释放锁对象

5.yield:让当前运行线程回到就绪状态(释放cpu)
6.join:t1调用t2的join方法,会先执行t2,然后执行t1.有三个重载版本。

join()
join(long millis)     //参数为毫秒
join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间

7.setDaemon():设置是否成为守护线程
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

4.创建线程

1.方式一:集成Thread类

package com.thread;
//通过继承Thread类实现自定义线程类
public class MyThread extends Thread {
    //线程体
    @Override
    public void run() {
        System.out.println("Hello, I am the defined thread created by extends Thread");
    }
    public static void main(String[] args){
        //实例化自定义线程类实例
        Thread thread = new MyThread();
        //调用start()实例方法启动线程
        thread.start();
    }
}

优点:实现简单,只需实例化继承类的实例,即可使用线程
缺点:扩展性不足,Java是单继承的语言,如果一个类已经继承了其他类,就无法通过这种方式实现自定义线程

2.方式二:实现runnable接口

package com.thread;
public class MyRunnable implements Runnable {
    //线程体
    @Override
    public void run() {
        System.out.println("Hello, I am the defined thread created by implements Runnable");
    }
    public static void main(String[] args){
    	//线程的执行目标对象
        MyRunnable myRunnable = new MyRunnable();
        //实际的线程对象
        Thread thread = new Thread(myRunnable);
        //启动线程
        thread.start();
    }
}

优点:扩展性好,可以在此基础上继承其他类,实现其他必需的功能
缺点:构造线程实例的过程相对繁琐一点

3.方式三:实现callable接口

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Hello, I am the defined thread created by implements Callable";
    }
    public static void main(String[] args){
        //线程执行目标
        MyCallable myCallable = new MyCallable();
        //包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        //传入线程执行目标,实例化线程对象
        Thread thread = new Thread(futureTask);
        //启动线程
        thread.start();
        String result = null;
        try {
            //获取线程执行结果
            result = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(result);
    }
}

优点:具备返回值以及可以抛出受检查异常
缺点:相较于实现Runnable接口的方式,较为繁琐

posted @ 2020-11-18 17:30  mu_阿成  阅读(168)  评论(0)    收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css