多线程机制
一、线程简介
1、线程与进程
每个进程都具有独立的代码和数据空间,进程间的切换会有较大的开销。线程是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
多进程:在操作系统中能同时运行多个任务(程序)
多线程:在同一应用程序中有多个顺序流同时执行
2、线程的应用
二、线程状态控制
线程具有创建、就绪、运行、阻塞、终止,五种状,详细的状态转换如下图所示:
1、线程的创建与启动
JVM启动时会有一个由主方法所定义的线程,程序员可以通过实现 Runable接口的类 或 Thread类 的实例创建新的线程,每个线程对象都是通过方法run()来完成其操作,通过start()方法来启动一个线程。
(1)定义线程类实现Runable接口【建议】
提供共享的数据(线程同步问题),另外,在实现Runable接口的类的run方法定义中可以使用Thread的静态方法。
1 public class Main {
2 public static void main(String args[]) {
3 Runner1 r = new Runner1();
4 Thread t = new Thread(r);
5
6 t.start();//r.run()是方法调用,而非线程启动
7
8 for(int i=0; i<100; i++) {
9 System.out.println("Main Thread:------" + i);
10 }
11 }
12 }
13
14 //同一个Runable可以创建多个Thread
15 class Runner1 implements Runnable {
16 public int a = 0;
17 public void run() {//定义线程体
18 for(int i=0; i<100; i++) {
19 System.out.println("Runner1 :" + i);
20 }
21 }
22 }
(2)定义Thread的子类,并重写run()方法
1 public class Main {
2 public static void main(String args[]) {
3
4 Runner1 r = new Runner1();
5 r.start();
6
7 for(int i=0; i<100; i++) {
8 System.out.println("Main Thread:------" + i);
9 }
10 }
11 }
12
13 //Thread类已经实现了Runable接口
14 class Runner1 extends Thread {
15 public void run() {
16 for(int i=0; i<100; i++) {
17 System.out.println("Runner1 :" + i);
18 }
19 }
20 }
21
(3)如何关闭一个线程
1 public class Main {
2 public static void main(String args[]){
3 Runner r = new Runner();
4 Thread t = new Thread(r);
5 t.start(); //线程启动
6
7 for(int i=0;i<10;i++){
8 if(i%2==0 & i>0)
9 System.out.println("in thread main i=" + i);
10 }
11 System.out.println("Thread main is over");
12
13 //stop()与interrupt()会立即关闭线程,造成正在打开的资源无法关闭,不建议使用
14 r.shutDown();
15 }
16 }
17 //设置flag标志位,表示线程是否关闭
18 class Runner implements Runnable {
19 private boolean flag=true;
20
21 public void run() {
22 int i = 0;
23 while (flag==true) {
24 System.out.println(" " + i++);
25 }
26 }
27
28 public void shutDown() {
29 flag = false;
30 }
31 }
2、线程的基本操作
线程控制基本方法如下:
(1)线程优先级
JAVA提供一个线程调度器来监视程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定调度那个线程来执行。线程的优先级用户数字表示,范围从1到10,线程的缺省优先级是5。
Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5
Thread.MAX_PRIORITY = 10
1 public class Main {
2 public static void main(String[] args) {
3 Thread t1 = new Thread(new T1());
4 Thread t2 = new Thread(new T2());
5
6 System.out.println(t1.getPriority()); //获取线程对象的优先级
7 t1.setPriority(Thread.NORM_PRIORITY + 3);//设置线程对象的优先级
8 t1.start();
9 t2.start();
10 }
11 }
12
13 class T1 implements Runnable {
14 public void run() {
15 for(int i=0; i<100; i++) {
16 System.out.println("T1: " + i);
17 }
18 }
19 }
20
21 class T2 implements Runnable {
22 public void run() {
23 for(int i=0; i<100; i++) {
24 System.out.println("------T2: " + i);
25 }
26 }
27 }
(2)线程wait与sleep方法
wait是Object的方法,调用wait方法时必须锁定对象,wait时别的线程可以访问锁定的对象,wait需要notify或notifyALL方法唤醒。
sleep是Thread静态方法,sleep时别的方法不能访问锁定的对象,sleep方法等睡眠时间到了,可以自己苏醒。
1 import java.util.*;
2 public class Main {
3 public static void main(String[] args) {
4 MyThread thread = new MyThread();
5 thread.start();//每隔一秒输出一次时间
6
7 try {
8 //在未继承Thread类的方法在可以调用Thread的静态方法sleep暂停进程
9 Thread.sleep(10000);
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }//主线程等待10s
13
14 thread.interrupt();//不建议使用interrupt和stop
15 }
16 }
17
18 class MyThread extends Thread {
19 boolean flag = true;
20 public void run(){
21 while(flag){
22 //若调用该线程的主线程还“活着”
23 if(Thread.currentThread().isAlive()) {
24 System.out.println("==="+new Date()+"===");
25 }
26
27 try {
28 //public static sleep(long millis)throw InterruptedException
29 //使得当前线程休眠,暂停执行millis毫秒
30 sleep(1000); //1000毫秒,即1s
31 } catch (InterruptedException e) {
32 //重写的方法不能抛出比被重写的方法不同的异常
33 //此处只能写try catch,不能写throws
34 return;
35 }
36 }
37 }
38 }
(3)线程合并
1 public class Main {
2 public static void main(String[] args) {
3 MyThread2 t1 = new MyThread2("myThread");
4 t1.start();
5 try {
6 t1.join();
7 } catch (InterruptedException e) {}
8
9 for(int i=1;i<=3;i++){
10 System.out.println("i am main thread");
11 }
12 }
13 }
14 class MyThread2 extends Thread {
15 MyThread2(String s){
16 super(s);//设置当前线程名为 s
17 }
18
19 public void run(){
20 for(int i =1;i<=3;i++){
21 System.out.println("i am "+getName());//输出线程名
22 }
23 }
24 }//线程合并结果,相当于方法调用
三、线程同步与死锁
1、线程同步
Java引入了对象互斥锁的概念,使用synchronized修饰符修饰方法和代码块,表明在某一时间段内,只能有一个线程访问被锁住的同步对象或同步方法,以保证共享数据的完整性;但其他线程仍可以访问没有锁定的方法,所以,要想保证数据同步,需将所有改变该属性值的方法都加锁,但锁加的越多,执行效率会被降低。建议对于读属性的方法无需加锁 。
1 public class Main implements Runnable {
2
3 private static int num = 100;
4
5 public static void main(String[] args) throws Exception {
6 Main test = new Main();
7 Thread t1 = new Thread(test);
8 Thread t2 = new Thread(test);
9 //设置线程名字
10 t1.setName("t1");
11 t2.setName("t2");
12 //启动线程
13 t1.start();
14 t2.start();
15
16 // test.m2();//使t1和t2同步
17 // test.m3();//使t1、t2和主线程 同步
18 // System.out.println("Main"+" : "+(num));
19 }
20 //在某一时间段,保证只有一个线程访问被锁住的方法
21 public synchronized void m1(){
22 //若不使用死锁,num++和num输出 的原子性过程可能会打断,结果是都是102或101
23 num++;
24 System.out.println(Thread.currentThread().getName() +": "+ num);
25 }
26
27 //其他线程仍可以访问没有锁定的方法,导致数据有可能不同步
28 public void m2() {
29 num++;
30 }
31
32 //要想保证数据同步,需将所有改变该属性值的方法都加锁
33 public void m3() {
34 synchronized (this) {//互斥锁
35 num++;
36 }
37 }
38
39 public void run() {
40 try {
41 m1();
42 } catch(Exception e) {
43 e.printStackTrace();
44 }
45 }
46 }
2、线程死锁
(1)死锁实例
1 public class Main implements Runnable {
2 public int flag = 1;
3 static Object o1 = new Object(), o2 = new Object();
4
5 public static void main(String[] args) {
6 Main td1 = new Main();
7 Main td2 = new Main();
8 td1.flag = 1;
9 td2.flag = 0;
10 Thread t1 = new Thread(td1);
11 Thread t2 = new Thread(td2);
12 t1.start();
13 t2.start();
14 }
15
16 public void run() {
17 System.out.println("flag=" + flag);
18 if(flag == 1) {
19 synchronized(o1) {
20 try {
21 Thread.sleep(500);
22 } catch (Exception e) {
23 e.printStackTrace();
24 }
25 synchronized(o2) {
26 System.out.println("1");
27 }
28 }
29 }
30 if(flag == 0) {
31 synchronized(o2) {
32 try {
33 Thread.sleep(500);
34 } catch (Exception e) {
35 e.printStackTrace();
36 }
37 synchronized(o1) {
38 System.out.println("0");
39 }
40 }
41 }
42 }
43 }
四、经典同步问题
1、生产者消费者
1
2 public class Main {
3 public static void main(String[] args) {
4 SyncStack ss = new SyncStack();
5
6 //生成者
7 Producer p = new Producer(ss);
8 new Thread(p).start();
9
10 //消费者
11 Consumer c = new Consumer(ss);
12 new Thread(c).start();
13 }
14 }
15
16 class WoTou {
17 int id;
18 WoTou(int id) {
19 this.id = id;
20 }
21 public String toString() {
22 return "WoTou : " + id;
23 }
24 }
25
26 //支持多线程同步操作的堆栈的实现
27 class SyncStack {
28 private int index = 0;
29 private WoTou[] arrWT = new WoTou[6];
30
31 public synchronized void push(WoTou wt) {
32 while(index == arrWT.length) {
33 try {
34 //Object的wait只能在synchronized修饰的方法中使用
35 //让当前正在访问的线程wait
36 this.wait();
37 } catch (InterruptedException e) {
38 e.printStackTrace();
39 }
40 }
41 this.notifyAll();//唤醒所有线程,此处也可使用notify()唤醒一个线程;
42
43 arrWT[index] = wt;
44 index ++;
45 }
46
47 public synchronized WoTou pop() {
48 //不能写if,在发生异常后还需在判断index值
49 while(index == 0) {
50 try {
51 this.wait();
52 } catch (InterruptedException e) {
53 e.printStackTrace();
54 }
55 }
56 this.notifyAll();// notify();
57 index--;
58 return arrWT[index];
59 }
60 }
61
62 class Producer implements Runnable {
63 SyncStack ss = null;
64 Producer(SyncStack ss) {
65 this.ss = ss;
66 }
67
68 public void run() {
69 for(int i=0; i<20; i++) {
70 WoTou wt = new WoTou(i);
71 ss.push(wt);
72 System.out.println("生产了:" + wt);
73
74 try {
75 Thread.sleep((int)(Math.random() * 200));
76 } catch (InterruptedException e) {
77 e.printStackTrace();
78 }
79 }
80 }
81 }
82
83 class Consumer implements Runnable {
84 SyncStack ss = null;
85 Consumer(SyncStack ss) {
86 this.ss = ss;
87 }
88
89 public void run() {
90 for(int i=0; i<20; i++) {
91 WoTou wt = ss.pop();
92 System.out.println("消费了: " + wt);
93 try {
94 Thread.sleep((int)(Math.random() * 1000));
95 } catch (InterruptedException e) {
96 e.printStackTrace();
97 }
98 }
99 }
100 }
2、银行家算法