1、继承Thread类创建线程类

a、定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
b、创建Thread子类的实例,即创建了线程对象。
c、调用线程对象的start()方法来启动该线程

public static void main(String[] args) {
        System.out.println("==========创建线程");
        new Thread(){
            @Override
            public void run(){
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "===新线程执行结束");
            }
        }.start();
        System.out.println("==========线程结束");
}

2、通过Runnable接口创建线程类

a、定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
b、创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
c、调用线程对象的start()方法来启动该线程。

public static void main(String[] args) {
        System.out.println("==========创建线程");
        Thread thread = new Thread(new MyThreadRunnable());
        thread.start();
        System.out.println("==========线程结束");
        
}

private static class MyThreadRunnable implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("=======新线程执行==============");
        }
}

3、通过Callable和Future创建线程

a、创建Callable接口的实现类,并实现call创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
b、创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
c、使用FutureTask对象作为Thread对象的target创建并启动新线程。
d、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

public static void main(String[] args) throws Exception {
        System.out.println("==========创建线程");
        MyThreadCallable callable = new MyThreadCallable();
        FutureTask task = new FutureTask(callable);
        Thread thread = new Thread(task);
        thread.start();
        // System.out.println(task.get());
        System.out.println("==========线程结束");
}


private static class MyThreadCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(1000);
            System.out.println("=======新线程执行==============");
            return "执行完毕";
        }
    }

task.get()可以对主线程进行阻塞,直到异步线程返回结果。

知识点一、runnable 和 callable 有什么区别

  • Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
  • Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

知识点二、线程通常都有五种状态,创建、就绪、运行、阻塞和死亡

  • 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  • 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  • 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  • 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  • 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪 

知识点三、sleep()与wait()的区别

sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

private synchronized static void threadSleepTest(int i){
        try {
            if (i == 0) {
                num = 100;
                System.out.println("0 set over!");
                //wait();
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("1 set over!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("========执行打印" + num);
    }

    public static void main(String[] args) throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSleepTest(0);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                threadSleepTest(1);
            }
        }).start();
}

执行结果:

java new新线程 java 创建新线程_java new新线程

wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

知识点四:创建线程池四种方法

①. newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
②. newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
④. newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

知识点五:在 java 程序中怎么保证多线程的运行安全

原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。

知识点六:怎么防止死锁

死锁的四个必要条件:

互斥条件

进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

请求和保持条件

进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

不可剥夺条件

是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

环路等待条件

是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和 解除死锁。
所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。
此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。

知识点七:ThreadLocal 是什么?有哪些使用场景?

ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题

java new新线程 java 创建新线程_开发语言_02

 注:

1、同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

2、InheritableThreadLocal类可以在子线程中获取到(InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法,将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中)

3、THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。