您的位置:首页 > 编程语言 > Java开发

Java核心技术学习笔记之六——线程

2016-03-09 09:39 756 查看

1.进程与线程

进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生、发展到最终消亡的过程。

多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程在进程的基础上进行的进一步划分。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,一个进程可能包含了多个线程。

所有的线程一定要依附于进程才能够存在,进程一旦消失线程也一定会消失。Java是为数不多的支持多线程的开发语言之一。

2.多线程的实现

2.1继承Thread类实现多线程

线程启动的主方法是需要覆写Thread类中的run()方法,线程主体类的定义格式:

Class 类名称 extends Thread{

属性……;

方法……;

Public void run(){

线程主体方法;

}

}

多个线程彼此之间的执行一定是以交替的方式运行,想在程序中真正的启动多线程,必须依靠Thread类的方法publicvoid start(),表示真正启动多线程,调用此方法会间接调用run()方法。

Java代码:

class TestThread extends Thread{
private String title;
public TestThread(String title)
{
this.title = title;
}
@Override
public void run() {
for(int i = 0;i<5;i++)
{
System.out.println(this.title + "运行,x = " + i);
}
}
}
public class MyThread{
public static void main(String[] args) {
TestThread ttd1 = new TestThread("线程A");
TestThread ttd2 = new TestThread("线程B");
TestThread ttd3 = new TestThread("线程C");
ttd1.start();
ttd2.start();
ttd3.start();

}
}
注意:在启动多线程时调用的是start方法,该方法底层调用的是private native void start0()是调用本地的方法,也就是说需要操作系统的支持。

2.2利用Runnable接口实现多线程

使用Thread的确可以方便的进行多线程的实现,但是这种方式最大的缺点就是单继承的问题。为此,在java 中也可以利用Runnable接口来实现多线程,接口的定义如下:

Public interfaceRunnable{

Public void run()



通过Runnable接口实现多线程:

Class 类名称 implements Runnable{

属性……;

方法……;

Public void run(){

线程主体



}

public class MyThread2 {
public static void main(String[] args) {
TestThread2 ttd1 = new TestThread2("线程 A");
TestThread2 ttd2 = new TestThread2("线程 B");
TestThread2 ttd3 = new TestThread2("线程 C");
new Thread(ttd1).start();
new Thread(ttd2).start();
new Thread(ttd3).start();
}
}
class TestThread2 implements Runnable{
private String title;
public TestThread2(String title)
{
this.title = title;
}
public void run() {
for(int i = 0;i<5;i++)
{
System.out.println(this.title + "运行,x = " + i);
}

}
}
注意:要想启动多线程必须依靠Thread类的start方法,在上述代码中使用Thread类的一个构造方法public Thread(Runnable target)

2.3Thread类和Runnable接口实现多线程的区别

Thread类的定义:public class Thread extends Object implements Runnable

可以发现,Thread类也是Runnable接口的子类

代码通过继承Thread类实现买票程序:

public class TickeTest {
public static void main(String[] args) {
ThreadTest3 ttd1 = new ThreadTest3();
ThreadTest3 ttd2 = new ThreadTest3();
ThreadTest3 ttd3 = new ThreadTest3();
ttd1.start();
ttd2.start();
ttd3.start();
}
}
class ThreadTest3 extends Thread{
private int ticket = 5;
@Override
public void run() {
for(int i = 0;i < 50;i++)
{
if(this.ticket>0)
{

System.out.println("买票,ticket = " + this.ticket--);
}
}
}
}
本程序定义了3个线程对象,目的是希望3个线程同时卖5张票,而最终的结果是一共卖出了15张票,等于是每个线程卖出了5张票。此时的关系图如下:



利用Runnable来实现多线程:

public class TicketTest_Runnable {
public static void main(String[] args) {
RunnableTest rt = new RunnableTest();
new Thread(rt).start();
new Thread(rt).start();
new Thread(rt).start();
}
}

class RunnableTest implements Runnable {
private int ticket = 5;

public void run() {
for (int i = 0; i < 50; i++) {
if (this.ticket > 0) {
System.out.println("买票,ticket = " + this.ticket--);
}
}
}
}
此程序也开启了3个线程,但这3个线程对象占着同一个Runnable接口对象的引用,实现了数据共享的操作:



虽然使用Thread也可以实现此功能,但是最好是用Runnable接口。

多线程的两种实现:

多线程的两种实现方式都需要一个线程的主类,而这个类可以实现Runnable接口或是继承Thread类。不管使用何种方式都必须在子类中覆写run方法,此方法为线程的主方法;

Thread类是Runnable接口的子类,使用Runnable接口可以避免单继承局限,以更加方便的实现数据共享的概念。

2.4线程的操作状态

要想实现多线程必须在主线程中创建新的线程对象。任何线程一般具有5种状态:创建、就绪、运行、阻塞和终止。

[1]创建状态:在程序中用构造方法创建了一个线程对象后,新的线程对象就处于创建状态,此时,它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread类的构造方法来实现,如Thread thread = new Thread();

[2]就绪状态:新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,即进入就绪状态。此时,线程将进入线程队列排队,等待CPU服务,这表明它已经具备了运行条件。

[3]运行状态:当就绪状态的献策好难过被调用并获得处理器资源时,线程就进入了运行状态,此时,自动调用该线程对象的run()方法。Run()方法定义了该线程的操作和功能。

[4]阻塞状态:一个正在运行的线程在某些特殊的情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出CPU并暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入阻塞状态。阻塞时,线程不能进入队列排队,只有当引起阻塞的原因被消除后,线程才可以进入就绪状态;

[5]终止状态:线程调用stop()方法或run()方法执行结束后,线程即进入终止状态。处于终止状态的线程不具有继续运行的能力。

3.线程的主要操作方法

3.1线程的命名和取得

线程本身属于不可见的运行状态,即每次操作时都是无法预料到的,如果想要在程序中操作线程,唯一依靠的就是线程名称,而想要取得和设置线程名称可以使用以下方法:

NO

方法
类型

描述

1

Public thread(Runnable target ,String name)

构造

实例化线程对象,接收Runnable接口子类对象,同时设置线程名称

2

Public final void setName(String name)

普通

设置线程名称

3

Public final void getName()

普通

取得线程名称

由于线程的状态不确定,所以线程的名字就成了线程的唯一分辨标记,则在定义线程名称时一定要在线程启动前设置,并且尽量不要重名,尽量不要为已经启动的线程修改名字。如果为线程设置名字则会使用设置的名字,如果没有为线程设置名字则会自动的为其分配一个名称,形式为“Thread-XXX”

当用户使用Java命令执行一个类时就表示启动了一个JVM进程,而主方法只是这个进程上的一个线程而已。当一个类执行完毕后,此进程会自动消失。每个JVM进程都至少启动两个线程main和gc。

3.2线程的休眠

线程的休眠指的是让程序的执行慢一些,在thread类中提供了方法:public static void sleep(long millis)throws InterruptedException,设置的休眠单位是毫秒。

3.3线程的优先级

NO

方法或常量

类型

描述

1

Public static final int MAX_PRIORITY

常量

最高优先级,数值为10

2

Public static final int NORM_ PRIORITY

常量

中等优先级,数值为5

3

Public static final int MIN_ PRIORITY

常量

最低优先级,数值为1

4

Public final void setPriority(int newPriority)

普通

设置线程优先级

5

Public final int getPriority()

普通

获得线程优先级

主线程的优先级为中等优先级

4.线程的同步和死锁

4.1同步问题

同步问题指的是多个线程操作同一资源时所带来的信息的安全性的问题,买票系统:

public class TicketSale {
public static void main(String[] args) {
TicketSaleThread tst = new TicketSaleThread();
new Thread(tst,"票贩子A").start();
new Thread(tst,"票贩子B").start();
new Thread(tst,"票贩子C").start();
new Thread(tst,"票贩子D").start();
new Thread(tst,"票贩子E").start();
}
}
class TicketSaleThread implements Runnable
{
private int ticket = 6;
public void run() {
// TODO Auto-generated method stub
for(int x= 0;x<10;x++)
{
if(this.ticket>0)
{
try{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "卖票,ticket=" + this.ticket--);
}
}
}
}

运行结果:
票贩子C卖票,ticket=6
票贩子A卖票,ticket=3
票贩子B卖票,ticket=4
票贩子E卖票,ticket=5
票贩子D卖票,ticket=2
票贩子E卖票,ticket=1
票贩子B卖票,ticket=-2
票贩子D卖票,ticket=0
票贩子A卖票,ticket=-3
票贩子C卖票,ticket=-1
此时卖出的票出现了负数,出现了不同步的操作问题。解决办法——使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程运行,其他线程要等待此线程执行完毕后才可以继续执行。

方式一:同步代码块。使用synchronized关键字定义的代码块就成为同步代码块,在进行同步的操作时必须要设置一个同步的对象,而这个对象应该理解为当前对象this。

public class TicketSynchronized {
public static void main(String[] args) {
TicketSynchronizedThread tst = new TicketSynchronizedThread();
new Thread(tst,"票贩子A").start();
new Thread(tst,"票贩子B").start();
new Thread(tst,"票贩子C").start();
new Thread(tst,"票贩子D").start();
new Thread(tst,"票贩子E").start();
}
}
class TicketSynchronizedThread implementsRunnable
{
private int ticket = 6;
public void run() {
// TODO Auto-generated method stub
for(int x= 0;x<10;x++)
{
synchronized (this) {

if(this.ticket>0)
{
try{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "卖票,ticket=" + this.ticket--);
}
}
}
}
}
运行结果:
票贩子A卖票,ticket=6
票贩子A卖票,ticket=5
票贩子E卖票,ticket=4
票贩子D卖票,ticket=3
票贩子C卖票,ticket=2
票贩子C卖票,ticket=1

本程序将判断是否有票以及卖票的两个操作都放入了同步代码块,这样当一个线程操作时,其他的线程就无法进入到主方法中进行操作,从而实现了线程的同步操作。

方式二:同步方法:

public class SynchronizedTicket {
public static void main(String[] args) {
SynchronizedTicketThread tst = new SynchronizedTicketThread();
new Thread(tst,"票贩子A").start();
new Thread(tst,"票贩子B").start();
new Thread(tst,"票贩子C").start();
new Thread(tst,"票贩子D").start();
new Thread(tst,"票贩子E").start();
}
}
class SynchronizedTicketThread implements Runnable{
private int ticket = 6;
public void run() {
// TODO Auto-generated method stub
for(int x= 0;x<10;x++)
{
this.sale();
}
}
public synchronized void sale()
{
if(this.ticket>0)
{
try{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "卖票,ticket=" + this.ticket--);
}
}
}
执行结果:
票贩子A卖票,ticket=6
票贩子A卖票,ticket=5
票贩子A卖票,ticket=4
票贩子A卖票,ticket=3
票贩子E卖票,ticket=2
票贩子D卖票,ticket=1

同步和异步的区别:

如果一块数据要在多个线程间进行共享,例如正在写的数据,以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取,当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,很多情况下采用异步痛经往往更有效率。

注释:abstract的method是不可同时是static、native或synchronized。

当一个线程进入一个对象的一个synchronized()方法后,其他线程不可进入此对象的其他方法,一个对象操作一个synchronized方法只能由一个线程访问。

4.2死锁

同步就是指一个线程要当等待另一个线程执行完毕才会继续执行的一种操作形式,但是过多的同步会造成死锁。死锁就是指两个线程都在等待彼此先完成,造成了程序的停滞状态。一般情况下,死锁都是在程序运行时出现的。

多个线程访问同一资源时一定要考虑到同步问题,过多的同步会带来死锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: