学习之前我们先了解一下什么是进程和线程。
进程:进程就是系统中正在运行的一个程序,程序一旦运行就是进程,每个进程都拥有独立的地址空间
线程:线程是进程的一个执行单元,一个进程可以有多个线程
举个例子 一个播放器 他可以有声音、图像、字幕 而这里播放器就相当于一个进程 声音、字幕、图像分别代表一个线程
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
在java中想要实现多线程,有三种手段,一种是继承Thread类,一种是实现Runnable接口 还有一种是实现Callable接口 其实最常见的就是第一种和第二种,第三种只是作为了解 创建线程还有第四种使用线程池创建、第五种使用匿名类也就是Lambda表达式 java 8的新特性
后三种可以作为了解。
一、继承Thread类
继承Thread类是比较常用的一种方式,你如果只想起一条线程的话 就可以使用Thread
public class TestThread1 extends Thread{
private String name;
public TestThread1(String name) {
this.name=name;
}
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 5; i++) {
System.out.println(name+"运行:"+i);
}
}
public static void main(String[] args) {
TestThread1 testThread1=new TestThread1("奥特曼");
TestThread1 testThread2=new TestThread1("怪兽");
// 调用start方法开启线程
testThread1.start();
testThread2.start();
}
}
输出:
奥特曼运行:0
怪兽运行:0
奥特曼运行:1
奥特曼运行:2
奥特曼运行:3
奥特曼运行:4
怪兽运行:1
怪兽运行:2
怪兽运行:3
怪兽运行:4
可以看到这两个线程是同时进行去抢资源运行 这里说明一下 程序启动运行main时候,java虚拟机就启动了一个进程 主线程main在main在被调用的时候创建,然后随着两个方法.start() 另外两个线程也被创建出来了
注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为就绪状态,什么时候运行就要看操作系统决定
Thread.sleep()方法是使线程进行休眠 目的就是不让某个线程霸占整个进程的cpu资源,流出一些时间给其他线程一个执行的机会
这里有个小知识点看一下 面试的时候有被问过整理一下:start()和run()的区别
run
首先呐 这个run方法是thread里的一个普通方法,直接调用run方法,这个时候他会运行在主线程中,这时候程序中依然只有主线程一个线程 程序执行路径还是只有一条 还是要按顺序执行,就是要等待run方法体执行完才可以继续执行代码,这样就没有达到一个多线程的目的。
start
使用start方法才是真正的实现了多线程运行,以为使用start方法不用等待run方法体代码执行完而是直接继续执行下面的代码,因为上面说过线程有五种阶段创建、就绪、运行、阻塞、终止,用Threar的start()方法来启动一个线程,此时这个线程处于就绪(可运行状态),并没有运行,等cpu空闲时,才会执行线程里的run方法,run方法执行完毕 此线程结束
总结:run方法只是Thread里的一个普通方法 而start()方法才是真正的执行多线程
二、实现Runnable接口
Runnable也是一种比较常用的方式 这里我们只需要重写run方法即可
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+ "运行了:" + i);
}
}
public static void main(String[] args) {
// 创建线程对象 并启动
new Thread(new TestThread3(),"怪兽").start();
new Thread(new TestThread3(),"奥特曼").start();
}
}
输出:
奥特曼运行了:0
怪兽运行了:0
奥特曼运行了:1
怪兽运行了:1
奥特曼运行了:2
奥特曼运行了:3
奥特曼运行了:4
怪兽运行了:2
怪兽运行了:3
怪兽运行了:4
注意:这里的Thread.currentThread().getName()是获取当前线程的名字,new Thread(new TestThread3(),"怪兽").start();此线程的名字
可以看到我们上面是使用的一个实现Runnable接口来创建的一个多线程,不管我们是继承Thread还是实现Runnable接口 他都会有一个run()方法体 这里run()方法是多线程程序的一个约定,所有的多线程代码都在run()方法里面,Thread类也是实现Runnable接口的类
三、Thread和Runnable的区别
讲完创建方式 来看看他们的区别吧 继承Thread 他不适合资源共享,但是如果实现Runnable接口,他就可以实现资源共享
那他是怎么进行资源共享的呐 来个简单的示例来看看吧
public class TestThread4 implements Runnable{
// 票数
private int ticketName = 10;
@Override
public void run() {
while (true){
if(ticketName<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketName--+"张票");
}
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"黄牛1").start();
new Thread(testThread4,"黄牛2").start();
new Thread(testThread4,"黄牛3").start();
}
}
输出:
黄牛1-->拿到了第10张票
黄牛3-->拿到了第10张票
黄牛2-->拿到了第9张票
黄牛3-->拿到了第8张票
黄牛2-->拿到了第8张票
黄牛1-->拿到了第8张票
黄牛1-->拿到了第7张票
黄牛3-->拿到了第7张票
黄牛2-->拿到了第7张票
黄牛2-->拿到了第6张票
黄牛1-->拿到了第6张票
黄牛3-->拿到了第6张票
黄牛2-->拿到了第5张票
黄牛1-->拿到了第4张票
黄牛3-->拿到了第5张票
黄牛3-->拿到了第3张票
黄牛2-->拿到了第2张票
黄牛1-->拿到了第3张票
黄牛2-->拿到了第0张票
黄牛1-->拿到了第1张票
黄牛3-->拿到了第-1张票
进程已结束,退出代码0
一个简单的抢票示例,从上面输出可以看到10个票3个人抢实现了资源共享 但是输出中出现了一些问题下面的线程同步会讲解 这里是演示一下Runnable怎么资源共享的
总结一下:
实现Runnable接口比继承Thread类所具有的优势:
-
适合多个相同的程序代码的线程去处理同一个资源
-
增强程序的健壮性,代码可以被多个线程共享,代码和数据独立
-
避免了java中的单继承限制
-
线程池中只能放入实现Runnable或callable类的线程,不能直接放入继承Thread的类
三、Thread的常用方法
sleep()
上面已经讲了一个sleep()方法,他是取决于具体系统计时器和调度程序的精度和准确性
有两种方式
sleep(x) 一个参数时 表示休眠 ,x表示毫秒
sleep(x,y) 两个参数时 表示休眠 x毫秒又y纳秒
currentThread()
返回对当前正在执行的线程对象的引用,上面就是用这个方法获取到当前线程的名字 使用方法可以看上面的代码
start()
表示一个线程的开始,执行后java虚拟机调用该线程的run方法。
run()
Thread的普通方法 执行在主线程 需要等待run方法体执行完执行
还有很多大家可以自行百度了解
yield()
线程礼让, 让当前正在执行的线程暂停,但是不阻塞,将线程从运行状态转换为就绪状态 这里注意礼让不一定成功 这就要看cpu心情了
join()
join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
四、线程优先级
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示 范围1~10
可以在Thread类里看到这里有3个常量 最小的优先级为1 最大的优先级为10
这是Thread类里面的一个设置线程优先级的方法 if里面有一个判断 这个是判断 如果优先级大于最大值(10)并且小于最小值(1)会发生一个异常
public class TestThread6 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
Mypriority mypriority = new Mypriority();
Thread t1 = new Thread(mypriority, "t1");
Thread t2 = new Thread(mypriority, "t2");
Thread t3 = new Thread(mypriority, "t3");
t1.setPriority(10);
t1.start();
t2.setPriority(4);
t2.start();
t3.setPriority(1);
t3.start();
}
}
class Mypriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
输出:
main-->5
t1-->10
t2-->4
t3-->1
可以看到打印出来的优先级 注意:要先设置优先级 再启动线程 我们可以使用setPriority()设置优先级 getPriority()获取优先级
五、线程同步
这里需要先熟悉synchronized 它是java中的一个关键字 是一种同步锁 它修饰的对象有一下几种
1.修饰代码块,被修饰的代码块称为同步语句块,主要作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
2.修饰方法,被修饰的方法称为同步方法,其作用是整个方法 作用的对象是调用这个方法的对象
3.修饰静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
4.修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象
我们可以将上面抢票的示例拿过来看看
public class TestThread4 implements Runnable{
// 票数
private int ticketName = 10;
@Override
public void run() {
while (true){
if(ticketName<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketName--+"张票");
}
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"黄牛1").start();
new Thread(testThread4,"黄牛2").start();
new Thread(testThread4,"黄牛3").start();
}
}
输出:
黄牛1-->拿到了第10张票
黄牛3-->拿到了第10张票
黄牛2-->拿到了第9张票
黄牛3-->拿到了第8张票
黄牛2-->拿到了第8张票
黄牛1-->拿到了第8张票
黄牛1-->拿到了第7张票
黄牛3-->拿到了第7张票
黄牛2-->拿到了第7张票
黄牛2-->拿到了第6张票
黄牛1-->拿到了第6张票
黄牛3-->拿到了第6张票
黄牛2-->拿到了第5张票
黄牛1-->拿到了第4张票
黄牛3-->拿到了第5张票
黄牛3-->拿到了第3张票
黄牛2-->拿到了第2张票
黄牛1-->拿到了第3张票
黄牛2-->拿到了第0张票
黄牛1-->拿到了第1张票
黄牛3-->拿到了第-1张票
这里黄牛1和黄牛3同时抢到10张票 这非常不合理 怎么一人一半?还有黄牛3还抢到了-1票 多个线程操作同一个资源发现了问题 这里我们就需要他们排队一个一个来
public class TestThread4 implements Runnable{
// 票数
private int ticketName = 10;
private boolean flag = true;
@Override
public void run() {
while (true){
try {
Thread.*sleep*(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
buy();
}
}
public synchronized void buy(){
if(ticketName<=0){
flag=false;
return;
}
System.*out*.println(Thread.*currentThread*().getName()+"-->拿到了第"+ticketName--+"张票");
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"黄牛1").start();
new Thread(testThread4,"黄牛2").start();
new Thread(testThread4,"黄牛3").start();
}
}
输出:
黄牛1-->拿到了第10张票
黄牛2-->拿到了第9张票
黄牛3-->拿到了第8张票
黄牛3-->拿到了第7张票
黄牛1-->拿到了第6张票
黄牛2-->拿到了第5张票
黄牛3-->拿到了第4张票
黄牛2-->拿到了第3张票
黄牛1-->拿到了第2张票
黄牛2-->拿到了第1张票
输出就不一样了,这里我修饰的是一个buy()方法 同步方法 注意synchronized锁的是一个增删改的一个对象
同步块:synchronized(Obj){} Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
六、其他线程创建方式
继承Callable类
public class TestThread7 implements Callable {
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
public static void main(String[] args) {
TestThread7 testThread7=new TestThread7();
//创建FutureTask对象 将callable对象传递到FutureTask构造器中
FutureTask futureTask=new FutureTask(testThread7);
new Thread(futureTask).start();
Object o = null;
try {
o = futureTask.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
System.out.println("总和为:"+o);
}
}
-
call()可以有返回值的
-
call()可以抛出异常,被外面的操作捕获,获取异常的信息
-
Callable是支持泛型的
使用线程池
public class TestThread8{
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Test1());
executorService.execute(new Test2());
executorService.shutdown();
}
}
class Test1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class Test2 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
使用匿名类
public class TestThread8{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程开始执行");
}
});
thread.start();
}
}
或者这样
new Thread(()->{
System.out.println("子线程开始执行");
}).start();
纯纯小白,有什么要注意的地方 请大佬指点一下,谢谢