Java多线程学习笔记1
Java多线程支持称得上是java的一个重量级特性。在一般的项目开发中可能很难看到他的身影,但是有关多线程的应用却是无处不在,例如java应用程序开发中的图形界面程序,其中图形界面的处理和由我们控制的java主线程执行的程序就分别由各自的线程来执行。还有就是J2EE中服务器同时处理两个或两个以上的用户的访问的时候。下面分几个部分来讲解Java多线程的基本应用,随后有可能会谈到一些java具体的应用中的一些解决方案,或者可以称为Java多线程设计模式;
1.创建多线程
java创建多线程有两种方式,一种是直接实现Runnable接口,另外一种就是直接继承自Thread类。
实现Runnable接口:
class test1 implements Runnable
{
public void run()
{
System.out.println("this is another thread");
}
public static void main(String[] args)
{
Thread t = new Thread(new test1());
t.start();
}
}
实现了Runnable接口之后也并不能说明该类是一个线程类,我们可以通过创建一个该类的接口,然后将它传给一个真正的线程类对象。在执行t.start()。之后线程就开始运行,即执行run()方法。
继承自Thread:
class test1 extends Thread
{
public void run()
{
System.out.println("this is another thread");
}
public static void main(String[] args)
{
Thread t = new test1();
t.start();
}
}
继承自Thread类那么该类就是一个真正的线程类,我们可以直接调用该线程类的start方法来启动该线程。
2.线程之数据共享
当有多个线程在系统中处于运行状态的话,但是我们的计算机在某一个时刻只能运行一个线程(对于单核的计算机来说),那么我们的系统会帮助我们在这些线程之间不断的来回切换。使得处于运行中的线程都可以执行,以此来模拟出并行处理的环境。当给予一个线程的执行时间一到期,他就会进行判断下一个会被调入cpu执行的线程,而具体的一个线程到底会在什么时候终止这都是不可预测的。而如果多个线程之间存在共享数据的话也会带来一些问题。看如下代码:
public class test1 implements Runnable{
private IntGenerator gen;
private Lock theLock;
public test1(IntGenerator theGen,Lock theL)
{
gen = theGen;
theLock = theL;
}
private static class IntGenerator{
private boolean canceled = false;
private long theNum = 0;
private Lock lock = new ReentrantLock();
public boolean isCanceled()
{
return canceled;
}
public void cancel()
{
canceled = false;
}
public long next(Lock thelock)
{
theNum++;
Thread.yield();
theNum++;
return theNum;
}
}
public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
IntGenerator gen = new IntGenerator();
//System.out.println("-------"+Calendar.getInstance().get(Calendar.SECOND)+":"+Calendar.getInstance().get(Calendar.MILLISECOND));
new Thread(new test1(gen,new ReentrantLock() )).start();
new Thread(new test1(gen,new ReentrantLock())).start();
}
@Override
public void run() {
int i = 0;
while(true)
{
long num = gen.next(this.theLock);
i++;
if(num%2!=0)
{
System.out.println(num+"+");
return;
}
if(num>=10000000)
{
System.out.println(num+"-------"+Calendar.getInstance().get(Calendar.SECOND)+":"+Calendar.getInstance().get(Calendar.MILLISECOND));
return;
}
}
}
}
我们创建了两个基于test1的线程类。他们二者持有一个共同的IntGenerator的对象。而且在他们的执行代码中会不断的执行这个gnerator的next方法,从其中拿到下一个num:我们看一下这个next的实现:
public long next(Lock thelock)
{
theNum++;
Thread.yield();
theNum++;
return theNum;
}
他会先把theNum++,然后执行Thread.yield()//这一句话可以不要,但是这是为了提高出现问题的概率,他的意思是线程让步,他建议系统切换到其他正在等待状态的线程中去执行。在执行完这一行代码之后cpu将切换到其他的线程进行处理的概率就会增大很多(不是绝对的会进行切换)。然后再将theNum加加。然后返回这个变量。两个线程都会不断的执行这一个函数中的代码,现在考虑如下:如果第一个theNum++(假如说加完之后theNum=7,)之后线程切换到另外一个线程执行,这个线程一直执行到return返回之后。那么他拿到的theNum=9.这时候将会和我们的初衷相违背。因为我们预料将theNum执行两次加加之后他一定会一直是一个偶数。但是因为 线程之间共享该变量,在对他的访问过程中出现了问题。所以才导致了这种现象的出现。那用什么方式来防止这种现象呢。
1)使用锁
锁又分为两种形式,一种是使用synchronized,另外一种是使用Lock来手动上锁
使用第一个的使用方式又分为两种,第一个即直接将next函数改为如下形式:
public synchronized long next(Lock thelock)
{
theNum++;
Thread.yield();
theNum++;
return theNum;
}
然后第一个线程在执行这个方法的时候就会获得这个锁(使用这种方式是为该类所在的对象加一把锁),然后另外一个线程也执行到这个方法的时候他会发现该方法所在的对象已经上锁了。所以他就等待(进入到了阻塞状态)。一直等到这个线程执行完毕.当那个线程执行完毕返回之后他就会释放他锁占有的锁。然后其他阻塞状态的线程就可以得以运行。
现在来具体的说一说这个synchronized,我们给一个方法添加上了这个标志之后就意味这个方法是同步方法。在任何一个线程想要执行这个方法的时候都必须获得该对象的锁才能够执行该方法。否则的话就处于阻塞状态。再说说这个锁,锁都是被绑定给一个对象的,这种给方法加标志的方式锁自然而然的就被加给了当前的对象。那么我们在执行该对象下的任何一个同步方法的时候都会要求这把锁(不是说两个线程执行同一个方法才会要求。)。我们除了使用直接给方法加这个标志之外,还可以在一个控制块中加锁,如下:
public long next(Lock thelock)
{
synchronized(this)
{
theNum++;
Thread.yield();
theNum++;
return theNum;
}
}
后面括号中的this就是指把锁加在哪一个对象上,这里可以除了是this意外的对象。但是给不同的对象之间上锁的两个控制块之间不会互斥的。
再说另外一种方式即Lock锁定的方式:
public long next(Lock thelock)
{
thelock.lock();
try{theNum++;
Thread.yield();
theNum++;
return theNum;
}finally{
theLock.unlock();
}
}
这个参数是我们在调用的时候传递过来的一个Lock对象。Lock lock = new ReentrantLock();我们可以为该线程类添加一个这样的成员变量。然后在创建线程的时候给他传递,但是一定要这两个线程获得的是同一个lock,否则的话就没有意义了。
还有就是锁在方法执行完之后一定要解锁。
2)手动添加标志,以保证lock函数中对theNum的操作具有原子性。(不谈)
(后续请看学习笔记2:有关线程结束,线程之间协作,线程设计模式)