Java线程介绍
通常情况下,在一个服务器上面运行的程序是很多的,可能同一时间会有多个客户
端的程序访问服务器,服务器都要对这些客户端做出响应。如果我们处理器有限,
只有一个处理器的时候,如何能够让这些任务看起来是并行的同时执行呢?这里就需要用到线程的知识。
当有超过一个以上执行空间时,看起来会像是有好几件事同时发生。但实际上,只有真正的多处理器系统才可以同时执行好几件事。使用java多线程可以让它看起来好像同时都在执行,也就是说,执行的动作可以在执行空间中快速切换,因此感觉上好像是每一个任务都在同时执行。在Java中,我们用Thread这个类来实现这一点。
下面我们通过代码一起来看一下如何新建一个线程(ThreadTest)。首先,我们需要新建一个Runnable对象。稍后我们会再定义Runnable类。而这个类会定义线程会执行什么样的任务。之后,我们会定义Thread对象,用它来去执行Runnable定义好的任务。然后,启动Thread,将Runnable对象的方法摆到新的执行空间中。
package web_server;
public class ThreadTest {
public static void main(String[] args) {
// 新建Runnable对象
Runnable threadJob = new MyRunnable();
// 将Runnable的实例传给Thread的构造函数
Thread myThread = new Thread(threadJob);
// 调用start()才会让线程开始执行,在此之前
// 它只是Thread一个实例,并不是真的线程
myThread.start();
System.out.println("back in main");
}
}
接下来我们再看如何实现Runnable。Runnable是一个接口,该接口只有一个方法,就是public void run()。在run中定义要执行的方法。因为Runnable是一个接口,线程的任务是可以被定义在任何实现Runnable的类上。线程只在乎传入给Thread的构造函数的参数是否为实现Runnable的类。当你把Runnable传给Thread的构造函数时,实际上就是在给Thread取得run的办法,这就等于你给Thread一项任务。
package web_server;
public class MyRunnable implements Runnable{
// Runnable接口只有一个方法,就是public void run()
public void run() {
go();
}
public void go() {
doMore();
}
public void doMore() {
System.out.println("top o' the stack");
}
}
线程的原子性
每一个线程变成可执行状态之后,它就会在可执行和不可执行两种状态中来来回回的切换,有的时候也会出现第三种状态,就是暂时不可执行状态。现在假设你有多个线程在排队等待执行,这时候哪个线程先执行,具体执行多长时间呢?这些工作都由调度器来实现。线程调度器会去决定哪个线程跑起来,而哪个线程会暂时不去执行。
但是这样又会有一个大问题,特别是对于多线程而言,可能会发生a线程执行一段时间,然后b线程再执行一段时间。如果a线程和b线程的程序方法互不影响还好,如果a线程和b线程使用的是同样的程序方法,可能会发生结果互相影响的问题。对于这样的问题,我们可以通过Synchronized来解决。Synchronized关键字代表线程需要一把钥匙来存取被同步化过程的线程。也就是说,通过synchronized同步化修饰过后的方法,会将方法中的内容执行完之后,再交给别的线程去执行。如果想要保护重要的数据,就把作用在数据上的方法给同步化。这部分程序,不可分割,应该被连续的执行。在古典物理学中,我们认为原子是不可分割的最小物理单元。因此我们说synchronized修饰过的方法具有原子性。我们下面通过一个例子一起来看一下。
package web_server;
public class TestSync implements Runnable{
private int balance = 0;
public void run() {
for(int i = 0; i < 50; i++) {
increment();
System.out.println("balance is " + balance);
}
}
public void increment() {
// public synchronized void increment() {
int i = balance;
balance = i + 1;
}
}
package web_server;
public class TestSyncTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestSync job = new TestSync();
Thread a = new Thread(job);
Thread b = new Thread(job);
a.start();
b.start();
}
}
可以看到,使用synchronized之后执行的效果看起来很正常,而不加之后有一些怪异的值。我们这里新建了两个线程,这两个线程执行的都是同样的方法。之所以不适用synchronized会出现数字乱的问题在于,比如说执行了i=balance之后,a线程就切换成b线程去执行了。还没有来得及累加。B线程执行一段时间之后再切换成a线程,然后a线程再继续累加,就会出现数字错乱的问题。而使用synchronized会让其强制执行完累加之后再去切换线程,所以不会出现错乱。
最后附上 本文内容对应的视频讲解
参考资料
《Head First Java》