进程与线程
进程:就是正在进行的程序。其实就是一个应用程序运行时的内存空间。
线程:线程就是进程当中的一个控制单元或执行路径。进程负责空间的标示,而线程负责执行应用程序的执行顺序。
当一个进程中出现多个线程是就是多线程。每个线程在栈中都有自己的执行空间、方法区、变量。
java VM启动的时候会有一个进程java.exe。该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
线程的创建方法
创建线程中第一种方式:继承Thread类。
步骤:
1.定义类继承Thread类。
2.复写Thread类中run方法
目的:将自定义代码存储在run方法,让线程运行。
3.调用线程start方法。
该方法有两个作用:启动线程,调用run方法。
为什么覆盖run方法?
Thread类用于描述线程。该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中run方法,用于存储线程要运行的代码。
线程都有自己的名称:Thread-编号 该编号从0开始,。
static Thread currentThread();获取当前线程对象。
getName()获取线程名称。
设置线程名称: setName()或者构造函数。
代码实例:
class Demo extends Thread
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run----"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();//创建好一个线程。
d.start();//开启线程并执行该线程的run方法。
//d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
for(int x=0; x<60; x++)
System.out.println("Hello World!--"+x);
}
}
第二种方式:实现Runable接口
步骤:
1.定义类实现Runable接口
2.覆盖Runable接口中的run方法。
将线程运行的代码存储在run方法中。
3.通过Thread类建立线程对象。
4.将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。Thread(Runable r)
自定义的run方法所属的对象是Runable接口的子类对象。所以要让线程去运行指定对象的run方法。就必须明确该run方法所属的对象。
5.调用Thread类的start方法开启线程(调用Runable接口子类中的run方法。)
售票实例:
class Ticket implements Runnable//extends Thread
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo
{
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();
}
}
两种方式的区别:
继承Thread:线程代码存放Thread子类的run方法中。
实现Runable:线程代码存放在接口的子类的run方法。
实现方式避免了单继承的局限性,在定义线程时,建议使用实现方式。
线程状态:
1.被创建:等待启动,调用start启动。
2.运行状态:具有执行资格和执行权。
3.临时状态(阻塞):有执行资格,但是没有执行权。
4.冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
5. 消忙状态:stop()方法,或者run方法结束。
多线程安全问题:一个线程在运行多条语句、操作同一个数据的时候,其他线程参与进来。导致错误数据的产生。
解决办法:只要让共享数据在某一时间只由一个线程执行,在此过程中其他的不能执行。所以会用到同步 Sychronized(对象){需要被同步的代码}。
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁
同步函数:用synchronized修饰函数即可。
锁是this。若同步函数被static修饰,所属类.class。
同步的前提:
必须有两个或两个以上线程才需要同步
多个线程必须用同一个锁。
同步的好处是解决的线程安全问题,弊端是需要不断的判断锁,消耗资源。
多线程间通信和一些线程操作方法
线程间通信其实就是多个线程在操作同一个资源,但是操作的动作不同
需要使用的方法有:
wait();
notify();
notifyAll();
上面的方法都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
同步中的死锁问题
何时会出现死锁?当同步中嵌套同步时会出现死锁。 这时会出现一个线程持有a锁要想执行下去要获取b锁,而另一个线程持有b锁要想执行下去要获取a锁。这就成了两个线程各自持有一个锁,又要获取对方的锁,这时就出现了死锁。程序会定在那执行不下去。
停止线程
只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();