java 并发(三)---Thread 线程
Thread 的状态
线程共有五种状态.分别是: (1)新建 (2)就绪 (3)运行 (4)阻塞 (5)死亡 ,下面列列举的状态需要结合状态示意图更好理解.
- 新建状态(New): 新创建了一个线程对象。
- 就绪状态(Runnable): 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得(包括我们所说的锁)。
- 运行状态(Running): 就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
- 等待阻塞 : 运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒.
- 同步阻塞 : 运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中,言外之意就是锁被其他线程拿了,自己只能等待。
- 其他阻塞 : 运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 阻塞这个状态可以这样总结: 线程存在且没死亡,那么运行和就绪以外的状态就是阻塞,不管是否获得锁或是进入锁池.
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
下面为线程的状态示意图: (出处见水印)
Thread 方法 和 Object两个方法
后面两个Object 方法
- sleep 方法 : “sleep”—“睡觉”,意思就是休眠一段时间,时间过后继续执行,不释放锁和其他资源,进入阻塞状态
- join 方法 : 源码实现在下方,可以看到只要thread存活的情况下就会某个时间内循环一个等待的方法
(注意这个方法和下面的
wait 方法不是一回事,下面的wait方法会一直阻塞在那里),使用场景是例如某个操作执行前需要执行一个加载资源的
任务,那么执行的这个操作就要一直等待加载的操作完成以后才可以执行(join(0)).
- yield 方法 : 让步于其他线程执行.
- wait 方法 : 等待,释放锁和其他资源,进入等待队列,这个等待队列里边存放的对象都是等待获取锁的对象,另外一点, wait 是对象
object 中的方法,而不是线程中的方法,同时调用 XX.wait(); 时必须要在同步语句中,或是该对象已经被加锁的情况
下,试想一下,wait 方法本身就是某个对象加锁后释放锁,不可能没加锁的情况下可以释放锁.wait 不能自动唤醒,需要
notify / notifyAll 方法
- notify/notifyAll 方法 : 唤醒
join的源码实现
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() – base;
}
}
}
同步方法就是使用synchronized关键字修饰某个方法,这个方法就是同步方法,这个同步方法(非static方法)无须显示指定同步监视器,同步方法的同步监视器就是this,也就是调用该方法的对象。看一下下面的例子。
1 public class Test {
2
3 static class ThreadJoinTest extends Thread {
4 public ThreadJoinTest(String name) {
5 super(name);
6 }
7
8 @Override
9 public void run() {
10 for (int i = 0; i < 1000; i++) {
11 System.out.println(this.getName() + ":" + i);
12 }
13 }
14 }
15
16
17 public static void main(String[] args) throws InterruptedException {
18 ThreadJoinTest t1 = new ThreadJoinTest("t1");
19 ThreadJoinTest t2 = new ThreadJoinTest("t2");
20 t1.start();
21
22 t1.join();
23 t2.start();
24 }
25 }
上面的例子中在main方法中调用 t1.join 方法,我们走到 join 源码中就会发现 wait 方法的调用就是main方法的对象(即主线程),那么main方法的对象一直wait ,即是说 t2.start () 一直执行不到unless t1 finished.
Thread thread1 = new Thread(() -> {
System.out.println("t1 开始执行" + new Date());
synchronized (obj) {
try {
Thread.currentThread().join(0);
//obj.wait();
System.out.println("线程1 继续执行,执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} }
}); Thread thread2 = new Thread(() -> {
try {
synchronized (obj) { System.out.println("线程2 开始执行 " + new Date());
Thread.sleep(2 * 1000);
System.out.println("线程2 执行结束 " + new Date());
// obj.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
});
thread1.start(); Thread.sleep(4*1000);
thread2.start();
执行后会发现线程一调用join()方法后,线程2没能获取对象执行,而是等待线程1执行完成后,线程2才会执行.
我们来看看在 Java 7 Concurrency Cookbook 中相关的描述(很清楚地说明了 join() 的作用):
Waiting for the finalization of a thread
In some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.
当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
方法使用
wait 和 notify 方法
可以看到 notify 方法的使用,是在同步方法内,并且同样获取同样的锁对象, wait 和 notify 方法的运用常常被用来做生产者-消费者的实现.
//以下代码来自参考文章,见参考资料
public class Test {
public static Object object = new Object();
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2(); thread1.start(); try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
} thread2.start();
} static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
}
System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
}
}
} static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println("线程"+Thread.currentThread().getName()+"调用了object.notify()");
}
System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");
}
}
}
join 方法
下面代码中parent线程会等待child线程执行完成后再继续执行.
// 父线程
public class Parent extends Thread {
public void run() {
Child child = new Child();
child.start();
child.join();
// ...
}
}
// 子线程
public class Child extends Thread {
public void run() {
// ...
}
}
yield
public class YieldExcemple { public static void main(String[] args) {
Thread threada = new ThreadA();
Thread threadb = new ThreadB();
// 设置优先级:MIN_PRIORITY最低优先级1;NORM_PRIORITY普通优先级5;MAX_PRIORITY最高优先级10
threada.setPriority(Thread.MIN_PRIORITY);
threadb.setPriority(Thread.MAX_PRIORITY); threada.start();
threadb.start();
}
} class ThreadA extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadA--" + i);
Thread.yield();
}
}
} class ThreadB extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadB--" + i);
Thread.yield();
}
}
}
以下总结来自参考文章.
Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,
有可能是其他人先上车了,也有可能是Yield先上车了。
但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,
最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。
Thread 线程生成
产生一个Thread的方式有两种 :
- 继承 Thread
- 创建一个类进程 Runnable , 再把这个类传给 Thread
第二种方式可以解决java 单继承的问题.同时接口和代码分离,比较清晰.同时同个线程可以复用由实现Runnable 的类.
public class DemoRunnable implements Runnable {
public void run() {
//Code
}
}
//with a "new Thread(demoRunnable).start()" call public class DemoThread extends Thread {
public DemoThread() {
super("DemoThread");
}
public void run() {
//Code
}
}
补充
中断
让某个线程中断---Thread.interrupt()方法
Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
如何正确中断一个线程执行?
可以参考这篇文章: 如何正确停止一个线程
处理InterruptionException
可以参考这篇文章:Dealing with InterruptedException
对于InterruptedException如何处理最重要的一个原则就是Don't swallow interrupts,一般两种方法:
- 继续设置interrupted status
- 抛出新的InterruptedException
1 try {
2 ………
3 } catch (InterruptedException e) {
4 // Restore the interrupted status
5 Thread.currentThread().interrupt();
6 // or thow a new
7 //throw new InterruptedException();
8 }
文章也可提到了,既然线程有可能中断,那么我们也可以创建一个可取消的任务。
wait 方法
这个方法在object里,这方法的文档中建议这样使用:
1 synchronized (obj) {
2 while (<condition does not hold>)
3 obj.wait(timeout);
4 ... // Perform action appropriate to condition
5 }
这是为什么呢?因为要是多个线程在等待锁,当这个线程从wait 中醒来的时候,某个变量被另外的线程改变了就会发生异常,所以就加一个一个while判断。这可能有点难解释 , 看下面摘自Stackoverflow 的例子。
链接地址: https://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again
Clearly,
notify
wakes (any) one thread in the wait set,notifyAll
wakes all threads in the waiting set. The following discussion should clear up any doubts.notifyAll
should be used most of the time. If you are not sure which to use, then usenotifyAll
.Please see explanation that follows.Read very carefully and understand. Please send me an email if you have any questions.
Look at producer/consumer (assumption is a ProducerConsumer class with two methods). IT IS BROKEN (because it uses
notify
) - yes it MAY work - even most of the time, but it may also cause deadlock - we will see why:1 public synchronized void put(Object o) {
2 while (buf.size()==MAX_SIZE) {
3 wait(); // called if the buffer is full (try/catch removed for brevity)
4 }
5 buf.add(o);
6 notify(); // called in case there are any getters or putters waiting
7 }
8
9 public synchronized Object get() {
10 // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
11 while (buf.size()==0) {
12 wait(); // called if the buffer is empty (try/catch removed for brevity)
13 // X: this is where C1 tries to re-acquire the lock (see below)
14 }
15 Object o = buf.remove(0);
16 notify(); // called if there are any getters or putters waiting
17 return o;
18 }FIRSTLY,
Why do we need a while loop surrounding the wait?
We need a
while
loop in case we get this situation:Consumer 1 (C1) enter the synchronized block and the buffer is empty, so C1 is put in the wait set (via the
wait
call). Consumer 2 (C2) is about to enter the synchronized method (at point Y above), but Producer P1 puts an object in the buffer, and subsequently callsnotify
. The only waiting thread is C1, so it is woken and now attempts to re-acquire the object lock at point X (above).Now C1 and C2 are attempting to acquire the synchronization lock. One of them (nondeterministically) is chosen and enters the method, the other is blocked (not waiting - but blocked, trying to acquire the lock on the method). Let's say C2 gets the lock first. C1 is still blocking (trying to acquire the lock at X). C2 completes the method and releases the lock. Now, C1 acquires the lock. Guess what, lucky we have a
while
loop, because, C1 performs the loop check (guard) and is prevented from removing a non-existent element from the buffer (C2 already got it!). If we didn't have awhile
, we would get anIndexArrayOutOfBoundsException
as C1 tries to remove the first element from the buffer!NOW,
Ok, now why do we need notifyAll?
In the producer/consumer example above it looks like we can get away with
notify
. It seems this way, because we can prove that the guards on the wait loops for producer and consumer are mutually exclusive. That is, it looks like we cannot have a thread waiting in theput
method as well as theget
method, because, for that to be true, then the following would have to be true:
buf.size() == 0 AND buf.size() == MAX_SIZE
(assume MAX_SIZE is not 0)HOWEVER, this is not good enough, we NEED to use
notifyAll
. Let's see why ...Assume we have a buffer of size 1 (to make the example easy to follow). The following steps lead us to deadlock. Note that ANYTIME a thread is woken with notify, it can be non-deterministically selected by the JVM - that is any waiting thread can be woken. Also note that when multiple threads are blocking on entry to a method (i.e. trying to acquire a lock), the order of acquisition can be non-deterministic. Remember also that a thread can only be in one of the methods at any one time - the synchronized methods allow only one thread to be executing (i.e. holding the lock of) any (synchronized) methods in the class. If the following sequence of events occurs - deadlock results:
STEP 1:
- P1 puts 1 char into the bufferSTEP 2:
- P2 attemptsput
- checks wait loop - already a char - waitsSTEP 3:
- P3 attemptsput
- checks wait loop - already a char - waitsSTEP 4:
- C1 attempts to get 1 char
- C2 attempts to get 1 char - blocks on entry to theget
method
- C3 attempts to get 1 char - blocks on entry to theget
methodSTEP 5:
- C1 is executing theget
method - gets the char, callsnotify
, exits method
- Thenotify
wakes up P2
- BUT, C2 enters method before P2 can (P2 must reacquire the lock), so P2 blocks on entry to theput
method
- C2 checks wait loop, no more chars in buffer, so waits
- C3 enters method after C2, but before P2, checks wait loop, no more chars in buffer, so waitsSTEP 6:
- NOW: there is P3, C2, and C3 waiting!
- Finally P2 acquires the lock, puts a char in the buffer, calls notify, exits methodSTEP 7:
- P2's notification wakes P3 (remember any thread can be woken)
- P3 checks the wait loop condition, there is already a char in the buffer, so waits.
- NO MORE THREADS TO CALL NOTIFY and THREE THREADS PERMANENTLY SUSPENDED!SOLUTION: Replace
notify
withnotifyAll
in the producer/consumer code (above).
举个例子,五个人等待某个东西,唤醒时醒了五个人,东西被第一个人拿走了,那么后面的人就拿不到东西,那么再加一个判断while判断就不会出问题。这种机制还可以用来处理伪唤醒(spurious wakeup),所谓伪唤醒就是no reason wakeup
,就是除了唤醒
和interrupt
之外的原因。
同时notify()方法的随机性,有可能导致死锁,看上面回答。
参考资料:
- https://www.cnblogs.com/jijijiefang/articles/7222955.html
- https://www.cnblogs.com/huangzejun/p/7908898.html
- https://www.cnblogs.com/java-spring/p/8309931.html
java 并发(三)---Thread 线程的更多相关文章
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程控制
在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期.这篇文章将深入讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态 ...
- Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Java并发编程:线程池的使用(转载)
转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- Java并发编程:线程池的使用(转载)
文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- [转]Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- 【转】Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- 牛客网提高组模拟赛第五场 T1同余方程(异或)(位运算)
区间不好做,但是我们可以转化成前缀来做.转化为前缀之后之后就是二维前缀和. 但是我还是不怎么会做.所以只能去看吉老师的题解 (确定写的那么简单真的是题解???). 我们要求模一个数余0,就等于找它的倍 ...
- libz.dylib框架怎么导入
1.General下 2.点击+号在弹出的对话框选择addother 3.在弹出的对话框中输入"cmd"+"shift"+"g" 输入/us ...
- c++实验4 栈及栈的应用+回文+中、后缀表达式
栈及栈的应用+回文+中.后缀表达式 1.栈顺序存储结构的基本操作算法实现 (1)栈顺序存储结构的类定义: class SeqStack { private: int maxsize; DataType ...
- 【sonar-block】Use try-with-resources or close this "BufferedInputStream" in a "finally" clause.
自己的理解: try-with-resources是JDK7的新语法结构,主要功能是自动关闭资源而不需要在finally里面手动close()关闭, 而且最重要的是,try块中的异常不会被close( ...
- bzoj2754:[SCOI2012]喵星球上的点名(后缀自动机)
Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串 ...
- JSR133规范学习
最近在看多线程相关的东西,通过阅读JSR133的faq来加深自己对多线程的理解,里面大部分的内容比较简单(越到后面越难),但是有的部分比较难以理解还没有完全弄懂,所以这里只记录了一下比较简单的阅读笔记 ...
- Swift和Objective C关于字符串的一个小特性
一.Unicode的一个小特性 首先,Unicode规定了许多code point,每一个code point表示一个字符.如\u0033表示字符"3",\u864e表示字符&qu ...
- ubuntu15.04下安装jdk8
前几天手贱,删掉了ubuntu自带的java,最后安装时遇到了Picked up JAVA_TOOL_OPTIONS的问题,经过网上各种找,终于被我弄成功了.下面将经验下载下面供大家方便: jdk8的 ...
- 如何高度自定义CollectionView的header和foot
最近在研究CollectionView,突然发现觉得他的HeaderSection和FootSection也可以高度自定义. 国外有详细的教程:http://www.appcoda.com/ios-c ...
- 大数据平台-java、mysql安装
补充: 对于ssh登录不是特定端口22的,进行文件修改 vim /etc/ssh/sshd_config Port 61333 简化后序命令输入,修改文件如下: 一.java环境安装 一共5台服务器 ...