1. 创建线程
Java的线程继承自Thread的类,处理一个名为run的方法。
而对于那些不能继承 Thread 的类,可以采取实现 Runnable 接口的方式进行。
2. 执行权转交
对于暂时交出执行权,Java 提供了 Thread.yield() 方法.
结果:
Java 也提供了 Thread.sleep(); 方法,但由于 Thread.sleep 可能被 interrupt( ) 方法中断,因此必须包含在 try{} 代码块中。
3. 优先级
Java 使用 setPriority( ) 方法调整优先级。
4. 背景线程
Java 使用 setDaemon() 方法。在 Java 中一般将背景线程称之为"守护线程(daemon thread)"。需要注意的是即便背景线程未结束,进程依然会终止。必须在线程启动前设置。
5. 线程等待
Java 中都使用 join/Join() 方法阻止调用线程,直到某个线程结束。不过 Java 里面情况还是要复杂一些。当某个线程处于 join 等待时,它可能会被 interrupt( ) 方法中断,因此也得放在 try{} 代码块中。
结果:
6. 资源锁定
Java 提供了 synchronized 关键字用来解决多线程资源共享锁定的问题,synchronized 可用于方法或者某个代码段。
当我们取消 synchronized 关键字时,我们会发现 t1 和 t2 交替执行。添加 synchronized 关键字以后,t2 会在 t1 执行完成后执行,因此对于方法的锁定是成功的。
结果:
另外,需要注意的是 synchronized 是全局锁定,也就是说对于静态方法而言,它们会锁定全部有此标记的方法(尽管它们不是同一个方法);而对于对象实例,它们会锁定同一对象全部有此标记的方法(尽管它们不是同一个方法)。
7. 原子操作
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行倒结束,中间不会有任何线程切换操作。
通常所说的Java原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。不过如果你在long或double前面加了volatile,那么它就肯定是原子操作了。
看下面的例子,虽然 return i是一个原子操作,但是从获得i的值到方法返回之间有可能被其他线程修改,因此不要主观的认为下面的操作是原子操作。我们还是有必要为其添加同步关键字synchronized。另外 ++i 和 --i 都不是原子操作,它们都涉及读和写两个步骤。
《Thinking in Java》中关于最安全的做法提出了如下的方针,不过我不完全赞同。
8. 线程协同
Java用对象锁来控制多线程安全,而所谓多线程协同无非是利用某种锁机制让线程暂时停顿下来,直到某个“信号”发生。为此,Java 将用来进行多线程协同的方法放在了根 Object 上,包括 wait(); notify(); notifyAll(); 。
调用 wait() / notify() / notifyAll() 之前必须先获取其对象锁,否则会抛出"IllegalMonitorStateException - current thread not owner" 异常。
输出
1
2
3
4
5
Notify Thread...
6
7
8
9
在上面这个例子中,当 i 等于 5 时,我们调用了对象锁 o 的 wait 方法阻塞线程。在 main 方法中我们循环检查对象状态,并适时发出信号结束等待。这个例子虽然很简单,但是由此可以处理多个线程之间的协同关系。当然,我们还可以给 wait 方法加一个参数,让其等待特定的时间,而不是上面例子中的无限制等待。
需要注意的是 wait() 会释放对象锁,sleep() 则不会。接着看下面的例子。
输出
t1 - 1
t1 - 2
t1 - 3
t1 - 4
t1 - 5
t2 - 1
t2 - 2
t2 - 3
t2 - 4
t2 - 5
9. 停止/中断线程
线程退出最好自己实现,在运行状态中一直检验一个状态,如果这个状态为真,就一直运行,如果外界更改了这个状态变量,那么线程就停止运行。
Java 不推荐使用 stop() / about() / suspend() / resume() 之类的方法来停止线程。原因包括:
当线程因某种原因处于阻塞等待状态时,我们就无法使用旗标终止它,那么改用interrupt()好了。
Java的线程继承自Thread的类,处理一个名为run的方法。
class
MyThread
extends
Thread
{
private int i = 0;
public void run() {
while (true) {
System.out.println(++i);
if (i > 100) break;
}
}
}
public class program {
public static void main(String[] args){
new MyThread().start();
}
}
private int i = 0;
public void run() {
while (true) {
System.out.println(++i);
if (i > 100) break;
}
}
}
public class program {
public static void main(String[] args){
new MyThread().start();
}
}
而对于那些不能继承 Thread 的类,可以采取实现 Runnable 接口的方式进行。
2. 执行权转交
对于暂时交出执行权,Java 提供了 Thread.yield() 方法.
class
MyThread
extends
Thread
{
public void run() {
int i = 0;
while (true) {
System.out.println( getName()+"-----------"+(++i));
if (i > 100) break;
yield();
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
public void run() {
int i = 0;
while (true) {
System.out.println( getName()+"-----------"+(++i));
if (i > 100) break;
yield();
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
结果:
t1-----------1
t2-----------1
t1-----------2
t2-----------2
t1-----------3
t2-----------3
t1-----------4
t2-----------4
t1-----------5
t2-----------5
.....
t2-----------1
t1-----------2
t2-----------2
t1-----------3
t2-----------3
t1-----------4
t2-----------4
t1-----------5
t2-----------5
.....
Java 也提供了 Thread.sleep(); 方法,但由于 Thread.sleep 可能被 interrupt( ) 方法中断,因此必须包含在 try{} 代码块中。
class
MyThread
extends
Thread
{
public void run() {
int i = 0;
while (true) {
System.out.printf("%s=%d\n", getName(), ++i);
if (i > 100) break;
try
{
sleep(0);
}
catch (InterruptedException e)
{
}
}
}
}
public class program {
public static void main(String[] args){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
public void run() {
int i = 0;
while (true) {
System.out.printf("%s=%d\n", getName(), ++i);
if (i > 100) break;
try
{
sleep(0);
}
catch (InterruptedException e)
{
}
}
}
}
public class program {
public static void main(String[] args){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
3. 优先级
Java 使用 setPriority( ) 方法调整优先级。
4. 背景线程
Java 使用 setDaemon() 方法。在 Java 中一般将背景线程称之为"守护线程(daemon thread)"。需要注意的是即便背景线程未结束,进程依然会终止。必须在线程启动前设置。
5. 线程等待
Java 中都使用 join/Join() 方法阻止调用线程,直到某个线程结束。不过 Java 里面情况还是要复杂一些。当某个线程处于 join 等待时,它可能会被 interrupt( ) 方法中断,因此也得放在 try{} 代码块中。
package tread;
class ThreadT extends Thread {
public boolean stopFlag = false;
public void run() {
int i = 0;
while (!stopFlag) {
System.out.println(++i);
yield();
}
System.out.println("MyThread over");
}
}
class JoinThread extends Thread {
public void run() {
ThreadT my = new ThreadT();
my.start();
try {
my.join();
} catch (InterruptedException e) {
my.stopFlag = true;
System.out.println("JoinThread InterruptedException");
}
}
}
public class JoinTest {
public static void main(String[] args) {
JoinThread join = new JoinThread();
join.start();
try {
join.sleep(100*5);
join.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class ThreadT extends Thread {
public boolean stopFlag = false;
public void run() {
int i = 0;
while (!stopFlag) {
System.out.println(++i);
yield();
}
System.out.println("MyThread over");
}
}
class JoinThread extends Thread {
public void run() {
ThreadT my = new ThreadT();
my.start();
try {
my.join();
} catch (InterruptedException e) {
my.stopFlag = true;
System.out.println("JoinThread InterruptedException");
}
}
}
public class JoinTest {
public static void main(String[] args) {
JoinThread join = new JoinThread();
join.start();
try {
join.sleep(100*5);
join.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
结果:
。。。。。。
6769
6770
6771
6772
6773
6774
6775
6776
6777
6778
JoinThread InterruptedException
MyThread over
6769
6770
6771
6772
6773
6774
6775
6776
6777
6778
JoinThread InterruptedException
MyThread over
6. 资源锁定
Java 提供了 synchronized 关键字用来解决多线程资源共享锁定的问题,synchronized 可用于方法或者某个代码段。
package
tread;
class Res {
String lock="";
synchronized static void test() {
for (int i = 0; i < 10; i++) {
System.out.println( Thread.currentThread().getName()+"========="+ i);
Thread.yield();
}
}
void test2() {
synchronized(lock){
for (int i = 0; i < 10; i++) {
System.out.println( Thread.currentThread().getName()+"========"+ i);
Thread.yield(); //没有放弃锁
}
}
}
}
class MyThreadA extends Thread {
public void run() {
new Res().test2();
}
}
public class SynTest {
public static void main(String[] args){
MyThreadA t1 = new MyThreadA();
MyThreadA t2 = new MyThreadA();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Res {
String lock="";
synchronized static void test() {
for (int i = 0; i < 10; i++) {
System.out.println( Thread.currentThread().getName()+"========="+ i);
Thread.yield();
}
}
void test2() {
synchronized(lock){
for (int i = 0; i < 10; i++) {
System.out.println( Thread.currentThread().getName()+"========"+ i);
Thread.yield(); //没有放弃锁
}
}
}
}
class MyThreadA extends Thread {
public void run() {
new Res().test2();
}
}
public class SynTest {
public static void main(String[] args){
MyThreadA t1 = new MyThreadA();
MyThreadA t2 = new MyThreadA();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
当我们取消 synchronized 关键字时,我们会发现 t1 和 t2 交替执行。添加 synchronized 关键字以后,t2 会在 t1 执行完成后执行,因此对于方法的锁定是成功的。
结果:
t1
========
0
。。。。
t1 ======== 7
t1 ======== 8
t1 ======== 9
t2 ======== 0
。。。
t2 ======== 8
t2 ======== 9
。。。。
t1 ======== 7
t1 ======== 8
t1 ======== 9
t2 ======== 0
。。。
t2 ======== 8
t2 ======== 9
另外,需要注意的是 synchronized 是全局锁定,也就是说对于静态方法而言,它们会锁定全部有此标记的方法(尽管它们不是同一个方法);而对于对象实例,它们会锁定同一对象全部有此标记的方法(尽管它们不是同一个方法)。
7. 原子操作
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行倒结束,中间不会有任何线程切换操作。
通常所说的Java原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。不过如果你在long或double前面加了volatile,那么它就肯定是原子操作了。
看下面的例子,虽然 return i是一个原子操作,但是从获得i的值到方法返回之间有可能被其他线程修改,因此不要主观的认为下面的操作是原子操作。我们还是有必要为其添加同步关键字synchronized。另外 ++i 和 --i 都不是原子操作,它们都涉及读和写两个步骤。
public int getValue() { return i; }
《Thinking in Java》中关于最安全的做法提出了如下的方针,不过我不完全赞同。
- 如果你要synchronize类的一个方法,索性把所有的方法全都synchronize了。要判断,哪个方法该synchronize,哪个方法可以不synchronize,通常是很难的,而且也没什么把握。
- 删除synchronized的时候要绝对小心。通常这么做是为了性能,但是synchronized的开销在JDK1.3和1.4里已经大为降低了。此外,只有在用profiler分析过,确认synchronized确实是瓶颈的前提下才能这么作。
8. 线程协同
Java用对象锁来控制多线程安全,而所谓多线程协同无非是利用某种锁机制让线程暂时停顿下来,直到某个“信号”发生。为此,Java 将用来进行多线程协同的方法放在了根 Object 上,包括 wait(); notify(); notifyAll(); 。
调用 wait() / notify() / notifyAll() 之前必须先获取其对象锁,否则会抛出"IllegalMonitorStateException - current thread not owner" 异常。
package
tread;
class MyThread extends Thread {
static Object o = new Object();
public void run() {
synchronized (o) {
int i = 0;
while (++i < 10) {
System.out.println(i);
try
{
if (i == 5) o.wait();
}
catch (InterruptedException e)
{
}
}
}
}
}
public class SynTest2 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
while (t1.getState() != Thread.State.WAITING)
{
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
}
}
System.out.println("Notify Thread");
synchronized(MyThread.o)
{
MyThread.o.notify();
}
}
}
class MyThread extends Thread {
static Object o = new Object();
public void run() {
synchronized (o) {
int i = 0;
while (++i < 10) {
System.out.println(i);
try
{
if (i == 5) o.wait();
}
catch (InterruptedException e)
{
}
}
}
}
}
public class SynTest2 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
while (t1.getState() != Thread.State.WAITING)
{
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
}
}
System.out.println("Notify Thread");
synchronized(MyThread.o)
{
MyThread.o.notify();
}
}
}
输出
1
2
3
4
5
Notify Thread...
6
7
8
9
在上面这个例子中,当 i 等于 5 时,我们调用了对象锁 o 的 wait 方法阻塞线程。在 main 方法中我们循环检查对象状态,并适时发出信号结束等待。这个例子虽然很简单,但是由此可以处理多个线程之间的协同关系。当然,我们还可以给 wait 方法加一个参数,让其等待特定的时间,而不是上面例子中的无限制等待。
需要注意的是 wait() 会释放对象锁,sleep() 则不会。接着看下面的例子。
class MyThread extends Thread {
static Object o = new Object();
public void run() {
synchronized (o) {
int i = 0;
while (++i < 10) {
System.out.printf("%s - %d\n", Thread.currentThread().getName(), i);
try
{
if (i >= 5) o.wait();
}
catch (InterruptedException e)
{
}
}
}
}
}
public class Program {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
static Object o = new Object();
public void run() {
synchronized (o) {
int i = 0;
while (++i < 10) {
System.out.printf("%s - %d\n", Thread.currentThread().getName(), i);
try
{
if (i >= 5) o.wait();
}
catch (InterruptedException e)
{
}
}
}
}
}
public class Program {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
输出
t1 - 1
t1 - 2
t1 - 3
t1 - 4
t1 - 5
t2 - 1
t2 - 2
t2 - 3
t2 - 4
t2 - 5
9. 停止/中断线程
线程退出最好自己实现,在运行状态中一直检验一个状态,如果这个状态为真,就一直运行,如果外界更改了这个状态变量,那么线程就停止运行。
Java 不推荐使用 stop() / about() / suspend() / resume() 之类的方法来停止线程。原因包括:
- 在调用这些方法的时候,我们不能确定线程方法执行到何处,是否完成了特定的逻辑。
- 这些方法会让线程无法正确释放对象锁,可能造成死锁。
- 使用旗标(flag)。
- 调用 interrupt()。
class MyThread extends Thread {
public boolean stopFlag = false;
public void run() {
while (!stopFlag) {
try
{
System.out.println("Thread running, " + System.currentTimeMillis());
Thread.sleep(50);
}
catch (Exception e)
{
}
}
System.out.println("Thread Stop...");
}
}
public class Program {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try
{
Thread.sleep(100);
}
catch (Exception e)
{
}
t.stopFlag = true;
}
}
public boolean stopFlag = false;
public void run() {
while (!stopFlag) {
try
{
System.out.println("Thread running, " + System.currentTimeMillis());
Thread.sleep(50);
}
catch (Exception e)
{
}
}
System.out.println("Thread Stop...");
}
}
public class Program {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try
{
Thread.sleep(100);
}
catch (Exception e)
{
}
t.stopFlag = true;
}
}
当线程因某种原因处于阻塞等待状态时,我们就无法使用旗标终止它,那么改用interrupt()好了。
class MyThread extends Thread {
public void run() {
try
{
while (true) {
System.out.println("Thread running, " + System.currentTimeMillis());
synchronized (this) {
wait();
}
}
}
catch (Exception e)
{
}
System.out.println("Thread Stop...");
}
}
public class Program {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try
{
while (t.getState() != Thread.State.WAITING) {
Thread.sleep(500);
}
t.interrupt();
}
catch (Exception e)
{
}
}
}
public void run() {
try
{
while (true) {
System.out.println("Thread running, " + System.currentTimeMillis());
synchronized (this) {
wait();
}
}
}
catch (Exception e)
{
}
System.out.println("Thread Stop...");
}
}
public class Program {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try
{
while (t.getState() != Thread.State.WAITING) {
Thread.sleep(500);
}
t.interrupt();
}
catch (Exception e)
{
}
}
}