一、进程和线程
二、多线程
2.1、什么是多线程
- 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
- 多个线程交替占用CPU资源,而非真正的并行执行
2.2、多线程好处
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
三、主线程
3.1、Thread类
Java提供了java.lang.Thread类支持多线程编程
3.2、主线程
- main()方法即为主线程入口
- 产生其他子线程的线程
- 必须最后完成执行,因为他执行各种关闭动作
代码示例
public static void main(String[] args) {
// main()方法是程序的主入口,是一个线程
// currentThread():获取当前线程对象
Thread thread = Thread.currentThread();
// getName():获取当前线程的名称
String name = thread.getName();
System.out.println(name);//main
// getPriority():获取当前线程的优先级,默认线程优先级为5
int priority = thread.getPriority();//5
System.out.println("当前线程的优先级:" + priority);
// setName():设置线程的名称
thread.setName("duohao");
System.out.println("线程名称:" + thread.getName());//duohao
// setPriority():设置线程优先级
thread.setPriority(8);
System.out.println("线程优先级:" + thread.getPriority());//8
System.out.println("线程优先级最高值:" + Thread.MAX_PRIORITY);// 10
System.out.println("线程优先级最低值:" + Thread.MIN_PRIORITY);// 1
System.out.println("线程优先级默认值:" + Thread.NORM_PRIORITY);// 5
}
四、线程的创建和启动
在Jav中创建线程的两种方式
- 继承java.lang.Thread类
- 实现java.lang.Runnable接口
使用线程的步骤
- 定义线程
- 创建线程对象
- 启动线程
- 终止线程
4.1、继承Thread类创建线程
- 定义MyThread类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
*** 代码示例 ***
MyThread.java
public class MyThread extends Thread {
public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 创建线程类对象
MyThread mt1 = new MyThread("多好");
MyThread mt2 = new MyThread("方浅");
// start()方法是启动线程的方法
mt1.start();
mt2.start();
// 当同时启动两个线程以后,会出现两个线程交替占用CPU执行代码的结果
}
}
多个线程交替执行,不是真正的“并行”
线程每次执行时长由分配的CPU时间片长度决定
4.2、实现Runnable接口创建线程
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
*** 代码示例 ***
MyThread.java
public class MyThread implements Runnable {
@Override
public void run() {
// 在重写的run()方法中编写你要执行的代码:使用循环输出1-20
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
}
}
Test.java
public class Test {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
Thread thread1 = new Thread(mt1, "千里眼");
Thread thread2 = new Thread(mt1, "顺风耳");
/*
* start()方法是Thread类中的方法,而我们需要通过start()方法来调用run()方法,不能直接调用run()方法
* 但是Runnable接口中只有一个抽象方法run()方法,那么实现Runnable接口的类不能调用start()方法
*
*解决办法:
* 将实现Runnable接口的类对象作为参数传递给Thread构造方法,然后通过Thread类对象调用start()方法
*
*/
// mt1.start();
thread1.start();
thread2.start();
}
}
两种线程对比
实现接口 | 对比 |
---|---|
Thread接口 | 编写简单,可直接操作线程; 适用于单线程 |
Runnable接口 | 避免单继承性; 便于共享资源 |
五、线程状态
六、线程调度
线程调度指按照特定机制为多个线程分配CPU的使用权
常用方法
方法 | 说明 |
---|---|
void setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join () | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive | 测试线程是否处于活动状态 |
6.1、线程优先级
线程优先级由1~10表示,1最低,默认优先级为5
优先级高的线程获得CPU资源的概率较大
6.2、线程休眠
让线程暂时睡眠指定时长,线程进入阻塞状态
睡眠时间过后线程会再进入可运行状态
public static void sleep(long millis)
millis为休眠时长,以毫秒为单位
调用sleep()方法需处理InterruptedException异常
代码示例
public class Wait {
public static void bySec(long s) throws InterruptedException {
for (int i = 0; i < s; i++) {
System.out.println(i + 1 + "秒");
Thread.sleep(0);
}
}
}
--------------------------------------------------------------------------
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("程序开始");
Wait.bySec(5);
System.out.println("程序结束");
}
}
----------------
程序开始
1秒
2秒
3秒
4秒
5秒
程序结束
----------------
6.3、线程强制运行
使当前线程暂停执行,等待其他线程结束后再继续执行本线程
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
millis:以毫秒为单位的等待时长
nanos:要等待的附加纳秒时长
需处理InterruptedException异常
代码示例
public class MyThread implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
}
}
----------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
Thread t = new Thread(mt,"多好");
t.start();
for (int i = 1; i <= 5; i++) {
if(i == 3){
t.join();
}
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
}
}
---------------
main 1
多好 1
main 2
多好 2
多好 3
多好 4
多好 5
main 3
main 4
main 5
---------------
6.4、线程的阻塞
暂停当前线程,允许其他具有相同优先级的线程获得运行机会
该线程处于就绪状态,不转为阻塞状态
public static void yield()
只是提供一种可能,但是不保证一定会实现“礼让”,自己也会去抢占线程
代码示例
public class MyThread implements Runnable {
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
if(i == 3){
System.out.print("线程阻塞:");
Thread.yield();
}
}
}
}
---------------------------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
Thread t = new Thread(mt,"多好");
Thread ta= new Thread(mt,"方浅");
t.start();
ta.start();
}
}
-------------------
多好 1
多好 2
方浅 1
多好 3
线程阻塞:多好 4
多好 5
方浅 2
多好 6
多好 7
多好 8
多好 9
多好 10
方浅 3
线程阻塞:方浅 4
方浅 5
方浅 6
方浅 7
方浅 8
方浅 9
方浅 10
-------------------
七、线程同步
多线程共享数据引发问题
多个线程操作同一共享资源时,将引发数据不安全问题
1、使用synchronized修饰的方法控制对类成员变量的访问
访问修饰符 synchronized 返回类型 方法名(参数列表){……}
或者
synchronized 访问修饰符 返回类型 方法名(参数列表){……}
synchronized就是为当前的线程声明一把锁
使用同步方法
代码示例
public class ticket implements Runnable {
public int num = 10;
public int count = 0;
public boolean flag = false;
@Override
public void run() {
while (!flag) {
sale();
}
}
private synchronized void sale() {
if (num <= 0) {
flag = true;
return;
}
num--;
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + count
+ "票,还剩" + num + "张票");
}
}
-----------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket ();
Thread t1 = new Thread(ticket ,"多好");
Thread t2 = new Thread(ticket ,"多余");
Thread t3 = new Thread(ticket ,"方浅");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
2、使用synchronized关键字修饰的代码块
synchronized(syncObject){
//需要同步的代码
}
- syncObject为需同步的对象,通常为this
- 效果与同步方法相同
public void run() {
while (true) {
synchronized (this) { //同步代码块
// 省略修改数据的代码......
// 省略显示信息的代码......
}
}
}
代码示例
public class Ticket implements Runnable {
public int num = 10;
public int count = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
if (num <= 0) {
break;
}
num--;
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第"
+ count + "张票,还剩下" + num + "张票");
}
}
}
}
-----------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"多好");
Thread t2 = new Thread(ticket,"多余");
Thread t3 = new Thread(ticket,"方浅");
t1.start();
t2.start();
t3.start();
}
}
多个并发线程访问同一资源的同步代码块时
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
八、线程安全
方法是否同步 | 效率比较 | 适合场景 | |
---|---|---|---|
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |