一、线程的基本概念
进程(Process)和线程(Thread)是现代操作系统不可避免的运行模型。操作系统可以运行多个线程,而每个进程又可以创建一个或多个线程。
这里注意,每个进程内至少有一个主线程(main)和gc进程(垃圾处理)。线程是多个同时进行的。
二、创建线程
1、继承Thread类
线程类Thread是在java.lang包中定义的,这里需要注意两点,
(1)编写并指定线程需要执行的方法
(2)启动线程
线程类Thread包含了实现上述功能的两个方法,
(1)run():包含线程运行时所执行的代码
(2)start():用于启动线程
用户的线程类必须基础Thread类(或实现Runnable接口),并且覆盖Thread类的run()方法。在Thread类中,run()方法·定义如下。
public void run(){ //代码语句
}
大家来看一段代码;
public class TestThread1 extends Thread{public void run() //编写并指定线程需要执行的方法
{
for (int i = 0; i < 50; i++) {
System.out.println("study"+i);
}
}
public static void main(String[] args) {
TestThread1 testThread1=new TestThread1();
testThread1.start(); //用于启动线程
for (int i = 0; i < 50; i++) {
System.out.println("play"+i);
}
}
}
它的结果如下,很好的反映了线程是多个同时运行的;
play0play1
play2
play3
play4
play5
play6
play7
study0
play8
study1
play9
study2
play10
study3
play11
study4
play12
study5
play13
study6
play14
study7
play15
study8
play16
study9
play17
study10
play18
study11
play19
study12
play20
study13
play21
study14
play22
study15
study16
study17
study18
study19
study20
study21
study22
study23
study24
study25
study26
play23
study27
play24
study28
play25
study29
play26
study30
play27
play28
play29
play30
play31
play32
play33
play34
play35
play36
play37
play38
play39
play40
play41
play42
study31
play43
play44
study32
play45
study33
play46
play47
play48
play49
study34
study35
study36
study37
study38
study39
study40
study41
study42
study43
study44
study45
study46
study47
study48
study49
Process finished with exit code 0
2、实现Runnable接口
在Java语言中,只支持单继承,一个类如果为了使用线程而继承了Thread类就不能再基础其他类。因此,Java提供了接口技术,通过一个或多个接口来解决这个问题。
在java.lang包中有一个Runnable接口,通过这个接口就能实现线程。该接口只有一个抽象方法 void
run(),用于实现线程要实现的代码。 Thread类中有一个类型为Runnable的属性,名为target。Thread类中run()方法用到了这个属性,并且调用该属性的run()方法,达到执行线程的目的。Thread类中run()方法调用如下;
{
if(target!=null){ //判断target是否为空,若不空, 即为Runnable类型对象的引用
target.run(); //调用方法
}
}
从上述代码可以看出来,如果将实现了Runnable接口的实例传给target属性,那么就可以通过线程类Thread的实例达到启动线程的目的。
总结一下,使用Runnable接口来创建线程步骤如下;
(1 )实现Runnable接口,比如实现了该接口的类为MyRunnable, 可在MyRunnable类的 run( ) 方法里编写想让线程执行的代码。
(2)创建实现了Runnable接口类的实例,比如创建MyRunnable类的实例为myRunnable。
(3)创建线程类Thread的实例,并用构造方法Thread(Runnable)将my Runnable赋值给target。
经过上述3步后,就得到了线程类实例,调用start()方法后就启动了这个线程。这个线程实际上是执行MyRunnable类中的run()方法的代码。
实现代码;
public class TestThread2 implements Runnable{public void run()
{
for (int i = 0; i < 50; i++) {
System.out.println("study"+i);
}
}
public static void main(String[] args) {
TestThread2 testThread2=new TestThread2(); //创建实现了Runnable接口类的实例
// Thread thread=new Thread(testThread2); //创建线程类Thread的实例,并用构造方法Thread(Runnable)将my Runnable赋值给target。
// thread.start();
//上面两行代码可以简换成14行的代码
new Thread(testThread2).start();
for (int i = 0; i < 50; i++) {
System.out.println("play"+i);
}
}
}
上面的代码依然能够实现多个线程的运行。
三、线程同步
由于一个进程里多个线程有时会同时共享一个对象,若他们同时访问该对象,必然会产生访问共享数据的冲突。这会破坏数据的一致性。为避免多个线程同一个共享对象产生的问题,Java引入了专门的机制来解决,即线程同步。
Java语言提供了关键字synchronized来实现多个线程的同步,并区分为两种实现方法:一种是方法同步,另一种是对象同步。
方法同步是为了防止多线程访问同一方法导致数据崩溃。具体来说,在定义方法时加上关键字synchronized修饰即可,这能保证某一线程在其他任何线程访问这一方法前完成一 次执行, 即某线程一旦启动对该方法的访问,其他线程只能等待这个线程执行完这个方法后再访问。方法同步定义synchronized方法的格式如下。
public synchronized void methodName ([parameterList]){/ /对共享对象的操作
}
对象同步是针对某数据对象而言的, 即synchronized关键字还可以放在对象前面限制访问该对象的一段代码,表示该对象在任何时刻只能由一个线程访问。 定义synchronized对象的格式如下。
synchronized (object) {// 允许访问控制的代码
}
显然,方法同步和对象同步的代码可以互相等价转换。下面两段代码是等价的。
//方法同步public synchronized void mymethodName() { //修饰方法
//对共享对象的操作
}//对象同步
public void mymethod( ) {
synchronized(this){ //修饰对象的引用
//对共享对象的操作
}
}
事实上,对于一个需要较长时间执行的方法来说,其中访问关键数据的时间可能很短,如果将整个方法申明为synchronized,将导致其他线程因无法调用该方法而长时间无法得到执行,这不利于提高程序的运行效率。这时,就可以使用对象同步,只把访问关键数据的代码段用花括号括起来,在其前面加上synchronized(this)即可。