1.线程连续两次调用start()方法所产生的情况
public class TestMain {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始运行");
}
});
System.out.println("线程当前的状态为" + thread.getState());
thread.start();
System.out.println("start后线程当前的状态为" + thread.getState());
thread.start();
}
}
运行结果:
由此可见:第二次调用start方法,会抛出异常java.lang.IllegalThreadStateException
原因看源代码可知
private volatile int threadStatus = 0;
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
A zero status value corresponds to state “NEW”(中文):零状态值对应于状态“NEW”
线程启动前,状态为NEW ,threadStatus 初始值为 0。线程启动后,其初始状态值会改变,所以会抛出异常。
2.调用线程对象的start()方法会执行run(),为什么不能直接调用run()方法??
start()方法启动线程后,整个线程处于就绪状态,等待虚拟机调度, 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。期间虚拟机是分时间片轮番调用各个线程体的。
run()方法启动是当作普通方法的方式调用,这里虚拟机不会线程调度,虚拟机会执行这个方法直到结束后自动退出。
3. 如何停止线程
看另一篇博客介绍
线程中断的方式 interrupt与volatile
4.如何处理不可中断的阻塞
我们知道可阻塞的方法都是通过提前返回或者抛出InterruptedException来响应中断请求的,从而使开发人员更容易构建出能响应取消请求的任务,那么不可中断的阻塞方法该如何处理呢?
不可中断阻塞的情况一般有以下几种:
- java.io包中的同步Socket I/O
- java.io包中的同步I/O
- Selector的异步I/O
- 获取某个锁
主要关键在于:继承thread类 重写原来中断线程或者取消任务的方法,在方法里面加入自己的取消操作,比如关闭数据流,关闭套接字等,然后再调用父类的中断方法,这样就可以既关闭了阻塞的任务,又中断了线程。
举例子
public class ReaderThread extends Thread {
private static final int BUFSZ = 1024;
private final Socket socket;
private final InputStream in;
public ReaderThread(Socket socket) throws IOException {
this.socket = socket;
this.in = socket.getInputStream();
}
// 覆盖Thread类的interrupt方法, 加入关闭socket的代码
// 如果发生中断时, 线程阻塞在read方法上, socket的关闭会导致read方法抛出SocketException, 然后run方法运行完毕
public void interrupt() {
try {
socket.close();
} catch (IOException ignored) {
} finally {
super.interrupt();
}
}
public void run() {
try {
byte[] buf = new byte[BUFSZ];
while (true) {
int count = in.read(buf);
if (count < 0)
break;
else if (count > 0)
processBuffer(buf, count);
}
} catch (IOException e) { /* Allow thread to exit */
}
}
private void processBuffer(byte[] buf, int count) {
//...
}
}
5. 线程的状态级生命周期
6. 用程序实现两个线程交替打印 0~100之间的奇偶数
/**
* 两个线程循环打印0到100的奇偶数
*/
public class WaitTest {
private static final Object lock = new Object();
private static int count =0;
public static void main(String[] args) throws InterruptedException {
new Thread(new HasAnEvenNumber(lock,count),"偶数").start();
//Thread.sleep(100);
new Thread(new HasAnEvenNumber(lock,count),"奇数").start();
}
}
class HasAnEvenNumber implements Runnable{
private static int count;
private Object lock;
public HasAnEvenNumber(Object lock,int count) {
this.lock = lock;
this.count = count;
}
@Override
public void run() {
synchronized (lock){
while (count <= 100){
System.out.println(Thread.currentThread().getName() +" " + count++);
lock.notify();
if (count<=100){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
7. 手写生产者消费者模式
package demo_18;
import java.util.ArrayList;
/**
* 生产者消费者
*/
public class Producers_And_Consumers {
public static void main(String[] args) {
MyStack myStack = new MyStack();
P_Thread p = new P_Thread(myStack);
C_Thread c = new C_Thread(myStack);
Thread threadP1 = new Thread(p);
Thread threadP2 = new Thread(p);
Thread threadP3 = new Thread(p);
Thread threadP4 = new Thread(p);
Thread threadP5 = new Thread(p);
Thread threadP6 = new Thread(p);
threadP1.start();
threadP2.start();
threadP3.start();
threadP4.start();
threadP5.start();
threadP6.start();
Thread threadC1 = new Thread(c);
Thread threadC2 = new Thread(c);
Thread threadC3 = new Thread(c);
Thread threadC4 = new Thread(c);
Thread threadC5 = new Thread(c);
threadC1.start();
threadC2.start();
threadC3.start();
threadC4.start();
threadC5.start();
}
}
/**
* 生产者
*/
class MyStack{
ArrayList<String> arrayList = new ArrayList<>();
synchronized public void set(){
while (arrayList.size() == 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
arrayList.add("张三");
/** 生产后通知消费者消费*/
this.notifyAll();
System.out.println("set : "+arrayList.size());
}
synchronized public void get(){
while (arrayList.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("get : "+arrayList.get(0));
arrayList.remove(0);
/** 消费后通知生产者生产*/
this.notifyAll();
}
}
/**
* 多线程之生产者调用
*/
class P_Thread implements Runnable{
private MyStack myStack;
public P_Thread(MyStack myStack){
this.myStack = myStack;
}
@Override
public void run() {
while (true){
myStack.set();
}
}
}
/**
* 多线程之消费者调用
*/
class C_Thread implements Runnable{
private MyStack myStack;
public C_Thread(MyStack myStack){
this.myStack = myStack;
}
@Override
public void run() {
while (true){
myStack.get();
}
}
}
8. 为什么wait()要在同步代码块中使用,而sleep()不需要
调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁.
同时为了防止产生死锁。
如下代码:
假设要开发一个线程安全的队列,代码如下所示:
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
notify(); // Since someone may be waiting in take!
}
public String take() throws InterruptedException {
while (buffer.isEmpty()) // don't use "if" due to spurious wakeups.
wait();
return buffer.remove();
}
}
上面的队列可能存在以下潜在问题:
-
消费者A调用 take() 时 buffer.isEmpty()为true
-
在消费者A调用wait()方法之前, 生产者B调用了 give(), ( buffer.add(data); notify();)
-
之后消费者A调用了 wait() (错过了生产者B调用的notify()).
-
give()如果之后没有别的生产者调用give()方法,消费者A所在线程则会一直等待。
解决方法如下: 总是保证 give/notify 和 isEmpty/wait 操作的原子性。这也是为什么wait方法要强制在同步代码块中调用.
9. 为什么线程通信的方法wait()、notify()、notifyAll()被定义在Object类里面?而sleep为什么定义在Thread类里面?
wait()、notify()、notifyAll():
首先其三者的作用便是释放锁/唤醒线程。
java中每个对象都是Object类型。而每个线程的锁都是是对象锁而非线程锁,每个线程想要执行锁内的代码,都需要先获取此对象。因此若想要将那个线程释放锁进入阻塞状态,直接调用此对象的wait()方法即可。
而处于阻塞状态的线程想要被唤醒便需要使用notify/notifyAll方法。而notify依据什么来唤醒某个处于阻塞状态的线程呢?两者间通过什么关联起来呢?答案便是对象的同步锁。
唤醒线程只有得到同步对象的锁,才能唤醒此对象中处于阻塞的线程。
wait()方法如果定义在Thread类中的话,那么线程正在等待的是哪个锁就不明确了。此三个方法定义在Object中。
sleep()方法
由于sleep为什么被定义在Thread中,我们只要从sleep方法的作用来看就知道了,sleep的作用是:让线程在预期的时间内执行,其他时候不要来占用CPU资源。从上面的话术中,便可以理解为sleep是属于线程级别的,它是为了让线程在限定的时间后去执行。而且sleep方法是不会去释放锁的
10. 关于Thread对象调用 wait()方法会怎样
首先需要知道,每个线程对象在执行完毕后,都会调用notifyAll()方法,而wait()方法位于Object类中,属于对象级别。所以如果使用Thread.wait(),在隐式的调用notifyAll后可能会使线程的wait方法失去作用。
11. notifyAll()后所有线程都会再次的抢占锁,如果某线程抢占失败会怎样?
线程处于就绪状态。等待在此抢占cpu资源
补充一点:
线程执行完wait(),join()方法后,处于WAITING状态,执行wait(Long time)/sleep(Long time)方法后处于TIMED_WAITING状态。
当线程处于等待锁释放时,处于BLOCKED状态
12. 用suspend和resume来阻塞线程可以么?
理论上可以,但是不建议使用。
原因:存在很多的弊端
(1)独占:因为suspend在调用过程中不会释放所占用的锁,所以如果使用不当会造成对公共对象的独占,使得其他线程无法访问公共对象,严重的话造成死锁。
如下代码:
public class Test {
public static void main(String[] args) throws Exception {
Wait wait = new Wait();
Thread thread = new Thread(wait);
thread.start();
thread.sleep(1);
thread.suspend();
System.out.println("main");
}
}
class Wait implements Runnable{
int i=0;
@Override
public void run() {
while(true){
i++;
System.out.println(i);
}
}
}
线程在打印到1985的时候执行了suspend方法,但是 mian 方法的输出语句无法执行。原因是printlf()方法是线程安全的。
suspend方法执行后,线程处于阻塞状态,但是并不会释放所占用的锁。
(2)当 suspend() 和 resume() 方法使用不当时也容易造成因线程的暂停导致数据不同步的情况。
13. join()期间,线程处于那种状态?
答:WAITING
状态,因为join内部执行的是wait方法。线程在执行完成后默认执行notifyAll方法,隐式得在唤醒执行join方法的当前线程。
14. 什么是守护线程
java有两种状态的线程
- 守护线程(例如垃圾回收线程:gc线程)
- 非守护线程(用户线程:用户线程即我们手动创建的线程)
守护线程与非守护线程的区别
- 守护线程
有一个特征,例如当主线程运行的时候,垃圾回收线程一起运行。
当主线程销毁,会和主线程一起销毁。 - 非守护线程
如果主线程销毁,用户线程继续运行且互不影响。
守护线程与非守护线程代码演示区别
创建守护线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("我是子线程(用户线程)");
}
}
});
// 标识当前方法为守护线程,一定要在启动线程前设置为守护线程
t1.setDaemon(true);
t1.start();
守护线程:主线程销毁停止,守护线程一起销毁
非守护线程:如果主线程销毁,用户线程继续运行且互不影响。
15.四种需要注意线程安全的情况
- 访问共享的变量或资源,会有并发的危险。比如对象的属性,静态变量,共享缓存,数据库等。
- 所有依赖时序的操作,即使每一步都是线程安全,还是存在并发问题。如 read-modify-write,check-then-act
- 不同数据之间存在捆绑关系的时候。如ip和端口号,若要修改,就需要一块修改。
- 我们使用其他类的时候,要注意是否是线程安全的。