2021-4-12_Java基础8

2021/4/12 20:29:42

本文主要是介绍2021-4-12_Java基础8,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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的区别

  • wait是Object的方法而sleep则是Thread的方法
  • wait只能在同步代码内使用而sleep可以随意使用
  • wait会释放锁,且要等notify唤醒,而sleep则不会释放锁,时间到了继续执行。

7.如何让子线程先于主线程立即执行
使用join()方法,比如:childThread.join()那么当前的主线程就会立即停止执行,直到子线程执行完毕,当然我们也可以设置时间限制,比如childThread.join(1000);主线程阻塞1000秒以后,恢复并行执行。
具体可以参看这篇文章



这篇关于2021-4-12_Java基础8的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程