0%

多线程

线程与进程

1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2 进程:执行中的程序
一个进程至少包含一个线程

3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程

4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源

线程的实现

继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}

class MyThread extends Thread{
private String name;

public MyThread(String name){
this.name = name;
}

@Override
public void run() {
System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
}
}

实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}

@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}

静态方法

1.currentThread()

1
2
3
4
5
6
public class Test2 {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
System.out.println("主线程名字:"+Thread.currentThread().getName());
}
}

2.sleep()

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
public class Test2 {

private int i = 10;
private Object object = new Object();

public static void main(String[] args) throws IOException {
Test2 test = new Test2();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.start();
thread2.start();
}

class MyThread extends Thread{
@Override
public void run() {
synchronized (object) {
i++;
System.out.println(Thread.currentThread().getName()+"===> i:"+i);
try {
System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
i++;
System.out.println("i:"+i);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
Thread-0===> i:11
线程Thread-0进入睡眠状态
//这里会等待3000毫秒
线程Thread-0睡眠结束
i:12

Thread-1===> i:13
线程Thread-1进入睡眠状态
//这里会等待3000毫秒
线程Thread-1睡眠结束
i:14

3.yield

  • 调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程
  • 但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
  • 注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyThread  extends Thread{
@Override
public void run() {
long beginTime=System.currentTimeMillis();
int count=0;
for (int i=0;i<50000000;i++){
count=count+(i+1);
//Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("用时:"+(endTime-beginTime)+" 毫秒!");
}
}

public class Run {
public static void main(String[] args) {
MyThread t= new MyThread();
t.start();
}
}
1
2
注释======>用时:23毫秒!
取消注释==>用时:4890 毫秒!

对象方法

start()

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

run()

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

getId()

1
2
3
4
5
6
7
public class Run{
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
System.out.println(thread.getId());
}
}
1
2
main
1

isAlive()

方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread thread2 = new MyThread("_2");
System.out.println(thread2.isAlive());
thread2.start();
Thread.sleep(1000);
System.out.println(thread2.isAlive());
}
}

class MyThread extends Thread{
private String name;

public MyThread(String name){
this.name = name;
}

@Override
public void run() {
System.out.println(this.isAlive());
}
}
1
2
3
false
true
false

join()

在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

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
public class Thread4 extends Thread{

public Thread4(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + " " + i);
}
}

public static void main(String[] args) throws InterruptedException {
// 启动子进程
new Thread4("new thread").start();

for (int i = 0; i < 10; i++) {
if (i == 5) {
Thread4 th = new Thread4("joined thread");
th.start();
th.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}

}
}
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
有join方法:
main 0
main 1
main 2
main 3
main 4
new thread 0
new thread 1
new thread 2
new thread 3
new thread 4
joined thread 0
joined thread 1
joined thread 2
joined thread 3
joined thread 4
main 5
main 6
main 7
main 8
main 9

没有join方法:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
new thread 0
joined thread 0
joined thread 1
joined thread 2
joined thread 3
joined thread 4
new thread 1
new thread 2
new thread 3
new thread 4

getName和setName()

用来得到或者设置线程名称。

getPriority和setPriority()

用来获取和设置线程优先级。

setDaemon和isDaemon()

用来设置线程是否成为守护线程和判断线程是否是守护线程。

停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。

停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
  • 使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

暂停线程

interrupt()方法

线程的优先级

JDK中使用3个常量来预置定义优先级的值,代码如下:

1
2
3
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

线程优先级特性:

  • 继承性
    比如A线程启动B线程,则B线程的优先级与A是一样的。
  • 规则性
    高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
  • 随机性
    优先级较高的线程不一定每一次都先执行完。

守护线程

在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

  • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
  • 在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
  • 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

同步与死锁

  1. 同步代码块
    在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
  2. 同步代码块格式
1
2
3
synchronized(同步对象){
需要同步的代码块;
}
  1. 同步方法
    除了代码块可以同步,方法也是可以同步的

  2. 方法同步格式

    1
    synchronized void 方法名称(){}