一、Java并发编程:多线程简介及入门案例

一、Java多线程简介

1. 什么是多线程(multithreading)

多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术。

2. 并发和并行

  • 并发:在一段时间间隔内,在一个处理器上,通过cpu调度算法,先后完成指令执行
  • 并行:在一段时间间隔内,在多个处理器上,同时指令执行

注:在《Java编程思想》中提到并发通常是提高运行在单处理器上的程序的性能,而直觉告诉我们,在单处理器上进行并发操作时开销更大,因为需要进行线程间的切换;但是,在程序的执行过程中,如果发生了阻塞(通常是IO),整个程序都会停滞,但是利用并发,可以使得程序中的其他任务继续执行。这,就是为什么要研究并发

3. 进程和线程

  • 进程:是系统进行资源分配和调度的基本单位,是线程的容器,一个进程包含多个线程程
  • 线程:是进程的基本组成单位

4. 线程的生命周期

在这里插入图片描述

5. 线程的五种基本状态

  • 新建状态(new):线程创建后,进入新建状态
  • 就绪状态(Runnable):调用线程的start()方法,线程进入就绪状态,等待cpu调度执行
  • 运行状态(Running):cpu调度处于就绪状态的线程,线程进入运行阶段
  • 阻塞状态(Blocked):运行期的线程,由于某种原因,放弃cpu的使用权,进入阻塞阶段,进入阻塞阶段的线程,马上进入就绪状态,等待cpu的再次调用

阻塞状态分为3种

  1. 等待阻塞:调用wait()方法,线程进入等待阻塞状态
  2. 同步阻塞:线程获取synchronized同步锁失败(被其他线程占用),进入同步阻塞状态
  3. 其他阻塞:调用sleep()或join()方法,线程进入阻塞状态,待sleep()超时、join()等待终止或超时、或者IO处理完成,线程重新进入就绪状态
  • 死亡状态(Dead):因为异常,或者程序执行结束退出run()方法,线程结束了生命周期

二、线程的创建及使用

1. 继承Thread类,重写run()方法(Thread类本身也实现了Runnable接口)

  • 继承Thread类重写run()方法,run()方法的方法体是线程要完成的任务
  • 创建继承了Thread类的实例,该实例为线程实例
  • 调用线程对象的start()方法来启动线程
package com.lt.thread;
/**
 * 1.继承Thread类创建线程
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
public class Thread_01 extends Thread {

	@Override
	public void run() {
		for(int i=1; i<=5; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
	
	public static void main(String[] args) {
		Thread t1 = new Thread_01();
		Thread t2 = new Thread_01();
		t1.start();
		t2.start();
	}
}

结果

Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-1:3
Thread-1:4
Thread-1:5

2. 实现Runnable接口,重写run()方法

  • 定义Runnable接口的实现类,重写run()方法,run()方法的方法体是线程要完成的任务
  • 创建实现了Runnable接口的实现类的实例,并将此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
  • 调用线程对象的start()方法来启动线程
package com.lt.thread;
/**
 * 2.实现Runnable接口
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
public class Thread_02 implements Runnable {

	@Override
	public void run() {
		for(int i=1; i<=5; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
	
	public static void main(String[] args) {
		Thread_02 _t1 = new Thread_02();
		Thread_02 _t2 = new Thread_02();
		Thread t1 = new Thread(_t1);
		Thread t2 = new Thread(_t2);
		t1.start();
		t2.start();
	}
}

结果

Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
Thread-0:3
Thread-1:3
Thread-0:4
Thread-1:4
Thread-0:5
Thread-1:5

3. 使用Callable和Future创建线程

  • 创建Callable的实现类,并重写call()方法,方法体即线程执行体,并且有返回值
  • 创建Callable实现类的实例,并用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  • 使用FutureTask对象作为Thread的target来创建Thread对象,该对象为真正的线程对象
  • 调用线程对象的start()方法启动线程
package com.lt.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * 3.使用Callable和Future创建线程
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
public class Thread_03 implements Callable<Object>{

	@Override
	public Object call() throws Exception {
		for(int i=1; i<=5; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		return Thread.currentThread().getName()+"执行完成!";
	}
	public static void main(String[] args) throws Exception {
		Thread_03 _t1 = new Thread_03();
		Thread_03 _t2 = new Thread_03();
		FutureTask<Object> _f1 = new FutureTask<>(_t1);
		FutureTask<Object> _f2 = new FutureTask<>(_t2);
		Thread t1 = new Thread(_f1);
		Thread t2 = new Thread(_f2);
		t1.start();
		t2.start();
		//获取线程中的返回值,get()方法会导致程序阻塞,直到子线程结束后才会得到返回值
		System.out.println(_f1.get());
		System.out.println(_f2.get());
	}
}

结果

Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
Thread-0:3
Thread-1:3
Thread-0:4
Thread-0:5
Thread-1:4
Thread-0执行完成!
Thread-1:5
Thread-1执行完成!
  • 程序启动运行main()方法时,java虚拟机启动一个进程,当程序执行到start()方法时,在当前进程里,创建新的线程,整个应用开始在多线程状态下运行。
  1. 当调用start()方法时,程序并不立马执行,此时线程进入Runnable状态,什么时候执行,由操作系统决定
  2. 多线程的执行顺序是乱序的,
  • Thread类本身也实现了Runnable接口

三、线程常用的方法

1. join()

A线程中调用B线程的join()方法,A线程会被阻塞进入阻塞状态(Blocked),直到B线程执行完成

package com.lt.thread;

/**
 * 线程常用的方法:join()
 * join():A线程中调用B线程的join()方法,A线程会被阻塞,直到B线程执行完成
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
public class Thread_04 {
	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1; i<=10; i++){
					System.out.println(Thread.currentThread().getName()+"-"+i);
				}
			}
		});
		for(int i=1; i<=10; i++){
			System.out.println(Thread.currentThread().getName()+"-"+i);
			if(i==7){
				t1.start();
				t1.join();
			}
		}
	}
}

结果

main-1
main-2
main-3
main-4
main-5
main-6
main-7
Thread-0-1
Thread-0-2
Thread-0-3
Thread-0-4
Thread-0-5
Thread-0-6
Thread-0-7
Thread-0-8
Thread-0-9
Thread-0-10
main-8
main-9
main-10

2. sleep()

让当前线程进入阻塞状态(Blocked),暂停指定的时长;睡眠期结束后,线程进入就绪状态(Runnable),并不会立即执行,如果需要立即执行,可以调用sleep(1)

package com.lt.thread;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * sleep()
 * 让当前线程进入阻塞状态,暂停指定的时长;睡眠期结束后,线程进入就绪状态(Runnable),并不会立即执行,
 * 如果需要立即执行,可以调用sleep(1)
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
@SuppressWarnings("all")
public class Thread_05 {
	public static void main(String[] args) throws Exception {
		while(true){
            //隔一秒打印一次时间
			Thread.currentThread().sleep(1000);
			System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
		}
	}
}

结果

2019-05-07 15:52:54
2019-05-07 15:52:55
2019-05-07 15:52:56
2019-05-07 15:52:57
2019-05-07 15:52:58
2019-05-07 15:52:59
2019-05-07 15:53:00
2019-05-07 15:53:01
2019-05-07 15:53:02
2019-05-07 15:53:03
2019-05-07 15:53:04
2019-05-07 15:53:05
2019-05-07 15:53:06
2019-05-07 15:53:07

3. setDaemon():守护线程

调用当前线程的setDaemon()方法便可将线程转化为守护线程;守护线程为其他线程提供服务,当其他线程进入死亡状态时,守护线程自动死亡

4. setPriority():改变线程优先级

线程的优先级并不是控制线程执行的先后顺序,而是表示线程占用CPU时间片长短,优先级越高,占用的时间片时间越长

package com.lt.thread;

/**
 * 线程常用的方法:join()
 * join():A线程中调用B线程的join()方法,A线程会被阻塞,直到B线程执行完成
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
public class Thread_06 {
	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1; i<=20; i++){
					System.out.println(Thread.currentThread().getName()+"-"+i);
				}
			}
		}, "线程1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1; i<=20; i++){
					System.out.println(Thread.currentThread().getName()+"-"+i);
				}
			}
		}, "线程2");
		t1.setPriority(8);
		t2.setPriority(2);
		t1.start();
		t2.start();
	}
}

结果

线程1-1
线程1-2
线程1-3
线程1-4
线程1-5
线程1-6
线程1-7
线程1-8
线程1-9
线程1-10
线程1-11
线程1-12
线程1-13
线程1-14
线程1-15
线程2-1
线程1-16
线程2-2
线程1-17
线程2-3
线程1-18
线程2-4
线程2-5
线程1-19
线程2-6
线程1-20
线程2-7
...

5. yield():线程让步

线程调用yield()方法,线程进入就绪状态(Runnable)

四、start()和run()方法的区别

1. start()

启动一个新线程,在新线程中会执行相应的run()方法,start方法不能被反复调用

2. run()

在当前线程中启执行run()并不会启动新的线程,可以被重复调用

package com.lt.thread;

/**
 * 1.调用start()方法,启动新的线程,在新的线程中执行run()方法
 * 2.调用run()方法,在当前线程执行run()方法,并不会启动新线程
 * @author lt
 * @date 2019年4月9日
 * @version v1.0
 */
public class Thread_07 {
	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1; i<=10; i++){
					System.out.println(Thread.currentThread().getName()+"-"+i);
				}
			}
		}, "线程1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1; i<=10; i++){
					System.out.println(Thread.currentThread().getName()+"-"+i);
				}
			}
		}, "线程2");
		t1.run();
		t2.start();
	}
}

结果

main-1
main-2
main-3
main-4
main-5
main-6
main-7
main-8
main-9
main-10
线程2-1
线程2-2
线程2-3
线程2-4
线程2-5
线程2-6
线程2-7
线程2-8
线程2-9
线程2-10

参考资料

【1】Java总结篇系列:Java多线程(一)
【2】Java多线程系列目录(共43篇)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值