多线程

* 进程:
* 正在运行的程序,是系统进行资源分配和调用的独立单位。
* 每一个进程都有它自己的内存空间和系统资源。
* 线程:
* 是进程中的单个顺序控制流,是一条执行路径
* 一个进程如果只有一条执行路径,则称为单线程程序。
* 一个进程如果有多条执行路径,则称为多线程程序。

*
* 大家注意两个词汇的区别:并行和并发。
* 前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
* 后者是物理上同时发生,指在某一个时间点同时运行多个程序。
*
* Java程序的运行原理:
* 由java命令启动JVM,JVM启动就相当于启动了一个进程。
* 接着有该进程创建了一个主线程去调用main方法。
*
* 思考题:
* jvm虚拟机的启动是单线程的还是多线程的?
* 多线程的。
* 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
* 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。

* 方式1:继承Thread类。
* 步骤
* A:自定义类MyThread继承Thread类。
* B:MyThread类里面重写run()?
* 为什么是run()方法呢?
* C:创建对象
* D:启动线程

 1 /*
 2  * 该类要重写run()方法,为什么呢?
 3  * 不是类中的所有代码都需要被线程执行的。
 4  * 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
 5  */
 6 public class MyThread extends Thread {
 7 
 8     @Override
 9     public void run() {
10         // 自己写代码
11         // System.out.println("好好学习,天天向上");
12         // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
13         for (int x = 0; x < 200; x++) {
14             System.out.println(x);
15         }
16     }
17 }

// 调用run()方法为什么是单线程的呢?
// 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
// 要想看到多线程的效果,就必须说说另一个方法:start()
// 面试题:run()和start()的区别?
// run():仅仅是封装被线程执行的代码,直接调用是普通方法
// start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

// MyThread my = new MyThread();
// my.start();
// // IllegalThreadStateException:非法的线程状态异常
// // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
// my.start();

1         // 创建两个线程对象
2         MyThread my1 = new MyThread();
3         MyThread my2 = new MyThread();
4 
5         my1.start();
6         my2.start();

* 如何获取线程对象的名称呢?
* public final String getName():获取线程的名称。
* 如何设置线程对象的名称呢?
* public final void setName(String name):设置线程的名称
*
* 针对不是Thread类的子类中如何获取线程对象名称呢?
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()

方法一:

1         // 创建线程对象
2         //无参构造+setXxx()
3          MyThread my1 = new MyThread();
4          MyThread my2 = new MyThread();
5         // //调用方法设置名称
6          my1.setName("林青霞");
7          my2.setName("刘意");
8          my1.start();
9          my2.start();

方法二:

 1         //带参构造方法给线程起名字
 2          MyThread my1 = new MyThread("林青霞");
 3          MyThread my2 = new MyThread("刘意");
 4          my1.start();
 5          my2.start();
 6         
 7         //我要获取main方法所在的线程对象的名称,该怎么办呢?
 8         //遇到这种情况,Thread类提供了一个很好玩的方法:
 9         //public static Thread currentThread():返回当前正在执行的线程对象
10         System.out.println(Thread.currentThread().getName());
 1 public class MyThread extends Thread {
 2 
 3     public MyThread() {
 4     }
 5     
 6     public MyThread(String name){
 7         super(name);
 8     }
 9 
10     @Override
11     public void run() {
12         for (int x = 0; x < 100; x++) {
13             System.out.println(getName() + ":" + x);
14         }
15     }
16 }

* 我们的线程没有设置优先级,肯定有默认优先级。
* 那么,默认优先级是多少呢?
* 如何获取线程对象的优先级?
* public final int getPriority():返回线程对象的优先级
* 如何设置线程对象的优先级呢?
* public final void setPriority(int newPriority):更改线程的优先级。
*
* 注意:
* 线程默认优先级是5。
* 线程优先级的范围是:1-10。
* 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
*
* IllegalArgumentException:非法参数异常。
* 抛出的异常表明向方法传递了一个不合法或不正确的参数。

 1         ThreadPriority tp1 = new ThreadPriority();
 2         ThreadPriority tp2 = new ThreadPriority();
 3         ThreadPriority tp3 = new ThreadPriority();
 4 
 5         tp1.setName("东方不败");
 6         tp2.setName("岳不群");
 7         tp3.setName("林平之");
 8 
 9         // 获取默认优先级
10         // System.out.println(tp1.getPriority());
11         // System.out.println(tp2.getPriority());
12         // System.out.println(tp3.getPriority());
13 
14         // 设置线程优先级
15         // tp1.setPriority(100000);
16         
17         //设置正确的线程优先级
18         tp1.setPriority(10);
19         tp2.setPriority(1);
20 
21         tp1.start();
22         tp2.start();
23         tp3.start();

* 线程休眠
* public static void sleep(long millis)

 * public final void join():等待该线程终止。 

等线程tj1执行完毕才执行其他线程

 1         ThreadJoin tj1 = new ThreadJoin();
 2         ThreadJoin tj2 = new ThreadJoin();
 3         ThreadJoin tj3 = new ThreadJoin();
 4 
 5         tj1.setName("李渊");
 6         tj2.setName("李世民");
 7         tj3.setName("李元霸");
 8 
 9         tj1.start();
10         try {
11             tj1.join();
12         } catch (InterruptedException e) {
13             e.printStackTrace();
14         }
15         
16         tj2.start();
17         tj3.start();

* public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
* 让多个线程的执行更和谐,但是不能靠它保证一人一次。

* public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
* 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。

 1     public static void main(String[] args) {
 2         ThreadDaemon td1 = new ThreadDaemon();
 3         ThreadDaemon td2 = new ThreadDaemon();
 4 
 5         td1.setName("关羽");
 6         td2.setName("张飞");
 7 
 8         // 设置收获线程
 9         td1.setDaemon(true);
10         td2.setDaemon(true);
11 
12         td1.start();
13         td2.start();
14 
15         Thread.currentThread().setName("刘备");
16         for (int x = 0; x < 5; x++) {
17             System.out.println(Thread.currentThread().getName() + ":" + x);
18         }
19     }

* public final void stop():让线程停止,过时了,但是还可以使用。
* public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。

线程的生命周期:

* 方式2:实现Runnable接口
* 步骤:
* A:自定义类MyRunnable实现Runnable接口
* B:重写run()方法
* C:创建MyRunnable类的对象
* D:创建Thread类的对象,并把C步骤的对象作为构造参数传递

 1 public class MyRunnable implements Runnable {
 2 
 3     @Override
 4     public void run() {
 5         for (int x = 0; x < 100; x++) {
 6             // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
 7             System.out.println(Thread.currentThread().getName() + ":" + x);
 8         }
 9     }
10 
11 }
 1     public static void main(String[] args) {
 2         // 创建MyRunnable类的对象
 3         MyRunnable my = new MyRunnable();
 4 
 5         // 创建Thread类的对象,并把C步骤的对象作为构造参数传递
 6         // Thread(Runnable target)
 7         // Thread t1 = new Thread(my);
 8         // Thread t2 = new Thread(my);
 9         // t1.setName("林青霞");
10         // t2.setName("刘意");
11 
12         // Thread(Runnable target, String name)
13         Thread t1 = new Thread(my, "林青霞");
14         Thread t2 = new Thread(my, "刘意");
15 
16         t1.start();
17         t2.start();
18     }

多线程两种方式的图解比较和区别:

* 实现Runnable接口的方式实现
*
* 通过加入延迟后,就产生了连个问题:
* A:相同的票卖了多次
* CPU的一次操作必须是原子性的
* B:出现了负数票
* 随机性和延迟导致的

* 思想:
* 把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
* 问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
*
* 同步代码块:
* synchronized(对象){
* 需要同步的代码;
* }
*
* A:对象是什么呢?
* 我们可以随便创建一个对象试试。
* B:需要同步的代码是哪些呢?
* 把多条语句操作共享数据的代码的部分给包起来
*
* 注意:
* 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
* 多个线程必须是同一把锁。

 1     // 定义100张票
 2     private int tickets = 100;
 3 
 4     // 定义同一把锁
 5     private Object obj = new Object();
 6 
 7     @Override
 8     public void run() {
 9         while (true) {
10             // t1,t2,t3都能走到这里
11             // 假设t1抢到CPU的执行权,t1就要进来
12             // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
13             // 门(开,关)
14             synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
15                 if (tickets > 0) {
16                     try {
17                         Thread.sleep(100); // t1就睡眠了
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                     }
21                     System.out.println(Thread.currentThread().getName()
22                             + "正在出售第" + (tickets--) + "张票 ");
23                     //窗口1正在出售第100张票
24                 }
25             } //t1就出来可,然后就开门。(开)
26         }
27     }

* 同步的特点:
* 前提:
* 多个线程
* 解决问题的时候要注意:
* 多个线程使用的是同一个锁对象
* 同步的好处
* 同步的出现解决了多线程的安全问题。
* 同步的弊端
* 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

* A:同步代码块的锁对象是谁呢?
* 任意对象。
*
* B:同步方法的格式及锁对象问题?
* 把同步关键字加在方法上。
*
* 同步方法是谁呢?
* this
*
* C:静态方法及锁对象问题?
* 静态方法的锁对象是谁呢?
* 类的字节码文件对象。

 1         // 线程安全的类
 2         StringBuffer sb = new StringBuffer();
 3         Vector<String> v = new Vector<String>();
 4         Hashtable<String, String> h = new Hashtable<String, String>();
 5 
 6         // Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
 7         // 那么到底用谁呢?
 8         // public static <T> List<T> synchronizedList(List<T> list)
 9         List<String> list1 = new ArrayList<String>();// 线程不安全
10         List<String> list2 = Collections
11                 .synchronizedList(new ArrayList<String>()); // 线程安全

* 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
* 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
*
* Lock:
* void lock(): 获取锁。
* void unlock():释放锁。
* ReentrantLock是Lock的实现类.

 1 public class SellTicket implements Runnable {
 2 
 3     // 定义票
 4     private int tickets = 100;
 5 
 6     // 定义锁对象
 7     private Lock lock = new ReentrantLock();
 8 
 9     @Override
10     public void run() {
11         while (true) {
12             try {
13                 // 加锁
14                 lock.lock();
15                 if (tickets > 0) {
16                     try {
17                         Thread.sleep(100);
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                     }
21                     System.out.println(Thread.currentThread().getName()
22                             + "正在出售第" + (tickets--) + "张票");
23                 }
24             } finally {
25                 // 释放锁
26                 lock.unlock();
27             }
28         }
29     }
30 
31 }

死锁:

 1 public class DieLock extends Thread {
 2 
 3     private boolean flag;
 4 
 5     public DieLock(boolean flag) {
 6         this.flag = flag;
 7     }
 8 
 9     @Override
10     public void run() {
11         if (flag) {
12             synchronized (MyLock.objA) {
13                 System.out.println("if objA");
14                 synchronized (MyLock.objB) {
15                     System.out.println("if objB");
16                 }
17             }
18         } else {
19             synchronized (MyLock.objB) {
20                 System.out.println("else objB");
21                 synchronized (MyLock.objA) {
22                     System.out.println("else objA");
23                 }
24             }
25         }
26     }
27 }

* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
* 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
* 原因:
* A:同一个数据出现多次
* CPU的一点点时间片的执行权,就足够你执行很多次。
* B:姓名和年龄不匹配
* 线程运行的随机性
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
* 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决。
*
* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。

 1 public class Student {
 2     private String name;
 3     private int age;
 4     private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
 5 
 6     public synchronized void set(String name, int age) {
 7         // 如果有数据,就等待
 8         if (this.flag) {
 9             try {
10                 this.wait();
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14         }
15 
16         // 设置数据
17         this.name = name;
18         this.age = age;
19 
20         // 修改标记
21         this.flag = true;
22         this.notify();
23     }
24 
25     public synchronized void get() {
26         // 如果没有数据,就等待
27         if (!this.flag) {
28             try {
29                 this.wait();
30             } catch (InterruptedException e) {
31                 e.printStackTrace();
32             }
33         }
34 
35         // 获取数据
36         System.out.println(this.name + "---" + this.age);
37 
38         // 修改标记
39         this.flag = false;
40         this.notify();
41     }
42 }
 1 public class GetThread implements Runnable {
 2     private Student s;
 3 
 4     public GetThread(Student s) {
 5         this.s = s;
 6     }
 7 
 8     @Override
 9     public void run() {
10         while (true) {
11             s.get();
12         }
13     }
14 }
 1 public class SetThread implements Runnable {
 2 
 3     private Student s;
 4     private int x = 0;
 5 
 6     public SetThread(Student s) {
 7         this.s = s;
 8     }
 9 
10     @Override
11     public void run() {
12         while (true) {
13             if (x % 2 == 0) {
14                 s.set("林青霞", 27);
15             } else {
16                 s.set("刘意", 30);
17             }
18             x++;
19         }
20     }
21 }
 1 public class StudentDemo {
 2     public static void main(String[] args) {
 3         //创建资源
 4         Student s = new Student();
 5         
 6         //设置和获取的类
 7         SetThread st = new SetThread(s);
 8         GetThread gt = new GetThread(s);
 9 
10         //线程类
11         Thread t1 = new Thread(st);
12         Thread t2 = new Thread(gt);
13 
14         //启动线程
15         t1.start();
16         t2.start();
17     }
18 }

线程的状态转换图及常见执行情况:

* 线程组: 把多个线程组合到一起。
* 它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

 1         MyRunnable my = new MyRunnable();
 2         Thread t1 = new Thread(my, "林青霞");
 3         Thread t2 = new Thread(my, "刘意");
 4         // 我不知道他们属于那个线程组,我想知道,怎么办
 5         // 线程类里面的方法:public final ThreadGroup getThreadGroup()
 6         ThreadGroup tg1 = t1.getThreadGroup();
 7         ThreadGroup tg2 = t2.getThreadGroup();
 8         // 线程组里面的方法:public final String getName()
 9         String name1 = tg1.getName();
10         String name2 = tg2.getName();
11         System.out.println(name1);
12         System.out.println(name2);
13         // 通过结果我们知道了:线程默认情况下属于main线程组
14         // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
15         System.out.println(Thread.currentThread().getThreadGroup().getName());
 1         // ThreadGroup(String name)
 2         ThreadGroup tg = new ThreadGroup("这是一个新的组");
 3 
 4         MyRunnable my = new MyRunnable();
 5         // Thread(ThreadGroup group, Runnable target, String name)
 6         Thread t1 = new Thread(tg, my, "林青霞");
 7         Thread t2 = new Thread(tg, my, "刘意");
 8         
 9         System.out.println(t1.getThreadGroup().getName());
10         System.out.println(t2.getThreadGroup().getName());
11         
12         //通过组名称设置后台线程,表示该组的线程都是后台线程
13         tg.setDaemon(true);

/*
* 线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
*
* 如何实现线程的代码呢?
* A:创建一个线程池对象,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行:
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口。
* C:调用如下方法即可
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:我就要结束,可以吗?
* 可以。

 1     public static void main(String[] args) {
 2         // 创建一个线程池对象,控制要创建几个线程对象。
 3         // public static ExecutorService newFixedThreadPool(int nThreads)
 4         ExecutorService pool = Executors.newFixedThreadPool(2);
 5 
 6         // 可以执行Runnable对象或者Callable对象代表的线程
 7         pool.submit(new MyRunnable());
 8         pool.submit(new MyRunnable());
 9 
10         //结束线程池
11         pool.shutdown();
12     }

//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。

 1 public class MyCallable implements Callable {
 2 
 3     @Override
 4     public Object call() throws Exception {
 5         for (int x = 0; x < 100; x++) {
 6             System.out.println(Thread.currentThread().getName() + ":" + x);
 7         }
 8         return null;
 9     }
10 
11 }

* 多线程实现的方式3:
* A:创建一个线程池对象,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行:
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口。
* C:调用如下方法即可
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:我就要结束,可以吗?

* 可以。

 1 /*
 2  * 线程求和案例
 3  */
 4 public class MyCallable implements Callable<Integer> {
 5 
 6     private int number;
 7 
 8     public MyCallable(int number) {
 9         this.number = number;
10     }
11 
12     @Override
13     public Integer call() throws Exception {
14         int sum = 0;
15         for (int x = 1; x <= number; x++) {
16             sum += x;
17         }
18         return sum;
19     }
20 
21 }
 1 public class CallableDemo {
 2     public static void main(String[] args) throws InterruptedException, ExecutionException {
 3         // 创建线程池对象
 4         ExecutorService pool = Executors.newFixedThreadPool(2);
 5 
 6         // 可以执行Runnable对象或者Callable对象代表的线程
 7         Future<Integer> f1 = pool.submit(new MyCallable(100));
 8         Future<Integer> f2 = pool.submit(new MyCallable(200));
 9 
10         // V get()
11         Integer i1 = f1.get();
12         Integer i2 = f2.get();
13 
14         System.out.println(i1);
15         System.out.println(i2);
16 
17         // 结束
18         pool.shutdown();
19     }
20 }

 * 匿名内部类的格式:

 1         // 继承Thread类来实现多线程
 2         new Thread() {
 3             public void run() {
 4                 for (int x = 0; x < 100; x++) {
 5                     System.out.println(Thread.currentThread().getName() + ":"
 6                             + x);
 7                 }
 8             }
 9         }.start();
10 
11         // 实现Runnable接口来实现多线程
12         new Thread(new Runnable() {
13             @Override
14             public void run() {
15                 for (int x = 0; x < 100; x++) {
16                     System.out.println(Thread.currentThread().getName() + ":"
17                             + x);
18                 }
19             }
20         }) {
21         }.start();

* 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。
* 依赖Timer和TimerTask这两个类:
* Timer:定时
* public Timer()
* public void schedule(TimerTask task,long delay)
* public void schedule(TimerTask task,long delay,long period)
* public void cancel()
* TimerTask:任务

 1 public class TimerDemo {
 2     public static void main(String[] args) {
 3         // 创建定时器对象
 4         Timer t = new Timer();
 5         // 3秒后执行爆炸任务
 6         // t.schedule(new MyTask(), 3000);
 7         //结束任务
 8         t.schedule(new MyTask(t), 3000);
 9     }
10 }
11 
12 // 做一个任务
13 class MyTask extends TimerTask {
14 
15     private Timer t;
16     
17     public MyTask(){}
18     
19     public MyTask(Timer t){
20         this.t = t;
21     }
22     
23     @Override
24     public void run() {
25         System.out.println("beng,爆炸了");
26         t.cancel();
27     }
28 
29 }

需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)

 1 class DeleteFolder extends TimerTask {
 2 
 3     @Override
 4     public void run() {
 5         File srcFolder = new File("demo");
 6         deleteFolder(srcFolder);
 7     }
 8 
 9     // 递归删除目录
10     public void deleteFolder(File srcFolder) {
11         File[] fileArray = srcFolder.listFiles();
12         if (fileArray != null) {
13             for (File file : fileArray) {
14                 if (file.isDirectory()) {
15                     deleteFolder(file);
16                 } else {
17                     System.out.println(file.getName() + ":" + file.delete());
18                 }
19             }
20             System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
21         }
22     }
23 }
24 
25 public class TimerTest {
26     public static void main(String[] args) throws ParseException {
27         Timer t = new Timer();
28 
29         String s = "2014-11-27 15:45:00";
30         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
31         Date d = sdf.parse(s);
32 
33         t.schedule(new DeleteFolder(), d);
34     }
35 }

1:多线程有几种实现方案,分别是哪几种?
两种。

继承Thread类
实现Runnable接口

扩展一种:实现Callable接口。这个得和线程池结合。

2:同步有几种方式,分别是什么?
两种。

同步代码块
同步方法

3:启动一个线程是run()还是start()?它们的区别?
start();

run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法

4:sleep()和wait()方法的区别
sleep():必须指时间;不释放锁。
wait():可以不指定时间,也可以指定时间;释放锁。

5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。

6:线程的生命周期图
新建 -- 就绪 -- 运行 -- 死亡
新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡
建议:画图解释。

原文地址:https://www.cnblogs.com/samuraihuang/p/10041319.html