创建线程的三种方式
1.继承Thread类,重写run()方法。创建Thread子类对象,调用start()方法开启线程。
2.实现Runnable接口,重写run()方法。创建Runnable子类对象,将其传递给Thread类的构造器,调用start()方法开启线程。
3.实现Callable接口,重写call方法。创建Callable子类对象,将其传递给Future Task的构造器,再将Future Task对象作为参数传递给Thread类的构造器,调用start()方法开启线程。
线程的run()方法和start()方法有什么区别
start()方法的作用是开启线程,让被启动的线程执行run()方法中的任务代码。直接调用run()方法并没有开启线程,执行run()方法的只有main()方法一条线程。而调用start()方法,当main()方法执行到start()时,会在栈内存中开辟出一个新的线程栈区,新的线程和main()方法并行执行。
线程的五种状态
1.新建状态(new):新建了一个线程对象。
2.可运行状态(runnable):线程对象创建后,当调用线程对象的start()方法,该线程处于就绪状态。
3.运行状态(running):可运行状态的线程获得cpu的执行权,就会执行程序代码。
4.阻塞状态(block):处于运行状态中的线程由于某种原因,暂时放弃对cpu的执行权,停止执行,此时进入阻塞状态。
5.死亡状态(dead):线程run()、main()方法执行结束或异常退出,该线程结束生命周期,死亡线程不可再次复发。
线程同步以及线程调度相关的方法
wait():使一个线程处于等待状态,并且释放所持有的对象的锁。
sleep():使一个正在运行的线程处于睡眠状态,不会释放锁,调用此方法要处理InterruptedException异常。
notify():唤醒一个处于等待状态的线程,当在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由jvm确定唤醒哪个线程。
notifyAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让他们竞争,只有获得锁的线程才能进入就绪状态。
yield():使当前线程从运行状态变为就绪状态。当前线程进入就绪状态不会立马变成运行状态,这要看cpu的调度。
sleep()和wait()方法有什么区别
1.sleep()不释放锁,wait()释放锁。
2.sleep()方法执行完成后,线程会自动苏醒。wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或notifyAll()方法来唤醒线程。
notify()和notifyAll()方法有什么区别
notify()会唤醒一个线程,notifyAll()会唤醒所有线程。
notifyAll()调用后,会将全部线程由等待池转移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify()方法只会唤醒一个线程,具体唤醒哪一个线程由jvm控制。
如何在两个线程间共享数据
在两个线程间共享变量即可实现共享数据。一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。
什么是线程同步和线程互斥
当一个线程对共享的数据进行操作时,应使之成为一个“原子操作”,即在没有完成相关操作之前,不允许其他线程打断他,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程同步。
线程互斥是指对于共享的系统资源,在各单个线程访问时的排他性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其他要使用该资源的线程必须等待,直到占用资源被释放,这就是线程互斥。
线程的优先级
每个线程都是有优先级的,一般来说高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是与操作系统有关的。我们可以定义线程的优先级,但这并不能保证高优先级的线程会在低优先级的线程前执行。java的线程优先级调度会委托给操作系统去处理,如非特别要求,一般不使用。
synchronized和Lock有什么区别
synchronized可以给类、方法、代码块加锁,而lock只能给代码块加锁。
synchronized不需要手动加锁和释放锁,使用简单,发生异常时会自动释放锁,不会造成死锁。而lock需要手动加锁和释放锁,如果使用不当,没有unLock()去释放锁就会造成死锁。通过lock可以知道有没有成功获取锁,而synchronized却无法办到。