java面试:线程

以下为博主整理网络资料而成,如有错误望请指正,感谢!

线程的状态
一般来讲,线程共有5种状态:
	1. 新建(NEW):新建了一个线程对象,没有调用start方法之前
	2. 就绪(又称可运行,RUNNABLE):调用start方法后进入就绪状态
	3. 运行(RUNNING):就绪状态的线程得到了CUP时间片,执行run方法中的代码
	4. 阻塞(BLOCKED):阻塞状态是指线程因为某些原因放弃了CPU使用权,暂时停止运行
		阻塞有三种:
			1. 等待阻塞:运行状态的线程执行wait方法,JVM把该线程放入等待队列
			2. 同步阻塞:运行状态的线程在获取对象的同步锁时,同步锁被别的线程占用,
						则JVM会把该线程放入锁池中
			3. 其他阻塞:如运行状态的线程执行了sleep方法或join方法或发出I/O请求;
			   当sleep超时、join等待线程终止或I/O处理完毕时,线程进入可运行状态
5. 死亡(DEAD):线程正常执行结束,或因异常退出了run方法
线程常用方法
  1. start()
    启动一个线程,使其进入就绪状态

  2. run()
    包含该线程的执行内容,当该线程进入运行状态自动执行run方法

  3. sleep()
    在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。sleep()使当前线程进入阻塞状态,在指定时间内不会执行。

  4. wait()【Object的方法】
    在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
    当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
    唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
    wait()和notify()必须在synchronized函数或synchronized block中进行调用。
    如果在non-synchronized函数或non-synchronized block中进行调用,
    虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常

  5. yield()
    暂停当前正在执行的线程对象。
    yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
    yield()只能使同优先级或更高优先级的线程有执行的机会(注意是有机会,不是一定)。
    实际上,yield()执行后只是让该线程和优先级高于等于该线程的其他线程一起抢夺CPU时间片

  6. join()
    等待该线程终止。
    等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测

  7. setPriority()
    更改线程的优先级。

  8. stop()
    已过时,强行终止线程(不安全)

  9. notify()/notifyAll()【Object的方法】
    唤醒在等待中的某个线程/所有线程

  10. currentThread()
    获取当前正在执行的线程

实现多线程的三种方式(前两种常用)
  1. 创建一个线程子类实现Thread类,重写run方法
  2. 创建一个实现类实现Runnable接口,重写run方法
    实现Runnable接口创建多线程的优点:
    1. 避免了单继承的局限性(继承Thread类就不能继承其他类,实现接口则不一样)
    2. 增强了程序的扩展性,降低了程序的耦合性。实现接口的方式,把设置线程任务和开启新线程进行了分离
    3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类
  3. 创建一个实现类实现Callable,重写call方法;与第二种方法不一样的是有返回值
start()和run()的区别
1. 用start方法来启动线程,真正实现多线程的运行,而run方法只是线程中的一个普通方法,	
   如果直接调用run方法,程序中只有主线程中执行(仍然按顺序执行)
2. start方法不能被重复调用,run方法可以
3. 调用start后执行的run方法代码可以不执行完就继续执行下面的代码(进行线程切换)
	直接调用run方法必须等待其代码全部执行完才能执行下面的代码
java中断(终止)线程的三种方法
1. 使用stop()
	强行终止线程。不安全,已过时,不能保证线程资源的释放
2. 使用退出标识符
3. 使用interruupt方法(异常退出法)【推荐】

参考博文

守护线程(Daemon)
用户线程:我们平常创建的线程普通线程(包括main线程)
守护线程:用来服务于用户线程(如JVM的GC),实现方式和普通线程类似,只是要先设置setDaemon(true)
daemon的特点:
	1. daemon线程的创建过程中需要先调用setDaemon方法进行设置,
	    而且setDaemon(true)必须在start方法之前设置,否则会抛出IllegalThreadStateException异常
	2. daemon线程的终止条件是当前是否存在用户线程。
	3. daemon线程不能用于访问固有资源(如进行读写操作、计算)
	4. daemon线程中产生的新线程也是daemon线程
死亡的线程能不能复活
进入DEAD状态不能复活;执行完run方法的线程即进入DEAD状态,对象可能仍然存在,
但是不能调用start(),否则抛出IllegalThreadStateException异常。
对于其对象,可能不会被GC回收(可以自救)

2019-09-25添加:

线程局部变量ThreadLocal
1. ThreadLocal的作用和目的
	用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,
	而在另外线程中运行时又共享另外一份数据,线程的数据是独享的

2. ThreadLocal的实现原理
	每个线程调用全局ThreadLocal对象的set方法,在set方法中,首先根据当前线程获取当前线程的ThreadLocalMap对象,
	然后向这个map中插入一条记录,key是ThreadLocal对象,value是各自的set方法传进去的值。
	在线程结束时可以调用ThreadLocal.remove()方法,这样更快释放内存,也可以调用ThreadLocal.clear()立即释放内存;
	也可以都不调用,因为线程结束后自动释放相关ThreadLocal变量
	1. 应用场景例子:
		银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,
		它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法
3. [实例可参考](https://juejin.im/post/5ac2eb52518825555e5e06ee)
synchronized和volatile关键字的作用
volatile:
	一但一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
		1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个共享变量的值,
		  这新值对其他线程来说是立即可见的。
		2. 禁止进行指令重排序
	volatile的本质是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取
synchronized:
	synchronized是锁定当前变量,只有当前线程可以访问变量,其他线程被阻塞住
区别:
	1. volatile仅能使用在变量级别;synchronized可以使用在变量、方法、类级别
	2. volatile仅能修改变量的可见性,不能保证原子性;synchronized则可以保证变量的原子性和可见性
	3. volatile不会造成线程阻塞;synchronized可能会造成线程的阻塞
	4. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值