Java中的多线程

线程定义

  • 目前Java程序都是串行执行的,也就是每个语句依次执行;
  • 有时候需要程序并行执行:
    1. 有时系统有多个工作要同时完成:例如一边打字、一边听歌;一边开视频会议、一边看网页;
    2. 利用CPU多线程的特性。
  • 一个进程可以有多个线程在执行。

创建线程的方法

Method1

通过继承Thread类创建线程

注意启动线程用start()方法而不能用run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//ch09.TestThread1
package ch09;
/**
* 多线程的例子,注意运行程序时候的输出
*/
public class TestThread1 {
public static void main(String args[]) {
Thread t = new MyThread1(100);
t.start();
System.out.println("Main thead end!");
}
}
class MyThread1 extends Thread {
private int n;
public MyThread1(int n) {
this.n = n;
}
public void run() {
for (int i = 0; i < n; i++) {
System.out.print(" " + i);
if ((i + 1) % 20 == 0) {
System.out.println("");
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

Method2

通过向Thread()构造方法传递Runnable对象来创建线程

注意启动线程用start()方法而不能用run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//ch09.TestThread2
package ch09;
/**
* 多线程的例子
*/
public class TestThread2 {
public static void main(String args[]) {
MyThread2 mytask = new MyThread2(100);
Thread t = new Thread(mytask);
t.start();
}
}
class MyThread2 implements Runnable {
private int n;
public MyThread2(int n) {
this.n = n;
}
public void run() {
for (int i = 0; i < n; i++) {
System.out.print(" " + i);
if ((i + 1) % 20 == 0) {
System.out.println("");
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

两种方法比较

  • 使用Runnable接口;
  • 直接继承Thread类;
  • 无论使用哪种方法,注意启动线程用start()方法而不能用run()方法。

多线程的实例

Example1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//ch09.sync.sum.SumMain.java
package ch09.sync.sum;
public class SumMain {
public static void main(String[] args) {
// TODO Auto-generated method stub
int threadCount = 100;
double sum = 0;
long mill=System.currentTimeMillis();
SumThread[] threads = new SumThread[threadCount];
long start = 0;
long end = 1000000000L;
for (int i = 0; i < threads.length; i++) {
long threadStart = start + (end - start) / threadCount * i;
long threadEnd = start + (end - start) / threadCount * (i + 1);
threads[i] = new SumThread(threadStart, threadEnd);
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
try {
threads[i].join();
sum = sum + threads[i].getSum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println((System.currentTimeMillis()-mill)+" ms cost!");
System.out.println("sum=" + sum);
}
}
  • thread.join(),等待这个线程结束;
  • 多线程可以显著提升系统性能;
  • 比较SumMain中1/2/3/4/5个线程时消耗的时间。

Example2(ridiculous!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//ch09.join.MySort.java
package ch09.join;
import java.util.ArrayList;
import java.util.List;
/**
* 一种奇葩的排序算法
*/
public class MySort extends Thread {
private List<Integer> list;
private int value;
public MySort(List<Integer> list, int value) {
this.list = list;
this.value = value;
}
@Override
public void run() {
try {
this.sleep(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(value);
}
public static void main(String[] args) {
int[] arrays = { 1230, 571, 340, 454, 1, 30, 60, 300, 89, 1999, 765 };
List<Integer> list = new ArrayList<Integer>();
MySort[] threads = new MySort[arrays.length];
for (int i = 0; i < arrays.length; i++) {
threads[i] = new MySort(list, arrays[i]);
}
for (int i = 0; i < arrays.length; i++) {
threads[i].start();
}
for (int i = 0; i < arrays.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+",");
}

}
}

Daemon线程和非Daemon线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//ch09.TestThreadDaemon.java
package ch09;
/**
* Daemon线程和非Daemon线程的区别
*/
public class TestThreadDaemon {
public static void main(String args[]) {
Thread t1 = new MyThread();
// 尝试一下false
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("main thread end");
}
}
class MyThread extends Thread {
static int id = 0;
MyThread() {
id++;
}
public void run() {
System.out.println("Start");
for (int i = 0; i < 100; i++) {
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
// yield();
}
}
}
/*
* 9.2.4 Daemon线程
* 线程有两种,一类是Daemon线程,一类是非Daemon线程。在Java程序中,若还有非Demon线程,则整个程序就不会结束;而Daemon线程,
* 可以在整个程序结束后继续运行,所以Demon线程可以用于后台服务程序。
* 通过调用isDaemon(),可检查一个线程是不是一个Daemon;用setDaemon (boolean
* flg)方法可以将一个线程设为Daemon线程。在一个Daemon线程中创建的子线程,也自动是Daemon线程。
*
*/
  • Thread.setDaemon(true/false);
  • Deamon线程→主程序终止,线程终止;
  • 非Deamon线程→主程序终止,线程不终止。

多线程访问的冲突问题

解决方法

因为多线程的原因,同一个对象,同一个时刻可能有多个线程访问,进而导致冲突

解决方式:使用synchronized

可加在方法、对象、类上

1
2
//ch09.counter0.SyncCounter0.java
//ch09.sync.stack0.MyStackUserThread

解决了多线程访问冲突之后的例子

1
2
//ch09.counter1.SyncCounter1.java
//ch09.sync.stack1.MyStackUserThread

synchronized带来的死锁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//ch09.lock.DeadLockTest
package ch09.lock;
/**
* 死锁的例子
*/
public class DeadLockTest {
public static void main(String args[]) {
Operator o1 = new Operator();
Operator o2 = new Operator();
o1.anotherOperator = o2;
o2.anotherOperator = o1;
Thread t1 = new Thread(o1);
Thread t2 = new Thread(o2);
t1.start();
t2.start();
}
}
//ch09.lock.Operator.java
package ch09.lock;
/**
*/
public class Operator implements Runnable {
Operator anotherOperator;
synchronized public void methodA(int depth) {
System.out.println(Thread.currentThread().getName() + ":begin methodA");
if(depth<=0) {
return;
}
try {
Thread.sleep(100);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ":call another methodA");
anotherOperator.methodA(--depth);
System.out.println(Thread.currentThread().getName() + ":end methodA");
}
public void run() {
methodA(1);
}
}

由于Operator中定义的methodA方法有synchronized修饰,所以当o1i调用方法的同时需要o2也调用该方法,但是o2在等待o1,造成了相互等待的情况,即死锁。反之亦然

死锁

定义

  • 当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁;
  • Java不监测也不视图避免这种情况。因而保证不发生死锁就成了程序员的责任。

Dead Lock

实例

1
//ch09.lock.DeadLockTest

避免死锁

  • 控制锁的范围;
  • 按序分配资源。

程序的状态与生命周期

  • 在一个线程的生命周期中,它总处于某一种状态中;
  • 线程的状态表示了线程正在进行的活动以及在这段时间内线程能完成的任务。

程序的状态与生命周期

3

多线程的控制

生产者·消费者问题

  • 不断使用某类资源的线程称为消费者;
  • 不断产生或释放同类资源的线程称为生产者;
  • 通过协调生产者和消费者的关系,保证生产的东西及时消费掉,也保证消费者总能够有资源可使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
//ch09.producer_consumer.ProducerConsumerDemo.java
package ch09.producer_consumer;
/**
* 一个生产者、消费者模型的例子
*/
public class ProducerConsumerDemo {
public static void main(String args[]) {
Stack stack = new Stack("stack1");
new Producer(stack, "producer1").start();
new Consumer(stack, "consumer1").start();
}
}
//producer和consumer的定义在同一个包下

线程中常用方法

notify()/notifyAll()

  • notify():用来选择并唤醒等候进入监视器的线程;
  • notifyAll():唤醒所有等待的线程;
  • 只有获得锁以后,才有权力调用notify()/notifyAll()方法。

wait()方法

wait()方法使当前线程处于等待状态,直到别的线程调用notify()方法来通知/唤醒它

如何终止线程

  • 当线程执行完run()方法,它将自然终止运行;
  • Thread有一个stop()方法,可以强制结束线程,但这种方法是不安全的。因此,目前stop()方法已经被废弃;
  • 实际编程中,一般是定义一个标志变量,然后通过程序来改变标志变量的值,从而控制线程从run()方法中自然退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//ch09.ThreadTerminateByFlag.java
package ch09;
import java.util.*;
public class ThreadTerminateByFlag {
public static void main(String args[]) {
Timer timer = new Timer();
Thread thread = new Thread( timer );
thread.setName( "Timer" );
thread.start();
for( int i=0; i<100; i++ ){
System.out.print("\r" + i );
try{
Thread.sleep(100);
}catch( InterruptedException e ){}
}
timer.stopRun();
}
}
class Timer implements Runnable {
boolean flg = true;
public void run() {
while(flg){
System.out.print( "\r\t" + new Date() + "..." );
try{
Thread.sleep(1000);
}catch( InterruptedException e ){}
}
System.out.println( "\n" + Thread.currentThread().getName() + " Stop" );
}
public void stopRun(){
flg = false;
}
}

定时器:Timer&TimerTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//ch09.TimerTaskDemo
package ch09;
import java.util.Timer;
import java.util.TimerTask;
/**
* timertask例子
*/
public class TimerTaskDemo {
public TimerTaskDemo() {
// TODO Auto-generated constructor stub
}
// public static void main(String[] args) {
// Timer timer = new Timer();
// timer.schedule(new MyTimerTask(), 0, 1000L);
// }
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
int i = 0;
public void run() {
System.out.println(i++);
}
}, 0, 1000L);
}
}
//ch09.TimerTaskDemo2

进程的优先级

Thread.setPriority(Thread.MAX_PRIORITY/NORM_PRIORITY/MIN_PRIORITY);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//ch09.TestThreadPriority.java
package ch09;
/**
* 本例子显示了不同的优先级对线程的影响
*/
public class TestThreadPriority {
public static void main(String args[]) {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunner(i));
if (i % 3 == 0) {
threads[i].setPriority(Thread.MAX_PRIORITY);
} else if (i % 3 == 1) {
threads[i].setPriority(Thread.NORM_PRIORITY);
} else {
threads[i].setPriority(Thread.MIN_PRIORITY);
}
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
}
class MyRunner implements Runnable {
int id;
MyRunner(int id) {
this.id = id;
}
public void run() {
double t = 0;
for (int j = 0; j < 100000000; j++) {
t = t + j;
// if (j % 100 == 0) {
// Thread.currentThread().yield();
// }
}
System.out.println(id + " finished!");
}
}

ThreadLocal

  • 如何在不传递参数的情况下,让一个线程实用程序外的一个变量?
  • ThreadLocal是一个线程内部的存储类;
  • 线程间互不干扰。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//ch09.thread_local.MyThreadLocal.java
package ch09.thread_local;
/**
* ThreadLocal 变量
*/
public class MyThreadLocal extends Thread {
public void run() {
for(int i=0;i<5;i++){
try {
Thread.currentThread().getName();
String value=this.getName()+" "+i;
MyValue.getSession().set(value);
Thread.sleep(1000);
MyFunction.print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new MyThreadLocal().start();
}
}
}

文章源码

文章源码 备用仓库