0%

6.30 线程

2017年6月30日 上午10:31

多线程的好处:
解决了多部分代码同时运行的情况
坏处:
每个线程占的资源少,单个线程的效率比较低

实现

1.     继承Thread
    1. run()运行代码
    2. start()开启线程:没有开始运行,进入了
    3. 一个线程类对象只能调用一次start()方法


图1 线程的四种状态(五种:创建,就绪,运行,阻塞,停止)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package package1;

public class ThreadTest extends Thread{
private String name;
//票的总数
private int num = 5;

public ThreadTest(String name){
this.name = name ;
}
public void run(){
while(num != 0){
System.out.println(name+"\t"+num--);
}
}
public static void main(String[] args) {
//创建了两个资源
ThreadTest tt = new ThreadTest("线程1");
ThreadTest tt1 = new ThreadTest("线程2");
//开启两个线程
tt.start();
tt1.start();
}
}

输出:

2. 接口Runnable     
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
package package1;

public class RunnableTest implements Runnable{
private String name ;
private int num =10;
public RunnableTest(String name) {
super();
this.name = name;
}

public static void main(String[] args) {
//创建资源
RunnableTest rt = new RunnableTest("线程1");
//同一个资源开两个线程
new Thread(rt).start();
new Thread(rt).start();
}

@Override
public void run() {
// TODO Auto-generated method stub
while(num != 0){
System.out.println(name+"\t"+num--);
}
}

}

输出1

输出2

对比 输出1 和 输出2 发现:

1. 输出1 一共输出的为11个,10输出了两次
2. 输出2 一共输出了10个,1-10各一次

总结:会有特殊情况出现

对比 程序1 和 程序2 发现:

1. 实现Runnable接口,方便实现资源共享
2. 继承Thread类受单继承影响,不适合多线程共享资源    
3. 只有Thread才能创建线程对象,Runnable只是实现run()方法
4. **多线程的特点是:**
            1. 一个对象的run()可以同时执行
            2. 多个对象的run()方法也可以同时执行


图2:Thread实现方式
Thread 的角色:提供资源 + 提供方法 + 运行方法

图3:Runnable实现方式
RunnableTest的角色:提供资源 + 提供方法run
Thread的角色:调用run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package package1;

public class ThreadTest extends Thread{
private String name;
//票的总数
private int num = 5;

public ThreadTest(String name){
this.name = name ;
}
public void run(){
while(num != 0){
System.out.println(name+"\t"+num--);
}
}
public static void main(String[] args) {
ThreadTest tt = new ThreadTest("线程1");
//调用两次start,来模仿Thread和Runnable的关系
tt.start();
tt.start();
}
}

输出:

通过这个例子来理解Runnable 和 Thread的不同:

1. Thread的run方法不能让同一个对象同时调用进行,但,可以不同对象之间进行
2. Runnable 的run方法可以被同一个对象同时调用
3. 那么,在thread中,如果强制调用tt.start()两次,就类似于runnable调用两次start(),会发生什么呢?很可惜,因为start()方法是synchronized的(如图5),所以只能支持Runnable的两次(图7),不支持Thread的两次(图6)。


图5

图6

图7

Thread的方法:

  1. Static Thread currentThread()
  2. Final String getName()
  3. Final void setPriority();设置优先级
  4. Void start()
  5. Static void sleep() 让出执行权,不让出资源
  6. Final void yield()暂停当前线程,让其他线程(大家同一个起跑线) 礼让
  7. void run()执行线程

线程方法 + object方法

同步的前提:

  1. 两个或两个以上的线程
  2. 多个线程使用同一资源
    1. (必须用Runnable)或者 (Thread+ static资源变量)

同步的概念:

  1. 回去判断每个线程上的锁,浪费资源
  2. 同一个时间只能运行一个线程
  3. 同步可以解决线程中的安全问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package package1;

public class ThreadTest2 extends Thread {
private String name ;
public ThreadTest2(String name) {
this.name = name;
}
public static void main(String[] args) {
ThreadTest2 tt = new ThreadTest2("线程");
tt.start();
}
@Override
public void run(){
for(int i = 0 ;i < 4;i++){
System.out.println(i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread.currentThread().yield();
}
}
}

以上代码使用
1. Thread.currentThread ().sleep 实现休眠
2. Thread.currentThread().yield() 实现礼让

同步代码块 同步方法

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
package package1;

public class RunnableTest2 implements Runnable {
private int num = 10 ;
@Override
public void run() {
//这里要死循环 或者 循环判断
while(num != 0){
synchronized (RunnableTest2.class) {
if(num > 0){
System.out.println(Thread.currentThread().getName()+"\t"+num--);
}
}
// sail();//两种方法的交换
}

}
public synchronized void sail(){
if(num > 0){
System.out.println(Thread.currentThread().getName()+"\t"+num--);
}
}
public static void main(String[] args) {
RunnableTest2 rt = new RunnableTest2();
new Thread(rt).start();
new Thread(rt).start();
new Thread(rt).start();
new Thread(rt).start();

}
}

同步代码块 同步方法的区别:

1. 上面的代码包含的两种方法,通过注释来更换
2.  他们两种没有功能上的区别,可以相互替换
3. 写成方法可以更加灵活,因为可以分块写,不用写成一块了

同步的死锁:


理解:
1. 爸:我要你的成绩单
2. 儿:我要玩具
3. 爸:你先给我成绩单!
4. 儿:不!你先给我玩具!
5. 两个人怒目相对,谁都不给谁,就相互看着…….

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
48
49
50
51
52
53
54
55
56
57
58
package package1;

public class LockRunnable implements Runnable {
private int flag = 1;
/*
* 这里是static,原因:
* 我在执行的时候初始化了两个LockRunnable对象,那么就会生成两套资源
* 也就是有 两个成绩单 和 两个玩具
* 所以,要通过static来保证只有 一个成绩单 和 一个玩具
*/
static Object o1 = new Object();//表示玩具
static Object o2 = new Object();//表示成绩单

@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(flag);//如果1 表示家长
if(flag == 1){
synchronized (o1) {//我现在要占用o1了,很幸运还在,我就把它拿走了,然后我接的执行
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (o2) {//我现在要占用o2了,但是发现不在有人拿着,我就等着!
System.out.println("可以给你玩具了");
}
}
}

if(flag == 0){
synchronized (o2) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (o1) {
System.out.println("可以给成绩单");
}
}
}
}

public static void main(String[] args) {

//这里是两个资源对象,所以要声明称static类型
LockRunnable t1 = new LockRunnable();
t1.flag =1 ;
LockRunnable t2 = new LockRunnable();
t2.flag =0 ;
new Thread(t1).start();
new Thread(t2).start();
}

}

在上面的代码中注意:

1. 正确理解synchronize的意思:
    1. **用在方法时,这个方法不能被同一个对象同时调用**
    2. 用在块时,()里的为需要站用的资源,根据这个进行判断
2.  为什么要使用static
3. 注意这里产生了两个LockRunnable资源对象,一个代表父亲,一个代表儿子