【多线程基础知识】
00.前言
此文章建议把 java的线程六种状态那篇文章看一下。
01.概念:
Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器,一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。多线程就是有多个线程,CPU可以根据一定的算法来调度和切换线程。
注意:多线程程序在运行过程中如果没有加锁 可以在线程的任意代码运行过程中切换到另一个线程
02.第一个案例:
代码
class A extends Thread
{
public void run()//只能调用run方法 run方法是Thread类里的方法 需要重写
{
while(true)
{
System.out.println("AAAA");
}
}
}
class B extends Thread
{
public void run()//只能调用run方法 run方法是Thread类里的方法 需要重写
{
while(true)
{
System.out.println("CCCCcc");
}
}
}
public class TestThread_1
{
public static void main(String[] args)
{
A aa=new A();
//aa.run(); //单线程
aa.start();//多线程 开启一个线程 并自动调用run方法,一个线程只能调用一次
B bb=new B();
bb.start();
while(true)
{
System.out.println("BBBBB");
}
}
}
结果
AAAA
AAAABBBBB
BBBBBCCCCcc
CCCCcc
CCCCcc
CCCCcc
可以看多线程是交替执行这些线程,且执行几次是不确定的,执行顺序也是不确定的。
03.创建多线程的两种基础方式:
01 实现Runnable接口
class A implements Runnable
{
public void run()
{
while(true)
{
System.out.println("AAAA");
}
}
}
class B
{
}
public class TestThread_2
{
public static void main(String[] args)
{
A aa=new A();
Thread t=new Thread(aa);
t.start();
//Runnable是一个接口 接口里面只有run这一个方法,所有必须先实现这个接口,然后传参到Thread类里 实例化一个Thread的对象
//只有这样才能调用 Thread类里的start方法
//Thread(Runnable ob)Thread类接收的参数类型是Runnable
B bb=new B();
//Thread t=new Thread(bb); //错误,因为Thread类接收的参数类型是Runnable
}
}
解释
这种方式实现Runnable接口并书写run方法,我们的线程实际上就是运行run方法里面的程序,之后我们的Thread类传一个实现了Runnable接口的对象 然后 创建一个Thread对象t,t.start()则是启动了一个线程,自此我们有两个线程 一个是主线程(main) 一个是我们刚刚创建的线程。
02 继承Thread类
代码
class A extends Thread
{
public void run()
{
System.out.printf("%s在执行\n",Thread.currentThread().getName());
}
}
public class TestThread_3
{
public static void main(String[] args)
{
A aa1=new A();
aa1.start();
aa1.setName("李四");//更改Thread-0的线程名
A aa2=new A();
aa2.start();
A aa3=new A();
aa3.start();
System.out.printf("%s在执行\n",Thread.currentThread().getName());
//Thread.currentThread()方法返回当前执行对象,getName方法获取当前对象名字
}
// 注:线程的执行顺序按照系统默认给的优先级算。创建n个线程就有n+1个线程执行,那个1代表main函数里的线程
}
结果
李四在执行
Thread-2在执行
Thread-1在执行
main在执行
解释:我们可以看出我们这种创建线程的方法是通过继承Thread类并重写run方法,我们的线程就是run里的程序,此外我们还可以通过setName方法来给线程命名
补充:我们推荐第一种创建方法,理由是如果我们要几个功能不同的线程,那么我们的第二种写法 就需要很多继承Thread的类并重写run方法,但是如果我们使用第一种方法 则可以实现多个Runnable接口 Thread类只需要传入不同的Runnable接口的类的对象 就可以实现。
04.多线程优先级
class T1 implements Runnable{
public void run()
{
for(int i=0;i<100;i++)
{
System.out.println("T1:"+i);
}
}
}
class T2 implements Runnable{
public void run()
{
for(int i=0;i<100;i++)
{
System.out.println("--------T2:"+i);
}
}
}
public class TestPriority
{
public static void main(String[] args)
{
Thread t1=new Thread(new T1());
Thread t2=new Thread(new T2());
t1.setPriority(Thread.NORM_PRIORITY+3);//设定优先级 默认为5
t1.start();
t2.start();
}
}
解释:
我们通过setPriority方法来设置优先级,注意:线优先级是指优先级越高,越有可能先执行,但只是建议先执行,具体什么时候执行由系统决定。
05.sleep线程暂停方法
class A implements Runnable
{
public void run()
{
for(int i=0;i<10;i++)
{
try
{
Thread.sleep(1000);//暂停一秒 在暂停的这一秒去运行另一个线程 且后面随机运行线程
//且sleep抛出必须处理的异常 使用只能用try
//并且run方法不能通过throws返回给上一层处理,因为父类Runnable接口中的run方法 其中并没有抛出异常
//子类处理异常的范围应该小于等于父类
}
catch(Exception e)
{}
System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
}
}
public class TestSleep
{
public static void main(String[] args)
{
A aa=new A();
Thread tt=new Thread(aa);
tt.start();
while(true)
{
System.out.println("哈哈");
}
}
}
解释
sleep方法是调用此方法的线程陷入等待即不在争抢锁(锁我们之后会介绍),在等待时间过去后会唤醒此线程,然后会继续争抢锁
06.join方法
public class TestJoin
{
public static void main(String args[])
{
MyRunner r = new MyRunner();
Thread t = new Thread(r);
t.start();
try
{
t.join();//暂停当前正在t.join()的线程 直到另一个线程执行完才继续执行
}catch(InterruptedException e){
e.printStackTrace();
}
for(int i=0;i<50;i++)
{
System.out.println("主线程: "+i);
}
}
}
class MyRunner implements Runnable
{
public void run()
{
for(int i=0;i<50;i++)
System.out.println("子线程: "+i);
}
}
解释
此方法的主要目的是可能某个线程可以运行需要其他线程的完成,此时我们用join方法把调用此方法的线程变为等待状态,在其他线程完毕后在唤醒此线程。
07.yield 线程让步方法
public class TestYield
{
public static void main(String[] args)
{
MyThread mt=new MyThread();
Thread t1=new Thread(mt);
Thread t2=new Thread(mt);
t1.setName("线程A");
t2.setName("线程B");
t1.start();
t2.start();
}
}
class MyThread implements Runnable
{
public void run()
{
for(int i=1;i<=100;i++)
{
System.out.println(Thread.currentThread().getName()+": "+i);
if(0==i%10)
{
Thread.yield(); //yield是线程让步,当满足i%10==0时 强制把当前线程转换为就绪状态 另一个线程转换为运行状态
}
}
}
}
结果
线程A: 1
线程A: 2
线程B: 1
线程B: 2
线程B: 3
线程B: 4
线程B: 5
线程B: 6
线程A: 3
线程B: 7
线程A: 4
线程B: 8
线程A: 5
线程B: 9
线程A: 6
线程B: 10
线程A: 7
我们可以看到当线程B 运行到10 后 之后我们就切换到了线程A,注意 注释里的就绪态 指的是java的六种线程状态中的可运行状态,运行状态在java的六种线程状态中没有写 指的是cpu正在运行的状态