基本概念
- 进程:就是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位。就比如,打开电脑的资源管理器可以看到,此时此刻电脑同时运行着多个程序,每个程序有一个独立的进程,而进程之间是相互独立存在的,也就是这些程序同时运行着但是不会相互影响,这就是进程。
- 线程:进程想要执行任务就需要依赖线程,就是进程中的最小执行单位就是线程,通常一个进程可以包含若干个线程,并且一个进程中至少有一个线程。
- 多线程:多线程顾名思义就是多个线程同时运行,就比如你在使用QQ音乐的时候,你能在听歌的同时,进行搜索歌曲,那么此时这个进程就有了两个线程,即播放音乐和搜索音乐,这就是多线程。
核心知识
- 线程就是独立执行的路径。
- 在程序运行时,即使自己没有创建线程,后台也会有多个线程,比如主线程、gc线程。
- main()称之为主线程,为系统的入口,用于执行整个程序。
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程的创建
- Thread class——>继承Thread类
- Runnable接口——>实现Runnable接口
- Callable接口——>实现Callable接口
1. Thread class——>继承Thread类
(1) 自定义线程类继承Thread类
(2) 重写run()方法,编写线程执行体
(3) 创建线程对象,调用start()方法启动线程
/*
* 创建线程方式一:
* 继承Thread类——>重新run()方法——>调用start()开启线程
* */
//注意线程开启不一定立即执行,由cpu调度执行
public class TestThread1 extends Thread {
@Override
public void run(){
//run方法线程体
for (int i = 0; i < 10; i++) {
System.out.println(i+".播放音乐中");
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread1 testThread1=new TestThread1();
//调用start方法开启线程
testThread1.start();
for (int i = 0; i < 100; i++) {
System.out.println(i+".搜索歌曲中");
}
}
}
//可以看见不是顺序执行的,是两个同时进行的,交互出现
/*部分输出:
47.搜索歌曲中
48.搜索歌曲中
49.搜索歌曲中
0.播放音乐中
1.播放音乐中
2.播放音乐中
3.播放音乐中
4.播放音乐中
5.播放音乐中
6.播放音乐中
7.播放音乐中
8.播放音乐中
9.播放音乐中
50.搜索歌曲中
51.搜索歌曲中
52.搜索歌曲中
53.搜索歌曲中
*/
2. Runnable接口——>实现Runnable接口
(1) 定义MyRunnable类实现Runnable接口
(2) 实现run()方法,编写线程执行体
(3) 创建线程对象,调用start()方法启动线程:(创建实现类对象——创建代理类对象——启动线程)
/*
* 创建线程方式二:
*实现Runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
* */
public class TestThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i+"正在播放歌曲");
}
}
public static void main(String[] args) {
//创建实现类对象
TestThread2 testThread2=new TestThread2();
//创建代理类对象
Thread thread=new Thread(testThread2); //以下两句可写为:new Thread(testThread2).start();
//启动线程
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println(i+"正在搜索歌曲");
}
}
}
/*部分输出:
220正在搜索歌曲
221正在搜索歌曲
0正在播放歌曲
222正在搜索歌曲
223正在搜索歌曲
1正在播放歌曲
224正在搜索歌曲
2正在播放歌曲
225正在搜索歌曲
3正在播放歌曲
*/
3. Callable接口——>实现Callable接口
(1) 实现Callable接口,需要返回值类型
(2) 重写call()方法,需要抛出异常
(3) 创建目标对象
(4)创建执行服务:ExecutorService ser= Executors.newFixedThreadPool(1);
(5)提交执行:Futureresult1= ser.submit(t1);
(6)获取结果:boolean r1=result1.get();
(7)关闭服务:ser.shutdownNow();
//线程创建方式三:实现callable接口
/*Callable的好处
1. 可以定义返回值
2. 可以抛出异常
* */
public class TestThread4 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println(i+"正在播放歌曲");
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建实现类对象
TestThread4 testThread4=new TestThread4();
//创建执行服务
ExecutorService ser= Executors.newFixedThreadPool(2);
//提交执行
Future<Boolean> r1= ser.submit(testThread4);
//获取结果
boolean rs1=r1.get();
//关闭服务
ser.shutdownNow();
}
}
/*输出:
0正在播放歌曲
1正在播放歌曲
2正在播放歌曲
3正在播放歌曲
4正在播放歌曲
Process finished with exit code 0
*/
并发问题:模拟一个买火车票,通过输出可以发现,多个线程操作同一个资源的情况下,线程不安全,数据紊乱,同一张票被多人拿到。
//多个线程操作同一个对象
public class TestThread3 implements Runnable{
private int tickenum=10;
@Override
public void run() {
while (true){
if(tickenum<=0) {
break;
}
//模拟一个延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName()获取当前执行进程的名字
System.out.println(Thread.currentThread().getName()+"拿到了第"+tickenum--+"张票");
}
}
public static void main(String[] args) {
TestThread3 testThread3=new TestThread3();
new Thread(testThread3,"小明").start();
new Thread(testThread3,"小王").start();
new Thread(testThread3,"小张").start();
}
}
/*输出:
小明拿到了第9张票
小王拿到了第10张票
小张拿到了第10张票
小明拿到了第8张票
小王拿到了第7张票
小张拿到了第6张票
小明拿到了第5张票
小张拿到了第4张票
小王拿到了第3张票
小明拿到了第2张票
小张拿到了第1张票
小王拿到了第1张票
小明拿到了第0张票
Process finished with exit code 0
*/
模拟龟兔赛跑
//模拟龟兔赛跑
public class Race implements Runnable {
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <=100; i++) {
//如果是兔子就休息
if(Thread.currentThread().getName()=="兔子"&&i%10==0){
try {
Thread.sleep(10); //通过延时模拟兔子休息
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag=gameOver(i);
//比赛结束就停止
if(flag){
break;
}
System.out.println(Thread.currentThread().getName()+"——>跑了——>"+i+"步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
//判断是否有胜利者
if (winner!=null){ //不为空说明已经存在了胜利者
return true;
}
if(steps>=100){
winner=Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race=new Race();
new Thread(race,"乌龟").start();
new Thread(race,"兔子").start();
}
}
/*部分输出:
兔子——>跑了——>6步
兔子——>跑了——>7步
兔子——>跑了——>8步
兔子——>跑了——>9步
乌龟——>跑了——>91步
乌龟——>跑了——>92步
乌龟——>跑了——>93步
乌龟——>跑了——>94步
乌龟——>跑了——>95步
乌龟——>跑了——>96步
乌龟——>跑了——>97步
乌龟——>跑了——>98步
乌龟——>跑了——>99步
winner is 乌龟
*/