一、多线程的概念
进程:指的是正在进行中的程序。每个进程都会占据一定的内存大小
线程:指的是负责程序执行的一条执行路径,也称为一个执行单元。进程的执行实际是线程在执行。
(1)一个进程至少有一个线程,当一个进程有多个线程时,就是一个多线程程序
(2)当一个进程中的所有线程全部执行完,进程才结束。
(3)多线程程序一定能提高效率吗?不一定,但是肯定能实现。
在同一个程序同时执行不同的功能或相同功能的效果。
(4)Java程序是不是多线程的?
至少有一个负责程序的线程,这个线程执行的是main中的代码,还需要有一个垃圾回收程序,这个线程执行的是Object类中finalize()方法中的代码(任何一个代码都能被回收,那么回收的代码应该写在Object中)
(5)线程的功能的不同,那么线程执行代码的所在位置肯定不同。
(6)线程的任务:线程执行的代码。线程的功能不同,其任务代码的位置就不同。
主线程的任务代码在main方法中,垃圾回收线程的任务代码在finalize方法中
线程是随着任务代码的执行完成而结束。
class Test
{
public void finalize()
{
System.out.println("被回收了....");
}
}
class Demo7
{
public static void main(String[] args)//主线程 :负责执行main中的代码
{
new Test();//垃圾回收线程,负责回收垃圾
new Test();
new Test();
System.gc();//运行垃圾回收 守护线程
}
}
二、实现多线程
让两个线程争抢CPU,谁抢到谁执行自己的任务代码
(1)创建线程的第一种方式——继承Thread类创建线程类(不能再继承其他类了)
1.创建一个Thread类的子类
2.重写Thread类中的run方法
------线程有其任务代码,run方法就是线程的任务代码的书写位置
3.创建子类的对象
------线程类的子类也是线程类,实际上就是创建了一个线程
4.启动线程
------start
创建线程时为了执行任务,任务代码需要有存储位置,run方法就是这个存储位置。因为Thread中的run方法没有实现任何功能,所以我们需要重写run()方法。
Thread类中几个重要方法
来看下面代码,创建线程
class Person extends Thread//定义线程类的同时,也定义了线程执行的任务代码
{
private String name;
Person(){}
Person(String name)
{
this.name=name;
}
public void run()//任务代码
{
for(int i=1;i<=10;i++)
{
System.out.println(Thread.currentThread().getName()+"...show..."+name+","+i);
}
}
}
class Demo8
{
public static void main(String[] args) //主线程 自己创建的线程:子线程,子线程是由主线程启动的
{
Person huang=new Person("黄文强");//创建了一个子线程
Person xu=new Person("许文强");//创建了一个子线程
//启动线程
huang.start();//线程启动了,当抢到cpu时,会自动去执行run方法中的任务代码
xu.start();
for(int i=1;i<=10;i++)
{
System.out.println(Thread.currentThread().getName()+"...show..."+i);
}
//huang.show();
//xu.show();
}
}
(1)若写的是 **线程.run() **则表示调用的普通方法
若写的是 线程.start() 则会启动线程,并自动调用run()方法
(2)只是创建线程,而线程没有启动,则不会去抢占CPU。启动了也不一定可以立刻抢到CPU。
(3)在该代码中,子线程都是由主线程所启动。
(4)主线程的名字是main
子线程的名字是 Thread-编号 ------ 编号从0开始
package day17;
//每个线程发生异常只是线程自己的事儿,不会影响其它线程
class Person extends Thread
{
private String name;
Person(){}
Person(String name)
{
this.name=name;
}
public void run()
{
int[] arr=new int[3];
for(int i=1;i<=10;i++)
{
// System.out.println(arr[3]);
System.out.println(Thread.currentThread().getName()+"...show..."+i);
}
}
}
class Demo9
{
public static void main(String[] args)
{
Person huang=new Person("黄文强");
Person xu=new Person("许文强");
huang.start();
xu.start();
// int a=5/0;
}
}
程序内存分析
(1)每个线程在栈中都拥有自己的内存,各自是独立的,当线程执行完自己的任务代码,就释放栈中的内存,所有的线程都执行完,主线程才会结束
(2)每个线程发生异常只是线程自己的事情,不会影响其他线程。如上面代码的5/0异常。其他线程代码依然正常执行。
(2)线程生命周期
(3)卖票问题
class Ticket extends Thread //该类既是线程类,也是任务类,该类的对象只能执行自己定义的任务
{
private static int num=50;//资源
public void run()
{
while(true)
{
if(num>0)
System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}
}
}
class Demo10
{
public static void main(String[] args)
{
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
出现了一张票被卖四次的问题,因此把票定义成了static,问题解决了,但是不是所有情况都可以使用静态。
下面用第二种方式解决问题
(4)创建线程的第二种方式——实现Runnable接口
步骤:
1.定义一个实现了Runnable接口的子类
2.在子类中重写run方法——run方法是任务代码书写的位置
3.创建Runnable接口的子类对象
4.创建Thread对象,把Runnable接口的子类对象作为参数传给Thread的构造方法
实现了Runnable接口的类是任务类,线程使用的是Thread,让线程执行什么任务就把那个任务传给线程,只要实现了Runnable接口的子类对象都可以传个Thread
class Ticket implements Runnable//任务类
{
private int num=50;//资源
public void run()//任务代码
{
while(true)
{
if(num>0)
System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}
}
}
class Test implements Runnable
{
}
class Demo11
{
public static void main(String[] args)
{
Ticket t=new Ticket();//任务对象
Thread t1=new Thread(t);//线程对象
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
问题:创建线程的第二种方式为什么可以解决卖票问题?
创建线程的第二种方式:只需要创建一个任务对象,也就是只有一份资源
总结:
(1)把这个任务对象分别传给了4个线程对象,4个线程执行的任务是相同的,但是资源只有一份
(2)创建线程的第一种方式:创建4个线程对象,就创建了4份资源
(3)第二种方式不仅解决了卖票问题,而且更加面向对象,更加灵活,线程执行的任务不是固定不变的了