导语:在Java中,多线程是非常重要的知识点,在笔试面试、实际应用等场合都是高频的知识点。本篇文章是我学习过程中一些知识点的理解分享,我将以案例来展示Java线程的创建。希望文章能对你的学习有所帮助。
(一)程序、进程、线程的理解:
①程序:顾名思义,程序就是我们所编写的Java代码。
②进程:在我们将编写的java代码运行起来,就是进程了。
③线程:在进程的内部,有多个线程的任务进行运行。
在我们日常冲浪的时候,我们可能会用到自带的电脑管家,例如联想电脑管家、腾讯电脑管家等。打开电脑管家,这就是一个进程,在电脑管家里面,有优化加速、垃圾清理......,我们可以同时优化加速、垃圾清理等操作,这时候线程就启动起来了。
(Fig.1:联想电脑管家)
如果在优化加速、垃圾清理等操作一半时候,我们终止了操作,那么就意味着这个线程被杀死了。
(二)案例的引入
京东举行了一次茅台抢购活动,限量10瓶。有来自北京、上海、广州、深圳地区的各100名用户参与了抢购,模拟一次抢购过程,看哪些用户抢到了茅台。
我们将用三种线程创建方式(实现Callable接口未实现)来完成案例演示,希望能够用案例实践来让大家对学习创建线程的知识点理解得更加透彻。
(三)线程的三种创建方式
所谓的线程简单的理解,就是在争抢资源,在争抢CPU资源。每个线程分得一定的时间片来使用CPU,然后反复的进行资源使用,在我们的宏观层面,就形成了多个线程都在运行的假象。
(1)方式一:继承Thread类
1.创建方式一线程的代码实现:
package com.suhuiteng.Study_Thread;
/**
* @Auther:suhuiteng
* @Data: 2021/9/29 - 09 - 29 - 10:39
* @Description:com.suhuiteng.Study_Thread
* @Vesion:
*/
public class Thread01 {
public static void main(String[] args) {
ThreadTest01 tt1=new ThreadTest01(); //创建具体线程
//上面的相当于:Thread tt1=new ThreadTest01();
//tt1.run(); 相当于一个普通的方法
tt1.start(); //应该用start()方法启动线程
}
}
class ThreadTest01 extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("子线程"+i);
}
}
}
为了能够更加明了的看出我们这个线程是有在抢资源的具体表现,我们再完善下代码,让继承Thread类和main()争抢资源,代码实现如下:
package com.suhuiteng.Study_Thread;
/**
* @Auther:suhuiteng
* @Data: 2021/9/29 - 09 - 29 - 10:39
* @Description:com.suhuiteng.Study_Thread
* @Vesion:
*/
public class Thread01 {
public static void main(String[] args) {
ThreadTest01 tt1=new ThreadTest01(); //创建具体线程
//上面的相当于:Thread tt1=new ThreadTest01();
//tt1.run(); 相当于一个普通的方法
tt1.start(); //应该用start()方法启动线程
/*****main()中抢资源*******/
for (int i = 0; i <10 ; i++) {
System.out.println("主线程"+i);
}
}
}
class ThreadTest01 extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("子线程"+i);
}
}
}
我们的预期效果是子线程和主线程交替打印。实际在控制台观察到,主线程和子线程是有在争抢资源:
2.案例程序代码:
package com.suhuiteng.Study_Thread;
/**
* @Auther:suhuiteng
* @Data: 2021/9/28 - 09 - 28 - 8:33
* @Description:com.suhuiteng.Study_Thread
* @Vesion:
*/
public class mythread_01 {
public static void main(String[] args) {
/****创建线程,模拟各个地区用户的抢购*****/
Thread th1=new BuyLiquorThread("北京");
th1.start(); //启动北京线程
Thread th2=new BuyLiquorThread("上海");
th2.start(); //启动上海线程
Thread th3=new BuyLiquorThread("广州");
th3.start(); //启动广州线程
Thread th4=new BuyLiquorThread("深圳");
th4.start(); //启动深圳线程
}
}
/**
* 抢购线程
*/
class BuyLiquorThread extends Thread{
//创建构造器
public BuyLiquorThread(String Name){
super.setName(Name);//设置当前线程的名字
}
static int LiquorNumber=10; //模拟白酒数量,记得加上static,在四个创建模拟地区的线程才能够共享白酒数量
@Override
public void run() {
for (int i = 1; i <=100 ; i++){
if(LiquorNumber>0){
System.out.println(super.getName()+"用户"+i+"抢到了第"+ LiquorNumber-- +"瓶茅台酒");
}
}
}
}
编译结果:
(2)方式二:实现Runable接口
1.创建方式一线程的代码实现:
package com.suhuiteng.Study_Thread;
/**
* @Auther:suhuiteng
* @Data: 2021/9/29 - 09 - 29 - 10:58
* @Description:com.suhuiteng.Study_Thread
* @Vesion:
*/
public class Thread02 {
public static void main(String[] args) {
ThreadTest02 tt2=new ThreadTest02(); //具体线程
Thread tdd02=new Thread(tt2); //因为具体线程tt2没有start,我们将tt2传入Thread来实现start启动
tdd02.start(); //启动线程
}
}
class ThreadTest02 implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("子线程"+i);
}
}
}
同上,为了观察子线程和主线程争抢资源的效果,我们完善代码(代码因篇幅未上传,读者可以自己学习完善下),观察到控制台如下:
2.案例程序代码:
package com.suhuiteng.Study_Thread;
/**
* @Auther:suhuiteng
* @Data: 2021/9/28 - 09 - 28 - 8:37
* @Description:com.suhuiteng.Study_Thread
* @Vesion:
*/
public class mythread_02 {
public static void main(String[] args) {
BuyLiquorThread02 pt=new BuyLiquorThread02();
Thread by1= new Thread(pt,"北京");
by1.start(); //启动北京线程
Thread by2= new Thread(pt,"上海");
by2.start(); //启动上海线程
Thread by3= new Thread(pt,"广州");
by3.start(); //启动广州线程
Thread by4= new Thread(pt,"深圳");
by4.start(); //启动深圳线程
}
}
class BuyLiquorThread02 implements Runnable{
int LiquorNumber=10; //模拟白酒数量,模拟的地区线程是共享BuyLiquorThread02,不用加上static
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
if(LiquorNumber>0){
//设置名字用 Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName()+"用户"+i+"抢到了第"+ LiquorNumber-- +"瓶茅台酒");
}
}
}
}
编译结果:
(3)方式三:实现Callable接口
接下来是第三种创建方式,我们已经在上面学习到了两种线程的创建方式,那没为什么要再引入实现Callable接口来进行创建线程呢?
在继承Thread和实现Runable接口两种方式中,存在着一定的局限性:
①Run( )方法只能是Void,而不能有返回。
②Run( )方法不能够抛出异常。
所以在JDK1.5之后就引进了实现Callaable接口的方式。我们进入查看Callable接口:
Callable接口创建线程也存在着一定的缺点,就是创建线程比其他两种方式复杂。
创建的过程如下:
public class mythread_03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建过程如下:
RadomThread rt=new RadomThread(); //创建实例
FutureTask ft=new FutureTask(rt); //通过FutureTask方法将RadomThread和Thread关联,使得RadomThread可以实现Start()启动
Thread td=new Thread(ft); //创建Thread,使得RadomThread和Thread关联
td.start(); //启动线程
Object o=ft.get();
System.out.println(o);
}
}
//RadomThread 是一个获取随机数线程
class RadomThread implements Callable<Integer>{
@Override
public Integer call(){
return new Random().nextInt(10);
}
}
创建实现Callable接口线程:
实例线程名 实例名=new 实例线程名();
FutureTask futuretask名=new FutureTask(实例名);
Thread thread名=new Thread(futuretask名);
案例程序代码:
因对知识的体系了解不够透彻,暂未使用实现Callable接口实现案例分析。