先理解一些概念
进程(process)常常被定义为程序的执行。 可以把一个进程看成是一个独立的程序,在内存中有其完备的数据空间和代码空间。一个进程所拥有的数据和变量只属于它自己。
线程(thread)则是进程中一个单独运行的程序,也就是说,线程是存在与进程之中的。
一个进程由一个或者多个线程构成,各个线程共享相同的代码和全局数据,但是每个线程都有自己独立的堆栈。
由于每个线程都有一个堆栈,所以局部变量对每个线程来说是私有的。
由于线程共享相同的代码和全局数据,线程比进程更紧密,比进程更趋向与相互作用,进程之间的通信通过全局数据。
一个进程和一个线程最显著的区别:是线程有自己的全局数据。线程存在于进程中,因此一个进程的全局变量由所有的线程共享。由于线程共享同样的系统区域,操作系统分配给一个进程的资源对该进程的所有线程都是可用的,正如全局数据可供所有线程使用一样
多线程:当有多部分代码需要同时运行时,就需要开辟多条执行路径来完成。
这时该程序就是多线程程序。
多线程解决了,让多部分代码同时运行的问题。开多了反而减低效率。
JVM中多线程
JVM中的多线程了解:
JVM中也一样是多线程程序,
只要有一个线程负责着程序的执行。
又有一个线程负责着垃圾的回收。
这个是同时进行的。
结论:
每一个线程都有自己的运行代码,这个称之为线程的任务。
对于每一个线程便于识别都有自己的名称。
比如负责从主函数执行程序代码的线程,称之为 主线程。main thread.
主线程运行的代码都定义在主函数中。
负责收垃圾的线程:垃圾回收线程。
class Test extends Object { /* 重写了垃圾回收器调用的方法 */ public void finalize() { System.out.println("test ok"); } } class Demo{ public static void main(String[] args) { new Test(); new Test(); new Test(); new Test(); new Test(); System.gc(); //运行垃圾回收器 System.out.println("Hello World!1"); System.out.println("Hello World!2"); System.out.println("Hello World!3"); System.out.println("Hello World!4"); } } //调用了一次垃圾回收器。主线程是不执行new Test();主函数在没有调用System.gc();时的时候,只看到Hello World!,调用之后Hello World和test ok是随机排列,
创建线程方式一-继承Thread
继承Thread类
1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2.建立子类对象的同时,线程也被创建。
3.通过调用start方法开启线程。
class Demo extends Thread{ private String name; Demo(String name){ this.name = name;} public void run(){ for(int x=0; x<10; x++){ System.out.println("name...."+x+"....."+name); } } } class ThreadDemo { public static void main(String[] args) { //创建线程对象。 Demo d1 = new Demo("小强"); Demo d2 = new Demo("旺财"); //开启线程。让线程运行起来。 d1.start(); d2.start(); d1.run(); d2.run(); } }
调用run方法和调用start方法的区别?
调用run方法,从始至终都是主线程在运行。
调用start方法,是开启新的线程,新的线程运行run方法。
创建线程方式一 详解
每一个线程都应该有自己的任务,而且任务都会定义在指定的位置上。
主线程的任务都定义在main方法中。
自定义线程的任务都定义在了run方法中。
Thread t = new Thread();
t.start();
//这种开启只能调用Thread类中自己的run方法。而该run方法中并未定义自定义的内容。
我还需要创建线程,还要让线程执行自定义的任务。
所以可以复写run方法。前提必须是继承Thread类。
而继承了Thread后,该子类对象就是线程对象。
多线程-状态图解
临时阻塞状态是cpu并行执行多线程的时候,正在执行一种线程的时候,其他线程临时等待cpu。
sleep方法需要指定睡眠时间,单位是毫秒。sleep的时间结束的时候,就是冻结结束的时候。
Wait方法冻结线程之后是需要用notify方法唤醒。
从冻结状态恢复执行状态的线程可能会处于临时阻塞状态。
多线程-售票的例子
/* 需求:通过4个窗口将100票卖出。 */ class Ticket extends Thread{ private int num = 100; Public void run(){ sale();} public void sale(){ while(true){ if(num>0){ System.out.println(Thread.currentThread().getName()+"..sale:"+num--); } } } } class ThreadDemo3_Ticket { public static void main(String[] args) { //创建四个线程。 Ticket t1 = new Ticket(); //开启线程。 t1.start(); t1.start(); t1.start(); t1.start(); //多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。那该怎么办,下面介绍创建线程的第二种方式。 } }
创建线程的第二种方式-实现Runnable接口
创建线程的第二种方式。实现Runnable接口。
1,实现Runnable接口。
2,覆盖run方法。
3,通过Thread类创建线程对象。
4,将Runnable接口的子类对象作为实参传递给Thread类中的构造函数。
因为要让线程去运行指定的对象的run方法。
5,调用start方法开启线程,并运行Runnable接口子类的run方法。
class Ticket implements Runnable { private int num = 100; public void run(){ //线程任务 while(true){ if(num>0){ System.out.println(Thread.currentThread().getName()+"..sale:"+num--); } } } } class ThreadDemo3_Ticket_Runnable{ public static void main(String[] args) { Ticket t = new Ticket(); //t 是任务对象 Thread t1 = new Thread(t); //t1,t2,t3,t4 是线程对象 Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); //多个线程对象调用同一个线程任务 } }
创建线程的第二种方式-实现Runnable接口原理
Thread里面是如何使用Runnable接口,其他程序是如何实现这个接口?
//下面是Thread里面的部分代码。 class Thread{ private Runnable target; Thread(){ } Thread(Runnable target){ this.target = target; } public void run(){ if(target!=null){ target.run(); } } public void start(){ run();} } class Student implements Runnable{ public void run(){ } } class Demo{ public static void main(String[] args) { Student stu = new Student(); Thread t = new Thread(stu); t.start();} }
创建线程的第二种方式-实现Runnable接口好处
第二种实现Runnable接口创建线程思想:
将线程任务和线程对象进行解耦,将线程任务单独封装成对象。
另外,实现Runnable接口可以避免单继承的局限性。
所以建议创建多线程,都是用实现Runnable接口的方式。