前言
当回顾其线程的时候,感觉什么都忘了,在网上查资料的时候,都没有什么好的文章,决定写一系列关于Java线程的文章。
这篇博客特别基础,所以推荐一篇写的比较好文章 —— Java多线程学习(吐血超详细总结)
线程和进程概述
- 进程:计算机中特定功能的程序在数据集上的一次运行。
- 线程:线程是进程的一个单元。
- 多线程:一个进程中有多个线程在同时运行,如迅雷下载,迅雷软件的一次运行就是一个进程,那么在迅雷中可以同时下载多个电影,这就是多线程(每一个下载都是一个线程)
- Jvm是多线程的,在我们运行jvm的时候后台会运行垃圾回收的线程,来清理没有被引用的对象。
举个例子:
进程:启动一个LOL.exe就叫一个进程。 接着又启动一个DOTA.exe,这叫两个进程。
线程:线程是在进程内部同时做的事情,比如在LOL里,有很多事情要同时做,比如"盖伦” 击杀“提莫”,同时“赏金猎人”又在击杀“盲僧”,这就是由多线程来实现的。
常用创建线程的两种方式
一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个,有兴趣看这里Java并发编程与技术内幕:Callable、Future、FutureTask、CompletionService 、BF360C )
使用Thread创建线程的步骤:
- 自定义一个类,继承java.lang包下的Thread类
- 重写run方法
- 将要在线程中执行的代码编写在run方法中
- 创建上面自定义类的对象
- 调用start方法启动线程
1. 继承Thread类
//1.自定义一个类,继承java.lang包下的Thread类
public class MyThreed extends Thread {
//通过构造函数为线程起名字
private String name;
public MyThreed(String name) {
this.name = name;
}
//2.重写run方法
@Override
public void run() {
//3.将要在线程中执行的代码编写在run方法中
for (int i = 0; i <=50 ; i++) {
System.out.println(name+"跑了"+i+"米");
}
}
}
测试
public static void main(String[] args) {
//4.创建上面自定义类的对象
MyThreed myThreed=new MyThreed("懒洋洋");
myThreed.start();
MyThreed myThreed1=new MyThreed("灰太狼");
//5.调用start方法启动线程
myThreed1.start();
System.out.println("方法结束");
}
2. 实现Runnable接口创建线程
使用Runnable创建线程步骤:
- 自定义一个类实现java.lang包下的Runnable接口
- 重写run方法
- 将要在线程中执行的代码编写在run方法中
- 创建自定义类的对象
- 创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法
- 调用start方法启动线程
// 1.自定义一个类实现java.lang包下的Runnable接口
public class MyRunable implements Runnable {
private String name;
public MyRunable(String name) {
this.name = name;
}
// 2.重写run方法
@Override
public void run() {
// 3.将要在线程中执行的代码编写在run方法中
for (int i = 0; i <=50 ; i++) {
System.out.println(name+"跑了"+i+"米");
}
}
//测试
public static void main(String[] args) {
// 4.创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法
Thread thread = new Thread(new MyRunable("喜洋洋进度"));
Thread thread1 = new Thread(new MyRunable("灰太狼进度"));
//6.调用start方法启动线程
thread.start();
thread1.start();
}
多线程创建的二种方式对比
继承Thread
- 优点:可以直接使用Thread类中的方法,代码简单
- 缺点:继承Thread类之后就不能继承其他的类
实现Runnable接口
- 优点:即时自定义类已经有父类了也不受影响,因为可以实现多个接口
- 缺点: 在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
线程的声明周期
线程是一个动态执行的过程,它也有一个从产生到销毁的过程。
下图显示了一个线程完整的生命周期。
- 新建: 线程被new出来
- 准备就绪:线程具有执行的资格,即线程调用了start(),没有执行的权利
- 运行:具备执行的资格和具备执行的权利
- 阻塞:没有执行的资格和执行权利
- 销毁: 线程的对象变成垃圾,释放资源。
线程的并发
我们举个例子,让我们更加懂得并发。
现有一家电影院,现哪吒之魔童降世的电影票共有一百张,共有三个窗口卖票,程序怎么实现?
public class MoviceThreed extends Thread {
//为线程起别名
private String name;
//共工100张静态变量的票
static int tickets = 100;
//创建一个锁对象,这个对象是多个线程对象共享的数据
static Object obj = new Object();
public MoviceThreed(String name) {
this.name = name;
}
@Override
public void run() {
//卖票是持续的
while (true) {
synchronized (obj) {
if (tickets > 0) {
System.out.println(name + "卖出座位是" + (tickets--) + "号");
} else {
break;
}
}
}
System.out.println(name+"卖票结束");
}
}
public class MoviceTest {
public static void main(String[] args) {
MoviceThreed m1 = new MoviceThreed("窗口1");
MoviceThreed m2 = new MoviceThreed("窗口2");
MoviceThreed m3 = new MoviceThreed("窗口3");
m1.start();
m2.start();
m3.start();
}
}
如果我么用传统的方式的话会发生并发行为,可能1号窗口,2号窗口卖出同一张票,这样是不对的所以我们上面代码用到 synchronized
-
语法:
synchronized(锁对象){ //操作共享资源的代码 }
同步代码加在什么地方?
- 代码被多个线程访问
- 代码中有共享的数据
- 共享数据被多条语句操作。