King's Studio

Java多线程

字数统计: 1.9k阅读时长: 8 min
2019/02/03 Share

要理解Java多线程,首先要区分进程和线程的概念。

进程

每个进程是一个应用程序,都有独立的内存空间。在同一个操作系统中,可以同时启动多个进程。

线程

线程是一个进程中的执行场景,一个进程可以启动多个线程。那么多线程有什么作用呢?多线程不是为了提高执行速度,而是提高应用程序的使用率,给人的感觉是多个线程在同时并发执行。

创建线程

创建线程一共有三种方式,在这里我们总结一下常用的两种:

继承 Thread类

创建一个类,通过继承Thread类来开辟一个线程,然后在main()方法中h创建子类对象,调用start()方法启动线程,当前类中我们关注创建的子类对象启动的线程和main线程。

1
2
3
4
5
6
7
8
9
10
11
//继承Thread类,即开辟一条线程
public class Rabbit extends Thread{

//开辟线程重写run()方法
@Override
public void run() {
for(int i =0;i<1000;i++) {
System.out.println("兔子跑了"+i+"步");
}
}
}
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) {
//创建子类对象,两条线程路径
Rabbit rab = new Rabbit();
Tortoise tor = new Tortoise();

//调用start()方法,启动线程
rab.start();
tor.start();

//第三条线程路径
for(int i = 0;i<1000;i++) {
System.out.println("main"+i);
}
}
}

但是通过继承Thread类开辟多线程有一个弊端,因为Java只能单继承,当我们写的这个类需要实现其他类的功能时,就会遇到麻烦,因此我们通常采用第二种方式。

实现Runnable接口

实现Runnable接口创建线程有两个好处:1、避免了单继承的局限性;2、实现了资源的共享。我们通过一个例子来研究。

1
2
3
4
5
6
7
8
9
10
//真实角色
public class Programmer implements Runnable{

@Override
public void run() {
for(int i =0;i<1000;i++) {
System.out.println("敲代码");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {

public static void main(String[] args) {
//1、创建真实角色
Programmer pro = new Programmer();
//2、创建代理角色+真实角色引用
Thread proxy = new Thread(pro);
//3、调用.start()方法启动线程
proxy.start();

//第二条线程路径
for(int i = 0;i<1000;i++) {
System.out.println("聊天");
}
}
}

线程的状态与终止线程

当我们创建了一个线程之后,它并不会立即进入运行状态,先进入就绪状态,当获得调度后才取得了CPU的使用权,当所分得的时间片结束,如果运行完线程就进入死亡状态,如果没有运行完,则回到就绪队列,等待下一次调度;在运行状态中发生导致阻塞的事件,进程就会进入阻塞状态,解除阻塞状态线程仍然会回到就绪状态,而不是运行状态。

终止线程

在停止线程的操作中要注意我们将不再使用Thread类提供的stop()方法,而是在线程体中设置一个私有的标识,对外提供一个公开的方法,操作该标识来停止线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Study implements Runnable{
//1、线程类中定义线程体使用的标识
private boolean flag = true;

@Override
public void run() {
//2、线程体使用该标识
while(flag) {
System.out.println("study thread...");
}
}

//3、对外提供改变标识的方法用来停止线程
public void stop() {
this.flag = false;
}
}
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) {
//创建真实角色
Study s = new Study();
//创建代理调用start()方法启动线程
new Thread(s).start();

//外部干涉
for(int i = 0; i<100;i++) {
if (50==i) {//外部干涉,设置条件停止线程
s.stop();
}
System.out.println("main...-->"+i);
}
}
}

线程阻塞

线程阻塞我们通过Thread类提供的join()方法和yield()方法来进行演示,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 Test extends Thread{

public static void main(String[] args) throws InterruptedException {
//真实角色
Test test = new Test();
//代理角色+真实角色引用
Thread t = new Thread(test);
//启动线程
t.start();

//main线程
for(int i =0;i<1000;i++) {
if (50==i) {
t.join();//main阻塞,需要等待join线程执行完才能执行
}
System.out.println("main..."+i);
}
}

@Override
public void run() {
for(int i = 0;i<1000;i++) {
System.out.println("join..."+i);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class yield extends Thread{

public static void main(String[] args) {
yield y = new yield();
Thread t = new Thread(y);
t.start();

for(int i = 0; i < 1000; i++) {
if (i % 20 == 0) {
//写在哪个线程体中,就暂停哪个线程,当前暂停main线程
Thread.yield();
}
System.out.println("main..."+i);
}
}

@Override
public void run() {
for(int i =0;i<1000;i++) {
System.out.println("yield..."+i);
}
}
}

sleep

sleep设置休眠的时间,单位毫秒,当一个线程遇到sleep的时候,就会睡眠,进入到阻塞状态,放弃CPU使用权,腾出CPU时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得CPU时间片,当睡眠时间到达了,线程会进入可运行状态,得到CPU时间片继续执行,如果线程在睡眠状态被中断了,将会抛出IterruptedException。

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

public static void main(String[] args) throws InterruptedException {
Date endTime = new Date(System.currentTimeMillis()+10*1000);
long end = endTime.getTime();
while(true) {
//输出
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
//等待一秒
Thread.sleep(1000);
//构建下一秒时间
endTime = new Date(endTime.getTime()-1000);
if (end-10000>endTime.getTime()) {
break;
}
}
}

public static void test() throws InterruptedException {
int num = 10;
while(true) {
System.out.println(num--);
Thread.sleep(1000);
if (num<=0) {
break;
}
}
}
}

线程同步

线程同步,指某一个时刻,只允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作S变量时,线程2不允许操作即可,只有线程一使用完S后,再让线程二来使用S变量。那么为什么要引入线程同步呢?这是为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。同步通常有两种方式,一是同步方法,而是同步代码块。

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
class Web implements Runnable{
private int num = 10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
test3();
}
}

//线程锁定块,线程安全,锁定正确
public void test2() {
synchronized (this) {
if (num<=0) {
flag = false;//跳出循环
return ;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}


//加上synchronized,同步方法,线程安全,锁定正确
public synchronized void test() {
if (num<=0) {
flag = false;//跳出循环
return ;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {

public static void main(String[] args) {
//真实角色
Web web = new Web();
//三个代理请求同一份web资源
Thread t1 = new Thread(web,"工程师");
Thread t2 = new Thread(web,"程序员");
Thread t3 = new Thread(web,"黄牛甲");

//启动线程
t1.start();
t2.start();
t3.start();
}
}

线程调度

关于线程的调度,我们通过提供的TimerTask类来进行学习。

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {

@Override
public void run() {
System.out.println("so easy...");
}
}, new Date(System.currentTimeMillis()+1000),1000);//第二个参数表示系统时间一秒后运行,第三个参数表示每隔一秒运行一次
}
}

原文作者:金奇

原文链接:https://www.rossontheway.com/2019/02/03/java回顾7/

发表日期:February 3rd 2019, 12:00:00 am

更新日期:March 21st 2019, 9:34:00 am

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可,除特别声明外,转载请注明出处!

CATALOG
  1. 1. 进程
  2. 2. 线程
    1. 2.1. 创建线程
      1. 2.1.1. 继承 Thread类
      2. 2.1.2. 实现Runnable接口
    2. 2.2. 线程的状态与终止线程
      1. 2.2.1. 终止线程
      2. 2.2.2. 线程阻塞
      3. 2.2.3. sleep
    3. 2.3. 线程同步
    4. 2.4. 线程调度