java多线程的学习.doc_第1页
java多线程的学习.doc_第2页
java多线程的学习.doc_第3页
java多线程的学习.doc_第4页
java多线程的学习.doc_第5页
已阅读5页,还剩21页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java多线程的学习Java线程一般我们写的程序是单线程的,其main函数就是启动单线程的入口。在java中可以实现Runable接口或是继承Thread来实现多线程,要实现多线程,就必须执行线程的star方法,执行start方法后,java会在后台启动一个新的线程并执行run方法,当然你也可以直接执行run方法,但是就不会去启动新的线程了。下面是一个多线程的例子public class ThreadMutile /* * param args */public static void main(String args) ThreadZou t=new ThreadZou();t.start();/新启动一个线程for(int i=0;i100;i+)System.out.println(main);class ThreadZou extends ThreadOverridepublic void run() / TODO Auto-generated method stubfor (int i=0;i100;i+)System.out.println(zouxiaohu);这里会有两个线程一个是main函数,一个是新建立的线程。你会发现其每次执行结果会有所不同,这是因为线程并发运行的结果实现线程有两种方法,上面说的是去继承Thread类,另一个方法是去实现Runable接口,然后将其示例传递给Thread构造器进行实例化一个线程,最后调用start方法来进行新建一个线程。注意:无论是哪种进行实现线程,新建线程的方法永远是进行调用start方法。线程的操作Sleep方法进行暂停,该方法是Thread类的类方法,用法如下:public static void main(String args) for(int i=0;i取款金额 进行取款可能会发生的情况是,两个线程a,b进行同时的访问,刚好b线程夹在a线程的确定余额和可用取款之间,那么就会发生两个线程都进行了取钱,最后则是余额是负数了。因此需要进行方法的同步下面是同步块的方法:public class Bank private String name;private int money;public Bank(String name,int money)this.money=money;=name;/进行存款public synchronized void deposit(int m)money+=m;public String getName() return name;public void setName(String name) = name;/进行取款public synchronized boolean withdrow(int n)if(money=n)money=-n;return true;elsereturn false;public static void main(String args) 注意:对于同一实例只能有一个线程去访问同步的方法(如果有两个实例那么一个实例如果有线程在执行同步方法,不会影响到另一实例的锁定即是:只要有实例,就会有一个锁定)也就是说如果有线程在进行去钱调用withdrow方法,那么该线程就会去加锁,其余的线程是无法进行调用其他的同步方法(存钱,取钱)此时只能进行等待获取锁。如果想进行锁定方法的局部,那么可以利用同步代码块synchronized (表达式)后面进行讲解。也可以定义类方法的同步:Public static synchronized void *这种同步锁定是针对类对象的实例进行锁定,假如类是SomeTh,则是针对SomeTh.class的实例进行锁定的,而一般的方法同步时针对对象的实例进行锁定(static是锁定了所有类对象的实例,而方法的锁定是针对实例的,不同的实例锁是不同的)线程的协作线程之间的协作是通过wait、notify、notifyAll来进行实现的:调用wait方法是首先是要获取到锁,执行此方法后就是线程进行暂停并释放锁Synchronized method() . obj.Wait();/开始进行等待,释放所调用notify 是会让等待的线程进行唤醒,当执行notifyAll时则是唤醒所有的线程。同样执行notify时要获取锁的权限,执行后就会去释放锁,唤醒的线程就会进行得到锁。如果没有锁定的线程去调用notfy或是notifyAll则会抛出异常IllegalMonitorStateException。对于notify和notifyAll方法的调用:两者很相似,除非你对程序很清楚,否则你最好还是调用notifyAll,这样会好点,不会出现有些线程挂死。Single Thread Excution Pattern下面的一个例子,一个过门的例子,每次只能有一个人进行过门,且过门的人会留下姓名和出生地址,当过门的人的姓名和地址的第一个字母不相同此时门的状态是不对的。为此我们设置三个类:1Gate类,里面有计数器、人的姓名和住址,会进行状态检查及过门方法,如下:/* * * author Administrator * 门,每次只能过一个人,且过人时其出生地和下面的第一个字母要相同 * */public class Gate private String name;private String address;private int count;public void pass(String name,String address)=name;this.address=address;count+;check();Overridepublic String toString() / TODO Auto-generated method stub return NO.+count+name:+name+ address:+address;public void check() if(name.charAt(0)!=address.charAt(0) System.out.println(*Broken*+toString(); 然后进行定义UseGate的线程,此处会引用Gate,初始化人的姓名和住址,并在线程中调用过门的方法如下:public class UseGateThread extends Thread private final Gate gate;private final String name;private final String address;public UseGateThread(Gate gate ,String name,String address)this.gate=gate;=name;this.address=address;/这个线程是进行过门Overridepublic void run() System.out.println(name+begin);while (true)gate.pass(name, address);进行主体类的测试 */public static void main(String args) /实例化们Gate gate=new Gate();/启动三个线程UseGateThread t=new UseGateThread(gate,zou,zz);UseGateThread t1=new UseGateThread(gate,xiao,xx);UseGateThread t2=new UseGateThread(gate,hu,h);t.start();t1.start();t2.start();结果如下:*Broken*NO.791803769name:xiao address:xx*Broken*NO.791805028name:xiao address:xx*Broken*NO.791806006name:xiao address:xx*Broken*NO.791806938name:xiao address:xx*Broken*NO.791807793name:xiao address:xx*Broken*NO.791808687name:xiao address:xx*Broken*NO.791809605name:xiao address:xx*Broken*NO.791810570name:xiao address:xx*Broken*NO.791811638name:xiao address:xx*Broken*NO.791812889name:xiao address:xx*Broken*NO.791813880name:xiao address:xx*Broken*NO.791815056name:xiao address:xx*Broken*NO.791817184name:xiao address:h*Broken*NO.791817336name:xiao address:xx你会发现姓名和住址的首字母相同也是broken的,这是因为在执行chek方法时候,其他的线程也正在执行pass方法此时的数据进行了改变,因此会出现上面的情况。由于多线程是多变的,即使测试出来没有问题也不能保证程序是安全的,这和测试的时间和地点及次数都有关系。上面我们在测试时使用了while的循环才发现问题的。由于上面的代码使多个线程进行访问了同一pass方法,导致会发生意外的情况,为了保证线程安全,我们知道其Gate是共享资源类,因此对于pass方法应该进行同步方法块来实现线程的安全对于单线程的设计模式:我们首先要确定临界资源点,找出共享的资源,确定出线程不安全的方法,然后加以进行线程同步的处理即可保证线程的安全;对于voliatile :在java中基本类型和对象的引用都是线程安全的,但对于long 和double却不是原子的操作,为了保证安全可以设定在syscronize方法中或是在定义字段时就指定voliatile下面是一个关于吃意大利面的例子,只有同时拿大勺子和叉子,才能去吃意大利面,有两个进程会去拿叉子和和勺子,为了避免死锁,这里规定先去获取勺子,在去拿叉子,当吃完后就去释放叉子和臊子,因此对于勺子和叉子需要进行同步处理,代码如下package question;public class Tool private final String name; public Tool(String name) = name; public String toString() return + name + ; public class EaterThread extends Thread private String name; private final Tool lefthand; private final Tool righthand; public EaterThread(String name, Tool lefthand, Tool righthand) = name; this.lefthand = lefthand; this.righthand = righthand; public void run() while (true) eat(); public void eat() synchronized (lefthand) System.out.println(name + takes up + lefthand + (left).); synchronized (righthand) System.out.println(name + takes up + righthand + (right).); System.out.println(name + is eating now, yam yam!); System.out.println(name + puts down + righthand + (right).); System.out.println(name + puts down + lefthand + (left).); public class Main public static void main(String args) System.out.println(Testing EaterThread, hit CTRL+C to exit.); Tool spoon = new Tool(Spoon); Tool fork = new Tool(Fork); new EaterThread(Alice, spoon, fork).start(); new EaterThread(Bobby, spoon, fork).start(); 叉子和勺子都是工具类Tool来进行实例的,在线程的run方法中进行了资源的同步,以保证只有一个线程去获取勺子后,在去获取叉子,另外线程传递是的工具顺序不要错了,不然就发生死锁了使用Executor使用Excutor能简化并发编程,Excutor允许你管理异步任务的执行,而无需显示的管理线程的生命周期。Excutor是启动任务的首选方法package zou;public class LiftOff implements Runnable protected int countDown=10; private static int taskCount=0; final int id=taskCount+; public String status() return #+id+(+(countDown0?countDown:LiftOff!)+); Overridepublic void run() while (countDown-0)System.out.println(status();Thread.yield();public class ExecutorTest /* * param args */public static void main(String args) /创建线程池ExecutorService executorService=Executors.newFixedThreadPool(5);for(int i=0;i10;i+) executorService.submit(new LiftOff();executorService.shutdown();/关闭任务,在此之前提交的任务执行完后,不在接受任务得到任务的返回结果集使用callable接口Runable是执行独立的工作任务,但没有返回值,如果想得到结果的返回值,那么需要实现Callable接口,Callbale是具有类型参数的泛型,它的类型参数是从方法call()返回的值(而不是run,并且必须使用ExecutorServices.subimti方法),需要注意的是Future的get方法是阻塞的,直到任务完成,返回结果。class TaskWithResult implements Callable private int id; public TaskWithResult(int id) this.id=id; Overridepublic String call() throws Exception System.out.println(#+id+work is run);return result TaskWithResult +id;public class CallabelDemo /* * param args */public static void main(String args) ExecutorService executorService=Executors.newFixedThreadPool(5);ArrayListFuture resultFuture=new ArrayListFuture();for(int i =0;i=5;i+)resultFuture.add(executorService.submit(new TaskWithResult(i);for(int i=0;iresultFuture.size();i+) try /调用future的get方法是阻塞的,直到任务完成返回结果 System.out.println(resultFuture.get(i).get(); catch (InterruptedException e) System.out.print(e); return;catch (ExecutionException e) System.out.print(e); return;finally/进行关闭操作executorService.shutdown();运行结果#0work is run#4work is run#5work is run#2work is run#1work is run#3work is runresult TaskWithResult 0result TaskWithResult 1result TaskWithResult 2result TaskWithResult 3result TaskWithResult 4result TaskWithResult 5原子性对于普通变量,其值的写入或读取如果对所有任务不是可视的话,那么就会非常危险,导致读取的值可能不正确。为了保证原子性可以设定为voliate变量,但并不是申明为voliate就是原子性的,例如i+这也不是原子性的,因为它存在2个步骤,先进行加1,然后在赋值到变量中,申明为voliate的变量只能保证简单的读取,如果 变量的值依赖上次的值或是依赖另一个变量,那么voliate变量也不是原子性的。使用原子性的类,保证并发我们应该少使用锁synchronized,这会使并发变弱,我们应该尽量使用支持原子性的操作类来代替同步或锁,例如下面的检测偶数生成器的程序:public abstract class IntGenerator private volatile boolean cancel;public abstract int next();public void cancel()cancel=true;public boolean isCancel()return cancel;public class EventChecker implements Runnable private IntGenerator generator; private int id; public EventChecker(IntGenerator generator,int id) this.generator=generator; this.id=id; Overridepublic void run() while(!generator.isCancel()int val=generator.next();if(val%2!=0)System.out.println(val+not even);generator.cancel();/取消/检测数据生成器public static void test(IntGenerator g,int count)System.out.println(press Control-c to exit);ExecutorService executorService=Executors.newFixedThreadPool(5);for(int i=0;icount;i+)executorService.submit(new EventChecker(g, count); executorService.shutdown();public class EventGenerator extends IntGenerator /private volatile int count=0; private AtomicInteger atointAtomicInteger=new AtomicInteger(0);Overridepublic int next() /count+;/count+; atointAtomicInteger.getAndAdd(2);return atointAtomicInteger.get();/* * param args */public static void main(String args) EventChecker.test(new EventGenerator(), 10);如上next方法是生成偶数的方法,在这里如果用简单count+,那么肯定是不安全的,两个线程可能会交叉的取值,导致有奇数,但如果用voliate变量也是不行的,因为+操作不是原子性的,所以这里改为利用AtomicInteger类来进行操作,当然可以利用同步机制synchronized来进行保证安全。本地线程存储防止任务在共享资源上进行冲突,除了同步我们还可以根除变量的共享。即利用线程本地存储,他是将相同变量为每个线程都创建不同的存储,这样如果有5个线程都使用到了x对象,那么就会有5个不同的存储对应5个线程,消除了变量的共享。class Accessor implements Runnable private final int id; public Accessor(int id) this.id=id; public String toString() return #+id+:+ThreadLocalVariableHolder.get(); Overridepublic void run() while(!Thread.currentThread().isInterrupted()ThreadLocalVariableHolder.increment();System.out.println(this);Thread.yield();public class ThreadLocalVariableHolder private static ThreadLocal valueLocal=new ThreadLocal()private Random random=new Random(50);Overrideprotected synchronized Integer initialValue() / TODO Auto-generated method stubreturn random.nextInt(1000);public static void increment()valueLocal.set(valueLocal.get()+1);public static int get()return valueLocal.get();/* * param args * throws InterruptedException */public static void main(String args) throws InterruptedException ExecutorService executorService=Executors.newFixedThreadPool(5);for (int i = 0; i 10; i+) executorService.submit(new Accessor(i);TimeUnit.SECONDS.sleep(3);executorService.shutdown();上面的程序虽然5个线程都是用的ThreadLocalVariableHodler对象,但由于是线程本地存储变量,因此变量没有共享,对于get和increment都没有进行同步方法。中断如果一个线程一直被阻塞,但我们希望终止它,此时需要将它跳出阻塞状态,那么我们可以利用中断。但并不是所有阻塞的线程能被中断,如正在I/O和在synchronized块上等待的是无法中断的(但对于异步I/O是可以直接中断,关闭底层的输入流也是能中断的)。class IOBlocked implements Runnable private InputStream inputStream; public IOBlocked(InputStream in) this.inputStream=in; Overridepublic void run() System.out.println(Waiting for read():);try inputStream.read(); catch (IOException e) if(Thread.currentThread().isInterrupted()System.out.println(interrupted from I/O);else throw new RuntimeException();System.out.println(Exiting IOBlocked.run();public class Interrupting public static void main(String args) throws Exception ExecutorService executorService=Executors.newCachedThreadPool();Runnable r=new IOBlocked(System.in);Future future=executorService.submit(r);TimeUnit.MICROSECONDS.sleep(100);System.out.println(interrupting +r.getClass().getName();future.cancel(true);System.out.println(interrupted send to +r.getClass().getName();运行结果:Waiting for read():interrupting zou.IOBlockedinterrupted send to zou.IOBlocked当线程意外终止时,此时我们应该做好清理工作,所以在代码中,我要尽量的写try-catch-finally语句来进行一些资源的清理。同步等待多个任务完成才进行下一个任务CountDownLatch可以用来进行控制同步多个任务完成后,才进行下一步的操作。可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用await()方法的都将阻塞,直至这个计数变为0。其他任务在工作结束时可以在该对象上调用countDown()来进行减小这个数值。CountDownLatch被用来设计只触发一次,计数值不能被重置。如果你需要重置的版本请使用CyclicBarrier版本。控制线程的完成进度可以利用信号量CountDownLatch来进行控制,首先是控制n个线程进行任务的操作,利用CountDownLatch来初始化数量,对于每个线程的执行相应减少1,直到为0则表示所有任务完成public class CountDownLatchDemo final static SimpleDateFormat sdf=new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); public static void main(String args) throws InterruptedException CountDownLatch latch=new CountDownLatch(2);/两个工人的协作 Worker worker1=new Worker(zhang san, 5000, latch); Worker worker2=new Worker(li si, 8000, latch); worker1.start();/ worker2.start();/ latch.await();/等待所有工人完成工作 System.out.println(all work done at +sdf.format(new Date(); static class Worker extends Thread String workerName; int workTime; CountDownLatch latch; public Worker(String workerName ,int workTime ,CountDownLatch latch) this.workerName=workerName; this.workTime=workTime; this.latch=latch; public void run() System.out.println(Worker +workerName+ do work begin at +sdf.format(new Date(); doWork();/工作了 System.out.println(Worker +workerName+ do work complete at +sdf.format(new Date(); latch.countDown();/工人完成工作,计数器减一 private void doWork() try Thread.sleep(workTime); catch (InterruptedException e) e.printStackTrace(); Wait与notifyAll()当任务之间需要进行协作时,此时需要用到wait与notify来进行协作控制。调用sleep时并没有释放锁,调用yield()也属于这种情况,理解这一点很重要。另外,当在一个任务方法里遇到了对wait()的调用的时候,线程的执行被挂起,对象上的锁被释放。因为wait释放锁,这就意味着另一个任务可以获得锁,因此在该对象中的其他synchronized方法可以在wait期间被调用。由于wait方法会释放锁,所以进行调用wati方法时必须获得锁。实际上,只能在同步控制方法或同步控制块里调用wait(),notify(),和notifyAll()方法。下面试一个程序的例子:将蜡涂到车上,后抛光它。显然抛光任务在涂蜡没有完成时是不能进行的,当抛光任务没有完成,下一步的涂蜡也是不能进行的。WaxOn涂蜡任务和WaxOff抛光任务都使用了Car对象,该对象在这些任务等待条件变化的时候,使用wait()和notifyAll()来挂起和重新启动这些任务:见java编程思想704Notify和notifyAll()Notify是唤醒等待锁的某个任务,而notifyall是唤醒等待锁的所有任务,但这个所有任务时等待的同一个锁,如果不是同一个锁的话,notifyall是并不能唤醒其它锁的等待任务的。生产者-消费者与队列Java现在提供了同步队列,比起直接去线程间的协作(wait,notify)效率要高,代码简单。BlockingQueue是一个同步队列,但队列是空的时候,如果去取东西,那么任务将会挂起,直到队列中有东西时才会唤醒。同样如果进行放入东西时,队列慢了,那么任务会挂起,直到有空的位置时才会被唤醒。class BlockqueBasketprivate BlockingQueue queue;public BlockqueBasket(BlockingQueue queue)this.queue=queue;/* * 进行生产苹果 * throws InterruptedException */public void produce() throws InterruptedExceptionqueue.put(put an apple);public void consume() throws InterruptedExceptionqueue.take(); class produceThred implements Runnable private BlockqueBasket basket; public produceThred(BlockqueBasket basket) this.basket=basket; Overridepublic void run() while(true)System.out.println(进行生产苹果);try duce(); catch (InterruptedException e) / TODO Auto-generated catch blockSystem.out.println(生产者中断); class cosumeThred implements Runnable private BlockqueBasket basket; public cosumeThred(BlockqueBasket basket) this.basket=basket; Overridepublic void run() while(true) System.out.println(进行消费苹果-); try basket.consume(); catch (InterruptedException e) / TODO Auto-generated catch blockSystem.out.println(消费者中断=); public class ProduceConsume /* * param args * throws InterruptedException */public static void main(String args) throws InterruptedException /规定大小BlockingQueue queue=new ArrayBlockingQueue(5);BlockqueBasket basket=new BlockqueBasket(queue);ExecutorService executorService=Executors.newCachedThreadPool();executorService.submit(new produceThred(basket);executorService.submit(new cosumeThred(basket);TimeUnit.SECONDS.sleep(5);executorSer

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论