我们知道线程thread与进程process是两个不可分割的概念,也是一对极易混淆的概念,我们了解线程之前首先了解一下进程及与线程的区别。
狭义定义:An instance of a computer program that is being executed(进程是正在运行的程序的实例)
进程特征:
定义:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
线程特点:
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
Java虚拟机从main方法依次从上到下执行main方法里的任务,为单线程。如果想要变成多线程,就需要extends Tread
具体格式:
class 类名 extends Tread{
属性
方法
修饰符 run(){ //重写Thread类里的run()方法
以线程处理的程序;
}
}
举例:
package com.Thread.test;
public class TestThread extends Thread
{
public void run() {
for(int i=0; i<10; i++) {
System.out.println("TestTread----"+i);
}
}
}
package com.Thread.test;
public class Test
{
public static void main(String[] args) //主线程
{
//创建自线程对象tt
TestThread tt =new TestThread();
//子线程执行任务
tt.run();
for(int i=0 ; i<10;i++) {
System.out.println("main-----"+i);
}
}
}
Output:
TestTread----0
TestTread----1
TestTread----2
TestTread----3
TestTread----4
TestTread----5
TestTread----6
TestTread----7
TestTread----8
TestTread----9
main-----0
main-----1
main-----2
main-----3
main-----4
main-----5
main-----6
main-----7
main-----8
main-----9
我们发现,结果是按照先打印TestThread----i后打印main-----i,即按照main方法从上到下执行的,并不是我们想要的两个线程相互争抢,出现TestThread-----i和main-----i相互交错的形式。问题出在哪???
问题就在tt.run();
想要同时执行主线程和子线程,应当tt.start();
start()方法是Thread类中的方法:使该线程开始执行Java虚拟机调用run()方法
package com.Thread.test;
public class TestThread extends Thread
{
public void run() {
for(int i=0; i<10; i++) {
System.out.println("TestTread----"+i);
}
}
}
package com.Thread.test;
public class Test
{
public static void main(String[] args) //主线程
{
//创建自线程对象tt
TestThread tt =new TestThread();
//子线程执行任务
tt.start();
for(int i=0 ; i<10;i++) {
System.out.println("main-----"+i);
}
}
}
Output:
main-----0
TestTread----0
TestTread----1
main-----1
TestTread----2
TestTread----3
TestTread----4
TestTread----5
TestTread----6
TestTread----7
TestTread----8
TestTread----9
main-----2
main-----3
main-----4
main-----5
main-----6
main-----7
main-----8
main-----9
出现了线程争抢现象。
但是,如果我在创建子线程以前又加入一段代码:
package com.Thread.test;
public class Test
{
public static void main(String[] args) //主线程
{
//新加入的代码:打印main(1)---i
for(int i=0; i<10; i++) {
System.out.println("main(1)----"+i);
}
//创建自线程对象tt
TestThread tt =new TestThread();
tt.start();
//打印main(2)---i
for(int i=0 ; i<10;i++) {
System.out.println("main(2)-----"+i);
}
}
}
Output:
main(1)----0
main(1)----1
main(1)----2
main(1)----3
main(1)----4
main(1)----5
main(1)----6
main(1)----7
main(1)----8
main(1)----9
main(2)-----0
main(2)-----1
TestTread----0
TestTread----1
TestTread----2
TestTread----3
TestTread----4
TestTread----5
TestTread----6
main(2)-----2
main(2)-----3
main(2)-----4
TestTread----7
main(2)-----5
TestTread----8
TestTread----9
main(2)-----6
main(2)-----7
main(2)-----8
main(2)-----9
执行了多次,我们发现总是先打印main(1)—i然后main(2)—i和TestThread—i相互争抢,为什么会出现这样的情况,接下来用图来解释:
有了子线程之后,才会出现线程争抢现象,即main(2)—i 与 TestThread—i 交错出现。
package com.Thread.test;
public class TestThread extends Thread
{
public void run() {
for(int i=0; i<10; i++) {
System.out.println(this.getName()+i);//tt调用run()方法,所以this就是tt
}
}
}
package com.Thread.test;
public class Test
{
public static void main(String[] args) //主线程
{
Thread.currentThread().setName("main:");
//创建自线程对象tt
TestThread tt =new TestThread();
//子线程执行任务
tt.start();
for(int i=0 ; i<10;i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
Thread-00
main:0
Thread-01
main:1
main:2
main:3
main:4
main:5
main:6
main:7
main:8
Thread-02
main:9
Thread-03
Thread-04
Thread-05
Thread-06
Thread-07
Thread-08
Thread-09
为子线程设置名称:在TestThread的class界面引入父类Thread的带有String的构造方法
为主线程设置名称方法同上2.1.1
子,主线程获取名称方法同上2.1.1
package com.Thread.test;
public class TestThread extends Thread
{
public TestThread(String name)
{
super(name);
// TODO Auto-generated constructor stub
}
public void run() {
for(int i=0; i<10; i++) {
System.out.println(this.getName()+i);
}
}
}
package com.Thread.test;
public class Test
{
public static void main(String[] args)
{
Thread.currentThread().setName("main:");
//创建子线程对象tt
TestThread tt =new TestThread("Thread");
//子线程执行任务
tt.start();
for(int i=0 ; i<10;i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
马上到暑假了,准备买从伯明翰飞往北京的机票,阿联酋航空公司当天飞机票仅剩10张。还有100人需要购买当天的机票返程,阿联酋航空公司在有三种不同的途径进行网上售票,分别是阿联酋官网,携程,飞猪。请用线程知识抽象模拟售票场景。
package com.Thread.test;
public class BookingTickets extends Thread{
//属性:三种途径共享10张票
private int ticketsNumber=10;
//带有string类型的父类构造方法
public BookingTickets(String name)
{
super(name);
}
//重写run()方法
public void run() {
for(int i=0;i<100;i++) {
if(ticketsNumber>0) {
System.out.println("I got ticket from "+this.getName()+" and there are still "+(--ticketsNumber)+" tickets left.");
}
}
}
}
package com.Thread.test;
public class Test
{
public static void main(String[] args){
//分别创建三个线程,分别启动线程
BookingTickets emirates = new BookingTickets("Emirates");
emirates.start();
BookingTickets xiecheng = new BookingTickets("Xiecheng");
xiecheng.start();
BookingTickets feizhu = new BookingTickets("Feizhu");
feizhu.start();
}
}
Output:
I got ticket from Xiecheng and there are still 9 tickets left.
I got ticket from Xiecheng and there are still 8 tickets left.
I got ticket from Xiecheng and there are still 7 tickets left.
I got ticket from Emirates and there are still 9 tickets left.
I got ticket from Xiecheng and there are still 6 tickets left.
I got ticket from Xiecheng and there are still 5 tickets left.
I got ticket from Xiecheng and there are still 4 tickets left.
I got ticket from Xiecheng and there are still 3 tickets left.
I got ticket from Xiecheng and there are still 2 tickets left.
I got ticket from Xiecheng and there are still 1 tickets left.
I got ticket from Xiecheng and there are still 0 tickets left.
I got ticket from Emirates and there are still 8 tickets left.
I got ticket from Emirates and there are still 7 tickets left.
I got ticket from Emirates and there are still 6 tickets left.
I got ticket from Emirates and there are still 5 tickets left.
I got ticket from Emirates and there are still 4 tickets left.
I got ticket from Emirates and there are still 3 tickets left.
I got ticket from Emirates and there are still 2 tickets left.
I got ticket from Emirates and there are still 1 tickets left.
I got ticket from Emirates and there are still 0 tickets left.
I got ticket from Feizhu and there are still 9 tickets left.
I got ticket from Feizhu and there are still 8 tickets left.
I got ticket from Feizhu and there are still 7 tickets left.
I got ticket from Feizhu and there are still 6 tickets left.
I got ticket from Feizhu and there are still 5 tickets left.
I got ticket from Feizhu and there are still 4 tickets left.
I got ticket from Feizhu and there are still 3 tickets left.
I got ticket from Feizhu and there are still 2 tickets left.
I got ticket from Feizhu and there are still 1 tickets left.
I got ticket from Feizhu and there are still 0 tickets left.
我们发现并不是只有10张票,而是每一个购票途径都有10张票。
问题出在哪里???
问题在 private int ticketsNumber=10;
相当于每一个线程建立都拥有了自己的10张票。
问题该如何解决???
加入静态修饰符static:private static int ticketsNumber=10;
问题迎刃而解。此时的ticketsNumber被三个线程共享。
为什么要有第二种实现多线程的方式呢?
原因:Java只允许单继承,一个子类只能继承一个父类,所以在Java中如果一个类继承了某一个类,同时又想用多线程,就不能extends Thread类,此时可以用Runable接口来实现多线程。
基本结构:
class 类名 implements Runnable{
属性
方法
修饰符 run(){
以线程处理的程序
}
}
实例:
package com.Thread.test;
public class TestThread implements Runnable
{
@Override
public void run()
{
for(int i=0; i<10; i++) {
System.out.println("Thread"+i);
}
}
}
package com.Thread.test;
public class Test1
{
public static void main(String[] args)
{
TestThread tt =new TestThread();
new Thread(tt).start();
for(int i=0; i<10;i++) {
System.out.println("main"+i);
}
}
}
Output:
main0
main1
main2
main3
main4
Thread0
main5
main6
Thread1
Thread2
main7
main8
main9
Thread3
Thread4
Thread5
Thread6
Thread7
Thread8
Thread9
注意⚠️:
先创建对象TestThread tt =new TestThread();
,然后将创建的对象tt传入新建的Thread里new Thread(tt)
,最后调用start()方法。上述代码使用了匿名对象的形式简化代码。
拓展:这里使用了Java的静态代理模式,想了解关于静态代理模式,请前往查看主页文章:Java代理模式之静态代理模式(static proxy)「强烈推荐」
https://blog.csdn.net/weixin_44551646/article/details/95011230
完整版:
TestThread tt =new TestThread();
Thread th=new Thread(tt);
tt.start();
1.获取名称方法:
在实现Runnable接口时,无法使用getName来获取名字,只能用Thread.currentThread().getName()
2.设置名称方法:
设置名称时,在创建Thread对象时,可以传入线程名称
TestThread tt =new TestThread();
Thread th=new Thread(tt,“线程1”); //在此传入线程名称即可!!!
tt.start();
使用匿名对象后(简化版):
TestThread tt =new TestThread();
new Thread(tt,“线程1”).start();
使用匿名对象后(最简化版):
new Thread(new TestThread(),“线程1”).start();
若使用匿名内部类,基本结构:
new Thread(new Runnable(){……重写的run()方法};)
匿名内部类必须重写借助父类或者接口:new Runnable(){ }
public class Test1
{
public static void main(String[] args)
{
new Thread(new Runnable(){ //若使用匿名内部类则应
@Override
public void run()
{
for(int i=0; i<10; i++) {
System.out.println("Thread"+i);
}
}
}).start();
for(int i=0; i<10;i++) {
System.out.println("main"+i);
}
}
}
想要了解更多关于匿名内部类,想查看主页文章【Java内部比较器与外部比较器+匿名内部类详细讲解】:https://blog.csdn.net/weixin_44551646/article/details/94741936#_302
继承Thread类和实现Runnable接口两种方法创建多线程时,覆写的run()方法有缺点
@Override
public void run(){
........
}
为了解决上述两个问题,从JDK1.5以后,出现了第三种方法:实现Callable接口。
基本格式:
package com.Thread.test;
import java.util.concurrent.Callable;
public class TestThread implements Callable
{
@Override
public Integer call() throws Exception
{
return null;
}
}
注意⚠️:
线程从创建到执行完成的整个过程称为线程的生命周期。一个线程在生命周期内总是处于一个状态,任何一个线程一般都具有以下五种状态:创建,就绪,运行,阻塞,终止
什么是阻塞事件??—>Tips1
为什么阻塞状态解除后要进入就绪状态而不是运行状态???---->Tips2
Thread.sleep(millis);括号内,如果填1000就是1秒,2000就是2秒。。。。。
package com.Thread.test;
public class TestThread extends Thread
{
@Override
public void run()
{
for(int i=0; i<10;i++) {
System.out.println(this.getName()+"----"+i);
}
}
}
package com.Thread.test;
public class Test1
{
public static void main(String[] args) throws InterruptedException
{
for(int i=3;i>0;i--) {
System.out.println(i);
Thread.sleep(1000);
}
TestThread tt = new TestThread();
tt.start();
for(int i=0; i<=10;i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
Output:
3
2
1
Thread-0----0
Thread-0----1
Thread-0----2
Thread-0----3
Thread-0----4
Thread-0----5
Thread-0----6
Thread-0----7
Thread-0----8
Thread-0----9
main----0
main----1
main----2
main----3
main----4
main----5
main----6
main----7
main----8
main----9
main----10
在开始前,每隔1秒,倒数1个数,从3开始,3,2,1
线程才开始争抢。
yield()方法
线程让步是指暂停当前正在执行的线程对象,转而执行其他线程。
public class YieldTest extends Thread {
public YieldTest(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 当i为20时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 20) {
this.yield();
}
}
}
public static void main(String[] args) {
YieldTest yt1 = new YieldTest("Tom");
YieldTest yt2 = new YieldTest("Kevin");
yt1.start();
yt2.start();
}
output:
在控制台有两种情况,第一种kevin抢到21
……
Tom-----19
Tom-----20
Kevin-----21
Kevin------22
……
第二种情况,Tom抢到21
……
Tom-----19
Tom-----20
Tom-----21
Tom-----22
Tom-----23
……
join()方法
package com.Thread.test;
public class TestThread extends Thread
{
@Override
public void run()
{
for(int i=0; i<10;i++) {
System.out.println(this.getName()+"----"+i);
}
}
}
package com.Thread.test;
public class Test1
{
public static void main(String[] args) throws InterruptedException
{
for(int i=0; i<=10;i++) {
if(i==6) {
TestThread tt = new TestThread();
tt.start();
tt.join();
}
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
Output:
main----0
main----1
main----2
main----3
main----4
main----5
Thread-0----0
Thread-0----1
Thread-0----2
Thread-0----3
Thread-0----4
Thread-0----5
Thread-0----6
Thread-0----7
Thread-0----8
Thread-0----9
main----6
main----7
main----8
main----9
main----10
join()方法作用就是中途加入到主线程中,这个线程先被执行,执行完了再执行其他线程。
注意⚠️:必须先start(),再join()才可以
请查看主页文章:教你弄懂线程安全(史上最详)
https://blog.csdn.net/weixin_44551646/article/details/95181050