您的位置:首页 > 职场人生

黑马程序员-18-java基础-多线程(1)-线程与同步

2015-06-06 18:52 676 查看
------- android培训java培训、期待与您交流!
----------

线程Thread与多线程

想要明白什么是线程,首先就需要了解什么是进程。进程是计算机资源分配的最小单位,是实际运行的一系列计算操作的集合,是计算机中的程序关于某数据集合上的依次运动活动。在早期的面向进程设计的计算机结构中,进程是程序的基本执行实体。注意,是早期。

线程,我们可以视它为轻量化的进程,在多处理器中,我们的参与运算的最小单位,是以线程计数的。即,线程就是程序执行的最小单位。

线程本身并不占有资源,它的资源来源于它所归属的进程。通常情况下,进程和线程有两种分配方式,即一进程对应一线程的“一对一”,和一进程对应多个线程的“一对多”。这两种分配方式主要应用于不同的目的,“一对一”主要用于提供使用者通过进程来控制线程的直接对应方式;“一对多”则主要是为了使多个线程在单一进程管理调配下,实现资源的合理利用,或性能上的关联提升。

因此,我们可以将线程称为程序中一个单一的顺序控制流程,是进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位(运行中程序的调度单位)。而我们把在单个程序中同时使用多个线程轮流完成不同工作的工作方式,称为多线程。在多线程中负责调动其他线程辅助自身完成程序的一个线程,我们称之为主线程。

Java中有专门的类用来描述线程,这就是Thread。Thread类将其中的大部分线程常用的方法封入其中。需要注意的是挂起进程等所示用的wait、notify、notifyAll、sleep、setDaemon、join方法是继承自Object类而来的。

常用的进程方法包括:currentThread()、getName()等,我们这里只部分举例。

Interrupt():强制唤醒进入等待的线程,并抛出InterruptException

setDaemon():设置守护线程,即后台线程。守护,即守护其他线程,内容自定。

join():抢占主线程资源,主线程只有等调用join的线程完毕后,才能执行

setPriority(int newPriority) :设置线程优先级,分为1-10这10各级别,有:

MAX_PRIORITY=10、MIN_PRIORITY=1、NORM_PRIORITY=5,这三个默认的常量。

yield():立即释放当前线程执行权

通过线程类Thread,我们可以继承并建立我们自己的线程。建立自己的线程,只需要我们继承Thread,并覆盖其中的run方法即可,具体例子如下:

class  MythreadTest
{
public static void main(String[] args)
{
//建立我们自己的线程对象
Mythread t = new Mythread();
//运行自定义线程
t.start();
//无限循环,看结果。
while(1)
{
System.out.println("1");
}
}
}

//建立我们自己的线程定义类
class Mythread extendsThread
{
//重载Thread的run方法
public void run()
{
//无限循环,看结果。
while(1)
{
System.out.println("2");
}
}
}


从结果,我们发现数字1和数字2交替出现。这说明了线程的交替进行,并不存在什么固定的顺序。同一时间只能有一个线程在CPU中运行(单CPU),CPU在运行过程中快速切换,以实现宏观上的并行处理。从例子中,我们发现,如果我们有多个自定义线程,那么我们就得多次的重写我们的Thread子类。这样明显比较麻烦,因此Java中为我们提供了一个接口类Runnable。接口类Runnable中声明了run方法,而我们只需要实现该接口,建立我们的运行参数类作为Thread的初始参数赋值,就可以简化线程的运行代码了。如下:

class  TicketSaler
{
public static void main(String[] args)
{
//建立统一的线程参数,售票机“联网”
Saler s = new Saler();
//建立3个售票机
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);

//运行3个售票机:
t1.start();
t2.start();
t3.start();
}
}

//定义我们自己的带运行线程
class Saler implementsRunnable
{
//总共100张票
int i = 100;
//重载run方法
public void run()
{
//有票则循环输出
while(i>0)
{
//输出票编号
System.out.println(Thread.currentThread().getName()+"saleticket:"+i--);
}
}
}


例题结果为: 0 sale ticket:100

0 sale ticket:99

0 sale ticket:98

0 sale ticket:96

0 sale ticket:95

0 sale ticket:93

1 sale ticket:97

1 sale ticket:92

2 sale ticket:94



从上面这道例题的结果,我们不难发现,由统一Runnable子类定义的执行部,它使用多线程运行时,可看作是将原执行部拆开,宏观并行执行的。参数归属与Runnable子类。

线程对象可以通过父类Thread的getName方法,来获取自己的默认名,这些默认名,依次从执行的先后分配,从0开始。当然,我们也可以用setName来命名线程。或是在线程对象通过构造函数建立的时候赋予名称。

比较上面两种线程的实现方式,我们发现通过Runnable子类的实现,避免了线程定义中实现的单继承局限性。一般在编程过程中,我们使用Runnable的实现方式。本质上,我认为这是“一对一”和“一对多”的更为微观的体现。

同步与关键字synchronized

什么是同步?同步就是在一个系统中所发生的事件,通过协调而在时间上出现一致性和统一化的这样一种现象。线程间的同步主要指的就是,多个线程在占用同一份资源时的相互协调使用,达到统一化的这样的一个过程。这里我们需要澄清一下互斥在编程的中概念,互斥指的是多个线程的不冲突交替运行,当一个线程占用临界资源时,临界资源所表示出的排他特征。

Java的同步实现机制是通过加锁来完成的。它提供相应的同步机制直接标识使用方式,即关键字synchronized。synchronized有3中标识同步的用法。从用法上,可分为:方法加锁、直接加锁、静态锁。而从本质上,这几种方法假的锁,其实都是对象锁,只不过选得对象不同而已。

加锁有利有弊,利在于解决了同步,弊在于降低了效率。

synchronized的用法

1.方法加锁:synchronized关键字在使用时需要放在方法声明当中,位于范围操作符(如public等)之后,位于返回类型声明(如void等)之前。方法锁在使用时,线程获得的是进入该方法的机会。当一个线程获得授权进入方法之后,其它的线程想要使用此方法,只能等使用该方法的线程释放权限之后,才能按排队顺序获取授权。方法加锁本质上是对象锁。

例如:

public synchronizedvoid myShow(){};  //本质上是对象锁


2.直接加锁:直接加锁就是用synchronized修饰的对象加的锁。从性质上讲,方法加锁用法和直接加锁,其实本质上是对象锁的不同表示而已。此时,线程想要访问该对象,就必须获得授权,同一时间只能有一个线程能够获得访问权。

例如:

public class SynTestimplements Runnable
{
public static void main(String args[])
{
//定义自定义类SynTest对象,初始化线程
SynTest s = new SynTest();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);

//开启线程
t1.start();
t2.start();
t3.start();
}

public void run()
{
//建立加有对象锁的同步代码块
synchronized(this)
{
System.out.println("run!!");
}
}
}


对于直接加锁来说,如果线程进入,则新进入的线程获得该对象的访问权限。其他的线程,在使用对应具有直接加锁的对象时,必须排队等待正在使用的线程归还执行权。上例中的这种直接加锁的方式,是一种比较粗糙的直接加锁方式。加锁的对象是有意义的存在,那么在加锁后,该对象中的一些共用资源也因为对象锁的使用而丧失了效率。为此,我们的解决方法就是在使用对象锁的时候,用一些无意义的对象来加锁,以避免这类问题发生,比如:

class CleverObjL
{
//类参数
int x,y;
//对象锁的专用无意义对象
Object lock1 = new Object();
Object lock2 = new Object();

//单独一个一个访问本类对象的参数
public void see()
{
synchronized(lock1)
{
//x的授权使用
System.out.println("x:"+x);
}
synchronized(lock2)
{
//y的授权使用
System.out.println("y:"+y);
}
}
//同时显示本类对象的参数
public seeAll()
{
synchronized(this)
{
//当两者都要同时使用时,我们在用本类对象来加锁,防止其他访问本类有实际意义的对象
System.out.println("x:"+x+"y:"+y);
}
}
}


当然,如果x,y的类型是Integer型的(即非基本类型),我们也可以直接用他们俩来代替lock1和lock2。基本类型不能用于synchronized语法之中。

3.静态加锁:静态加锁主要是指使用在有静态关键字static修饰的代码块或方法上的由synchronized关键字机制构成的锁。对于有static静态标识的方法和同步代码块来说,他们使用的就不是对象锁了。而是对应的类加锁。例如:

class A
{
public static synchronized void show(){}
}


这个show方法它的锁就是。这是因为静态在进内存的时候,内存中没有本类对象,但是一定有该类对应的字节码文件对象。这个对象就是"类名.class"。该对象的类型是反射机制中的Class类。例如上例中的方法加锁这种对象锁。我们用直接加锁方式写出,就是:

class A
{
public static synchronized void show()
{
synchronized(A.class){}
}
}


上面就是synchronized加锁的实现同步的3大用法。再次强调,这几种用法本质上都是对象锁。并没有什么所谓“变量锁”,所谓变量锁,只是对象锁或者说锁的3中使用方式中的直接加锁这种应用方式的变形而已。而对象锁,只是我们对synchronized加锁机制所得到的所的追根溯源的称呼。

------- android培训java培训、期待与您交流!
----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: