java线程相关题目

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Dear_mango/article/details/80354949

进程是系统中拥有资源的一个基本单位。(相当于一个程序)

线程是系统中调度和分派的基本单位。它被包括在进程中。(能够执行代码的执行单元)

&多线程的好处:1.发挥多核cpu的优势 2.防止阻塞. 3.方便建模(大任务分解成小任务,通过多线程分别运行,简单多了)

&创建线程的三种方式:

1.继承Thread类,重写run()方法。

2.实现runnable接口,并实现该接口的run()方法。(推荐使用)

3.实现Callable接口,重写call()方法。(Executor框架的功能类,可以提供返回值,可以抛出异常)

&同步和异步的区别:同步就是等待返回结构,只有结果返回了才往下继续执行。(一般在多线程环境中,需要访问同一个资源时,为了确保该资源在同一时刻只能一个线程访问,否则运行的结构无法预料,这个时候使用同步)。异步就是不需要等待返回结果,可以直接运行接下来的程序。(节省了时间,提高程序的效率)

&run()方法和start()方法的区别:系统通过调用start()方法来启动一个线程,此时线程处于就绪状态而非运行状态,也就意味着JVM可以调度这个线程。在调度过程中,通过线程类的run()方法来完成实际的操作,当run()方法结束后,线程就终结了。

如果直接调用run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程。

也就是说,start()方法能够异步的调用run()方法,但是直接调用run()方法却是同步的,因此无法达到多线程的目的。

&sleep()方法与wait()方法的区别:1).都是使线程暂停的方法。但是sleep()方法是Thread类的静态方法,它会把执行机会让给其它线程。而wait()方法是Object类的方法,这个方法会使当前拥有该对象锁的进程等待,直到其它线程调用notify()方法才醒过来。2).sleep()方法是让线程暂停执行一段时间,时间一到自动恢复,不涉及线程间的通信。因此,调用sleep()方法不会释放锁。而wait()方法会释放掉它所占用的锁,从而使线程所在对象的其它synchronized数据可以被别的线程使用。3).由于wait()方法的特殊意义,因此它必须放在同步控制方法或同步语句块中。但是sleep()方法可以放在任意地方使用。4).sleep()方法必须捕获异常。在sleep的过程中,有可能被其他对象调用它的interrupt(),产生InterruptedException异常。

&join()方法:让调用该方法的线程在执行完run()方法后,再执行调用的线程join方法后面的代码。具体而言,可以通过线程A的join方法来等待线程A的结束,使先执行线程A的代码。也可以设置等待时间。

现有T1、T2、T3三个线程,使它们依次执行,T2在T1执行完之后执行,T3在T2执行之后执行:

public static void main(String[] args) {
        method01();
        method02();
    }

    /**
     * 第一种实现方式,顺序写死在线程代码的内部了,有时候不方便
     */
    private static void method01() {
        Thread t1 = new Thread(new Runnable() {
            @Override public void run() {
                System.out.println("t1 is finished");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 is finished");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 is finished");
            }
        });

        t3.start();
        t2.start();
        t1.start();
    }

&多线程同步的实现方法(三种同步机制):

在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。

解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其它线程便不能在使用那个资源,除非被解决。

一、

Synchronized:

当Synchronized关键字修饰一个方法的时候,该方法叫做同步方法:java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法的时候,表示将对象上锁,此时其它任何线程都无法再去访问synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。

注意1:

如果一个对象有多个synchronized方法,某一个时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其它线程是无法访问该对象的任何synchronzed方法的。

注意2:

如果某个Synchronized方法是static的,那么当线程访问该方法时,它锁的并不是Synchronized方法所在的对象,而是Synchronized方法所在的对象所对象的Class对象,因为java中无论一个类有多少个对象,这些对象会对应唯一一个class对象,因此当线程分别访问同一个类的两个对象的两个static Synchronized方法的时候,他们执行的顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行。

Java多线程中的10个面试要点

二、

wait()方法和notify()方法:

当使用synchronized来修饰某个共享资源时,如果线程A1在执行synchronized代码,另外一个线程A2也要同时执行同一个对象的同一synchronized代码时,线程A2将要等待线程A1执行完成后,才能继续执行。在这种情况下可以使用wait()方法和notify()方法。

在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进入等待状态,并且可以调用notify()方法或notifyAll()方法通知正在等待的其它线程。notify()方法仅唤醒一个线程(等待队列中的第一个线程)并允许它去获得锁,notifyAll()方法唤醒所有等待这个对象的线程并允许它们去获得锁(并不是让所有唤醒线程都获得锁,而是让它们去竞争)。

三、

使用重入锁Lock

ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 
    它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
    ReenreantLock类的常见使用:
        ReentrantLock() : 创建一个ReentrantLock实例 
        lock() : 获得锁 
        unlock() : 释放锁 
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

(四)、

还有一些其他的方法可以同步线程

volatile关键字修饰变量。

使用线程本地变量 ThreadLocal

使用阻塞队列实现线程同步(java.util.concurrent包中提供的类)

注意:Lock和synchronized的锁的机制不同,所以它们的锁相当于两种不同的锁,使用时互不影响。

&什么是守护线程:

线程分为用户线程和守护线程。守护线程的一个典型例子就是垃圾回收器。它是指在程序运行时在后台提供一种通用服务的线程。如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了。

&终止线程的3中方式
1.线程正常执行完毕,正常退出。

2.使用while()循环在特定条件下退出。

    通过设置标志来控制run()方法中的循环是否执行,从而离开run()方法终止线程。

private volatile  Boolean flag;
public void stop(){
	flag = false;
}
public void run(){
	while(flag){
		;//do something
	}
}
    

3.使用interrupt方法终止线程。(会抛出异常,需捕获,让线程安全退出)

&一个线程如果出现了运行时异常会怎么样

如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是: 如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

&怎么检测一个线程是否持有对象监视器

我也是在网上看到一道多线程面试题才知道有方法可以判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着 "某条线程"指的是当前线程 。

&死锁:

线程A和线程B相互等待对方持有的锁导致程序无限死循环下去。

写一个死锁的程序:

(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的对象锁,便被阻塞,此时一个死锁就形成了。

&同步方法和同步块,哪个是更好的选择

同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越少越好。

借着这一条,我额外提一点,虽说同步的范围越少越好,但是在Java虚拟机中还是存在着一种叫做 锁粗化 的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味着要进行反复的加锁->解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁-->解锁的次数,有效地提升了代码执行的效率。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页