多线程-基础
0)什么是线程?
一个Java应用是一个进程。
一个进程可以有多个线程,线程是操作系统调度的最小单位,每个线程都拥有单独的栈内存用来存储本地数据。
线程并不是越多越好。
A)线程是宝贵的资源,一个线程大约10M。
B)多线程运行时存在上下文切换。
1)如何创建一个线程?
继承Thread类 Or 实现 Runnable接口/Callable接口
Runnable接口无返回值,无法抛出异常;Callable接口有返回值。
实现Runnable接口的run方法实际实现是线程执行任务的内容;继承Thread类,重写run方法即线程执行的任务内容。
为什么说实现 Runnable 接口比继承 Thread 类实现线程要好?
A:Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
B:Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。
2)如何开启或调度一个线程,调用哪个方法?
()->{
}.start(); //start方法
3)如何正确地停止线程?
while (!Thread.currentThread().isInterrupted() && more work to do) {
do more work
}
4)线程有几种状态?状态切换
线程的状态共有6种。(JDK,API中定义的。)
1:创建好线程还未执行(未调用Start()) 初始化状态
2:()->{
}.start(); 调用start()方法后,由操作系统调度。获取到时间片就执行,时间片到期后就绪,继续等待操作系统调度。 这个过程统称为运行状态。
3:synchronized关键字,等待其它线程释放锁。 阻塞状态
4:超时等待 将执行权交给其它线程,有限时间等待其它线程通知它再回到运行状态。 自动回到运行时状态。
设置了时间参数的 Thread.sleep(long millis) 方法;
设置了时间参数的 Object.wait(long timeout) 方法;
设置了时间参数的 Thread.join(long millis) 方法;
设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。
5:等待状态 将执行权交给其它线程,等待其它线程通知它再回到运行状态。
没有设置 Timeout 参数的 Object.wait() 方法。
没有设置 Timeout 参数的 Thread.join() 方法。
LockSupport.park() 方法。
6:终止状态
线程生命周期不可逆:一旦进入 Runnable 状态就不能回到 New 状态;一旦被终止就不可能再有任何状态的变化。所以一个线程只能有一次 New 和 Terminated 状态,只有处于中间状态才可以相互转换。
5)线程优先级
setPriority()设置线程的优先级
1-10
线程优先级依赖于操作系统。
6)守护线程
thread.setDaemon(true);
设置守护线程要在调用前。
7)sleep方法和wait方法有什么区别?
sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器。
sleep方法是线程的方法,wait方法是Object的方法。
wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
8)如何检测一个对象持有对象监视器?
Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着”某条线程”指的是当前线程。
9)一个线程出现异常会怎么样?
如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。
10)线程的基本操作
yield
public static native void yield();这是一个静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。
sleep
public static native void sleep(long millis)方法显然是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。
join
public final synchronized void join(long millis)
public final synchronized void join(long millis, int nanos)
public final void join() throws InterruptedException
11)写一个Java线程死锁程序
1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock作为同步代码块的锁;
2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不需要太多,50毫秒差不多了,然后接着获取lock2的对象锁。这么做主要是为了防止线程1启动一下子就连续获得了lock1和lock2两个对象的对象锁
3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的
这样,线程1"睡觉"睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就形成了。