Java-多线程

《进阶-Java多线程锁》

什么是线程

  • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
  • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作

CPU核心数和线程数的关系

以前:
六个核心数, 1:1, 一个核心 就是一个线程
单核 一个核心 一个线程

现在:
超线程技术, 1:2 , 六个核心数 = 12个线程
四核 六核 ...

安卓处理器跟PC还不太一样
ARM32,ARM64,x86,x64

CPU时间片轮转机制

随机切换机制
进程:操作系统所管理的最少单元
线程:CPU调度的最少单元
CPU时间片轮转机制采用了RR调度算法

进程和线程

进程 > 线程
一个进程至少一个线程 或 多个线程
如果一个进程,还有一个线程没有杀掉,还存活,那么进程还存活 (线程依附进程)

多线程并行和并发的区别

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
  • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

并行:四个跑道
并发:10秒钟,服务器的吞吐量。比如十秒钟,多少车流量,多少车跑过去

Java程序运行原理

  • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。

JVM的启动是多线程的吗

// Java服务器

/*虚拟机线程管理的接口*/
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
/*取得线程信息*/
ThreadInfo[] threadInfos = 
 threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos) {
    System.out.println("["+threadInfo.getThreadId()+"]"+" "
            +threadInfo.getThreadName());
}

上述代码打印结果:

[6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的
[5] Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
[4] Signal Dispatcher  // 分发处理发送给JVM信号的线程
[3] Finalizer  // 调用对象finalize方法的线程
[2] Reference Handler//清除Reference的线程
[1] main //main线程,用户程序入口
  • Finalizer Object finalize() 需要资源回收 就复写该方法 把代码写这里面去
  • GC 并不是马上就会有GC,资源需要回收,JVM才会检测到,启用GC

多线程程序实现的方式1

public class Demo2_Thread {

        /**
         * @param args
         */
        public static void main(String[] args) {
                MyThread mt = new MyThread();       //4,创建Thread类的子类对象
                mt.start();                         //5,开启线程,调用run方法
                
                for(int i = 0; i < 1000; i++) {
                    System.out.println("bb");
                }
        }

}

class MyThread extends Thread {             //1,继承Thread
        public void run() {                     //2,重写run方法
                for(int i = 0; i < 1000; i++) {     //3,将要执行的代码写在run方法中
                    System.out.println("aaaaaaaaaaaa");
                }
        }
}

多线程程序实现的方式2

public class Demo3_Thread {

        /**
         * @param args
         */
        public static void main(String[] args) {
                MyRunnable mr = new MyRunnable();   //4,创建Runnable的子类对象
                Thread t = new Thread(mr);          //5,将其当作参数传递给Thread的构造函数
                t.start();                          //6,开启线程
                
                for(int i = 0; i < 1000; i++) {
                    System.out.println("bb");
                }
        }

}



class MyRunnable implements Runnable {      //1,定义一个类实现Runnable

        @Override
        public void run() {                     //2,重写run方法
                for(int i = 0; i < 1000; i++) {     //3,将要执行的代码写在run方法中
                    System.out.println("aaaaaaaaaaaa");
                }
        }
    
}

有返回值的多线程

public class MyClass {

    private static class WorkerThread implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println("do work WorkerThread");
            Thread.sleep(10000);
            return "run success";
        }
    }

    public static void main(String[] args) throws Exception {
        // --- 有返回值  任务不能运行,需要寄托 Thread
        WorkerThread workerThread = new WorkerThread();
        FutureTask<String> futureTask = new FutureTask<>(workerThread);
        new Thread(futureTask).start();
        System.out.println("get==="+futureTask.get()); // 阻塞的
    }
}

thread的好处就是runnable的弊端

  • 继承Thread
    • 好处是:可以直接使用Thread类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能用这种方法(java是单继承,只能一个父类)
  • 实现Runnable接口
    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

匿名内部类实现线程的两种方式

案例:

public class Demo4_Thread {

        /**
         * @param args
         */
        public static void main(String[] args) {
            //继承Thread类
            new Thread() {                                      //1,继承Thread类
                    public void run() {                             //2,重写run方法
                        for(int i = 0; i < 1000; i++) {             //3,将要执行的代码写在run方法中
                            System.out.println("aaaaaaaaaaaaaa");
                        }
                    }
            }.start();                                          //4,开启线程
            
            //实现Runnable接口
            new Thread(new Runnable() {                         //1,将Runnable的子类对象传递给Thread的构造方法
                    public void run() {                             //2,重写run方法
                        for(int i = 0; i < 1000; i++) {             //3,将要执行的代码写在run方法中
                            System.out.println("bb");
                        }
                    }
            }).start();                                         //4,开启线程
        }

}

多线程获取名字和设置名字

  • 1.获取名字
    • 通过Thread.getName()方法获取线程对象的名字
  • 2.设置名字
    • 通过Thread.setName("李四")设置名字,不推荐在 run函数中设置,线程一般执行东西较多。在start前设置

获取当前线程的对象

  • Thread.currentThread(), 主线程也可以获取

多线程休眠

  • Thread.sleep(毫秒,纳秒)

守护线程

  • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

  • 守护线程在结束时也要有时间缓冲才结束,所以并不一定非守护线程执行完后守护线程就立马结束

案例:

public class Demo4_Daemon {

        /**
         * @param args
         * 守护线程
         */
        public static void main(String[] args) {
                Thread t1 = new Thread() {
                        public void run() {
                                for(int i = 0; i < 2; i++) {
                                        System.out.println(getName() + "...aa.."+i);
                                }
                        }
                };
                
                Thread t2 = new Thread() {
                        public void run() {
                                for(int i = 0; i < 50; i++) {
                                        System.out.println(getName() + "...bb.."+i);
                                }
                        }
                };
                
                t2.setDaemon(true);                         //设置为守护线程
                
                t1.start();
                t2.start();
        }

}

打印结果:

Thread-0...aa..0
Thread-0...aa..1
Thread-1...bb..0
Thread-1...bb..1
Thread-1...bb..2
Thread-1...bb..3
Thread-1...bb..4
Thread-1...bb..5
Thread-1...bb..6
Thread-1...bb..7
Thread-1...bb..8
Thread-1...bb..9
  • 主线程结束,不管守护线程有没有结束,守护线程都必须结束

public class DaemonThread {

    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "---" + i);
                }
            }
        };
        t.setDaemon(true); // 设置了守护线程
        t.start(); // 谁调用的 main, main管了我 我就守护main

        // 主线程,是为了 等 Thread t 10秒钟
        Thread.sleep(10000);

        // 非守护线程  主线程一直在等 Thread t 到底执行完了没有

        // 走到这里,代表主线程结束,主线程结束,不管t线程有没有结束,t线程都必须结束,因为t线程是守护线程,守护了main
    }

}

加入线程

  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
  • join(int), 可以等待指定的毫秒之后继续
public class Demo5_Join {

        /**
         * @param args
         * join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
         */
        public static void main(String[] args) {
                final Thread t1 = new Thread() {
                        public void run() {
                                for(int i = 0; i < 10; i++) {
                                        System.out.println(getName() + "...aaaaaaaaaaaaa");
                                }
                        }
                };
                
                Thread t2 = new Thread() {
                        public void run() {
                                for(int i = 0; i < 10; i++) {
                                        if(i == 2) {
                                                try {
                                                        //t1.join();
                                                        t1.join(1);  //插队指定的时间,过了指定时间后,两条线程交替执行  毫秒
                                                } catch (InterruptedException e) {
                                                        e.printStackTrace();
                                                }
                                        }
                                        System.out.println(getName() + "...bb");
                                }
                        }
                };
                
                t1.start();
                t2.start();
        }

}

礼让线程

  • yield让出cpu
  • 使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行。
public class Demo6_Yield {
    
        /**
         * yield让出cpu礼让线程
         */
        public static void main(String[] args) {
                new MyThread().start();
                new MyThread().start();
        }

}

class MyThread extends Thread {
        public void run() {
                for(int i = 1; i <= 1000; i++) {
                        if(i % 10 == 0) {
                                Thread.yield();                     //让出CPU
                        }
                        System.out.println(getName() + "..." + i);
                }
        }
}

设置线程的优先级

  • setPriority()设置线程的优先级, 只是大部分优先一点(不靠谱)
public class Demo7_Priority {

        /**
         * @param args
         */
        public static void main(String[] args) {
                Thread t1 = new Thread(){
                        public void run() {
                                for(int i = 0; i < 100; i++) {
                                        System.out.println(getName() + "...aaaaaaaaa" );
                                }
                        }
                };
                
                Thread t2 = new Thread(){
                        public void run() {
                                for(int i = 0; i < 100; i++) {
                                        System.out.println(getName() + "...bb" );
                                }
                        }
                };
                
                //t1.setPriority(10);                   设置最大优先级 默认是5 最大10 最小1
                //t2.setPriority(1);
                
                t1.setPriority(Thread.MIN_PRIORITY);        //设置最小的线程优先级
                t2.setPriority(Thread.MAX_PRIORITY);        //设置最大的线程优先级
                
                t1.start();
                t2.start();
        }

}

多线程同步方法

  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
  • 锁可以是任意对象,但不能是匿名对象。因为匿名对象不是同一个对象,也就不是同一把锁

案例(非静态的同步方法):

public class Demo2_Synchronized {

        /**
         * @param args
         * 同步代码块
         */
        public static void main(String[] args) {
                final Printer2 p = new Printer2();
                
                new Thread() {
                        public void run() {
                                while(true) {
                                        p.print1();
                                }
                        }
                }.start();
                
                new Thread() {
                        public void run() {
                                while(true) {
                                        p.print2();
                                }
                        }
                }.start();
        }

}

class Printer2 {
        Demo d = new Demo();
        //非静态的同步方法的锁对象是神马?
        //答:非静态的同步方法的锁对象是this     谁来调用我this就代表谁的对象
        
        //同步方法只需要在方法上加synchronized关键字即可
        public synchronized void print1() { 
                System.out.print("1");
                System.out.print("2");
                System.out.print("3");
                System.out.print("4");
                System.out.print("5");
                System.out.print("\r\n");
        }
        
        public void print2(this) {
                //synchronized(new Demo()) {                            
                //锁对象不能用匿名对象,因为匿名对象不是同一个对象
                synchronized(Printer2.class) {      
                    System.out.print("a");
                    System.out.print("b");
                    System.out.print("c");
                    System.out.print("d");
                    System.out.print("\r\n");
                }
        }
}

案例(静态的同步方法):

public class Demo2_Synchronized {

        /**
         * @param args
         * 同步代码块
         */
        public static void main(String[] args) {
                final Printer2 p = new Printer2();
                
                new Thread() {
                        public void run() {
                                while(true) {
                                        p.print1();
                                }
                        }
                }.start();
                
                new Thread() {
                        public void run() {
                                while(true) {
                                        p.print2();
                                }
                        }
                }.start();
        }

}

class Printer2 {
        Demo d = new Demo();
        //静态的同步方法的锁对象是什么?
        //是该类的字节码对象 .class 
        public static synchronized void print1() {                          
        //同步方法只需要在方法上加synchronized关键字即可
                System.out.print("1");
                System.out.print("2");
                System.out.print("3");
                System.out.print("4");
                System.out.print("5");
                System.out.print("\r\n");
        }
        
        public static void print2() {
            //synchronized(new Demo()) {                            
            //锁对象不能用匿名对象,因为匿名对象不是同一个对象
            synchronized(Printer2.class) {      
                    System.out.print("a");
                    System.out.print("b");
                    System.out.print("c");
                    System.out.print("d");
                    System.out.print("\r\n");
            }
        }
}

死锁

  • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
    • 尽量不要嵌套使用
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
    new Thread() {
        public void run() {
            while(true) {
                synchronized(s1) {
                    System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                    synchronized(s2) {
                        System.out.println(getName() + "...拿到" + s2 + "开吃");
                    }
                }
            }
        }
    }.start();
    
    new Thread() {
        public void run() {
            while(true) {
                synchronized(s2) {
                    System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                    synchronized(s1) {
                        System.out.println(getName() + "...拿到" + s1 + "开吃");
                    }
                }
            }
        }
    }.start();
}

停止线程

  • 自然终止

要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

  • 手动中止

暂停、恢复和停止操作对应在线程ThreadAPI就是suspend()resume()stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()resume()stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为true来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false

如果一个线程处于了阻塞状态(如线程调用了thread.sleepthread.jointhread.wait、),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false

不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,一、一般的阻塞方法,如sleep等本身就支持中断的检查,二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断


import java.text.SimpleDateFormat;
import java.util.Date;

/**
 *类 说明:抛出InterruptedException异常的时候,要注意中断标志位
 */
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) { // sleep 会把中断信号清除
                                    System.out.println(threadName+" catch interrput flag is "
                                            +isInterrupted()+ " at "
                                            +(formater.format(new Date())));
                                    // TODO interrupt 需要在此内部 调用才能中断了,才能把被清楚的标记修改成true
                                    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(); // 被 InterruptedException e 人家清除
        }

}

深入理解run()和start()

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。
start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用。
而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,可以被单独调用。

总结

run 和start的区别 ?

答:run是函数调用 和线程没有任何关系, .start会走底层 会走系统层 最终调度到 run函数,这才是线程。

如何控制线程的执行顺序 ?

答:join来控制 让t2获取执行权力,能够做到顺序执行

多线程中的并行和并发是什么?

答:四个车道,四辆车并行的走,就是并行, 四个车道中,五秒钟多少的车流量,多少的吞吐量一样

在Java中能不能指定CPU去执行某个线程?

答:不能,Java是做不到的,唯一能够去干预的就是C语言调用内核的API去指定才行,这个你回答的话,面试官会觉得你研究点东西

在项目开发过程中,你会考虑Java线程优先级吗?

答:不会考虑优先级,为什么呢? 因为线程的优先级很依赖与系统的平台,所以这个优先级无法对号入座,无法做到你想象中的优先级,属于不稳定,有风险
因为某些开源框架,也不可能依靠线程优先级来,设置自己想要的优先级顺序,这个是不可靠的
例如:Java线程优先级又十级,而此时操作系统优先级只有2~3级,那么就对应不上

sleep和wait又什么区别?

答:sleep是休眠,等休眠时间一过,才有执行权的资格,注意:只是又有资格了,并不代表马上就会被执行,什么时候又执行起来,取决于操作系统调度
wait是等待,需要人家来唤醒,唤醒后,才有执行权的资格,注意:只是又有资格了,并不代表马上就会被执行,什么时候又执行起来,取决于操作系统调度
含义的不同:sleep无条件可以休眠, wait是某些原因与条件需要等待一下(资源不满足)

在Java中能不能强制中断线程的执行?

答:虽然提供了 stop 等函数,但是此函数不推荐使用,为什么因为这种暴力的方式,很危险,例如:下载图片5kb,只下载了4kb 等
我们可以使用interrupt来处理线程的停止,但是注意interrupt只是协作式的方式,并不能绝对保证中断,并不是抢占式的

如何让出当前线程的执行权?

答:yield方法,一般只在JDK某些实现才能看到,是让出执行权

sleep,wait,到底那个函数才会 清除中断标记?

答:sleep在抛出异常的时候,捕获异常之前,就已经清除

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

推荐阅读更多精彩内容

  • Java中的多线程是每一个Java作为底层开发语言的开发人员都应该懂得。深入了解Java线程会发现其实Java线程...
    阿博聊编程阅读 658评论 1 8
  • Java面试题之多线程(一) 标签(空格分隔): Java面试题 多线程 什么是进程? 是一个具有一定独立功能的程...
    andy_victory阅读 242评论 0 0
  • java线程状态 初始(NEW)新创建了一个线程对象,但还没有调用start()方法 运行(RUNNABLE)Ja...
    追风还是少年阅读 70评论 0 1
  • 前言: Java的多线程编程随着应用功能越来越复杂,用户对软件体验要求越来越高,那么对于多线程的编程越发重要了。接...
    CaoMeng阅读 285评论 0 0
  • 1.进程与线程 (1).什么是进程? 进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自...
    一只洁_阅读 513评论 1 1