1.说说线程中run()和start()的区别:
run其实指的就是当前线程执行run()方法体里的内容,就是相当于执行一个普通的类方法。
而start则是线程体执行run()方法体里的内容,这个时候才是真正创建了一个线程去执行。
2.线程可以重复启动吗?
不可以,因为线程一旦启动就会持续执行到死亡。重复启动会抛出
IllegalThreadStateException异常。
3.说说线程的生命周期:
创建(new):我们new出来一个线程对象,这个时候,该线程就是一个普通的对象,在栈内存中有地址,堆内存中有空间。
就绪(ready):我们调用start()方法,线程就进入了就绪状态,记住,此时并没有进入运行,因为我们还要等待JVM的资源调度,分配到了资源才能开始运行。
运行(running):线程分到了CPU资源就可以开始运行了,如果是一个多核CPU的电脑,那可以多个线程并行运行,当然如果线程数量过多的话,还是会出现某个核上不同的线程来回切换执行。
阻塞(blocking):这里可以是主动阻塞,比如说线程调用了某个IO操作,或者sleep方法或者在等待某个通知(notify)。也可以是被动阻塞,比如说JVM觉得你运行时间够久了,就切了你。
死亡(death):call()或者run()方法体里面的方法执行完了,自然结束死亡或者抛出异常死亡了。
4.线程是如何实现同步的:
1.使用synchronize关键字:(可重入、不可中断、非公平)
使用synchronize关键词修饰方法:
public class SynchronizedDemo2 { public synchronized void method() {//直接在签名上面加就好了 System.out.println("synchronized ⽅法"); } }
在指令文件中就是这样实现的,通过monitorenter和monitorexit关键字
使用synchronize关键词修饰代码块:
public void method() { synchronized (this) { System.out.println("synchronized 代码块"); } }
这里面的this参数就是和我们以往理解的一样指的就是该类的对象引用,那有人就会问了,那是不是就是直接把整个对象都锁住了,其实不是的。其他线程仍然可以访问synchronized(this)代码块以外的内容,这个**“锁住对象”**的操作体现在,哪怕线程进入的是该对象中的某一个synchronized代码块,那么该对象中其他所有被synchronized修饰的代码块也会拒绝其他线程进入,这样我们就可以理解为什么是synchronized(this)了。
当synchronized修饰static方法的时候,那他就是锁住整个类了
2.使用ReentrantLock:
这是Lock的实现类,属于默认非公平(可牺牲性能变成公平),可重入,互斥锁,基本与synchronized功能相同,但是使用方法不同且更灵活。使用方法如下:
class MyThread implements Runnable { private ReentrantLock lock = new ReentrantLock();//创建锁 public void run() { lock.lock();//锁住的区域 try{ for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } }finally{ lock.unlock();//必须释放锁,哪怕中间抛出异常也要释放 } } }
但是ReentrantLock比synchronized更加的灵活,比如说他可以设置成公平锁,等待可中断,相对应的synchronized就是死等,不管等多久就等到为止。
上面synchronized和ReentrantLock确保的是操作的原子性,下面的方法volatile和原子变量为确保变量的可见性。
volatile,确保每次读取都是读取内存中的值而非寄存器中的值。
原子变量,其实就是一种泛化的volatile,他比锁的粒度更细,因为他的竞争只存在单个变量的操作中,但是确保了单个变量的原子性。
ExecutorService executorService = Executors.newFixedThreadPool(5); AtomicInteger count = new AtomicInteger(0); for (int i = 0; i < 1000; i++) { executorService.submit((Runnable) () -> { System.out.println(Thread.currentThread().getName() + " count=" + count.get()//获取原子变量的值); count.incrementAndGet();//获取原子变量的值,并且自增 }); }
5.线程之间是如何通信的:
Object类自带了wait(),notify(),notifyAll()等一系列方法。
与ReentrantLock搭配使用的Condition类的await(),signal(),signalAll()等一系列。
这二者基本相同,只不过ReentrantLock与Condition更加的高效。
基本都是维护了一个就绪队列和阻塞队列,将等待的线程加入阻塞队列,然后发送signal或者notify之后,将阻塞队列中的线程加入就绪队列,就绪队列中的线程争夺CPU的使用权。
还有一种通信方式就是BlockingQueue,队列满了的话通知,写入的线程阻塞,队列空了的话就通知读取的线程阻塞,只要应用在生产者与消费者模型。
6.说说wait和sleep的区别
7.如何让子线程先于主线程立即执行
使用join()方法,比如:childThread.join()
那么当前的主线程就会立即停止执行,直到子线程执行完毕,当然我们也可以设置时间限制,比如childThread.join(1000);
主线程阻塞1000秒以后,恢复并行执行。
具体可以参看这篇文章