多线程开发
参考资料:
1.多线程概述
要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。
范例1:继承Thread类不能资源共享
package test; public class MyThreadDemo1 { public static void main(String args[]) { MyThread1 mt1 = new MyThread1(); MyThread1 mt2 = new MyThread1(); MyThread1 mt3 = new MyThread1(); mt1.start(); mt2.start(); mt3.start(); } static class MyThread1 extends Thread { private int ticket = 5; @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) if (ticket > 0)// 当余票大于0则买票 { System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票 } } } }
程序运行结果:
卖票:剩余ticket=5 卖票:剩余ticket=4 卖票:剩余ticket=3 卖票:剩余ticket=2 卖票:剩余ticket=1 卖票:剩余ticket=5 卖票:剩余ticket=4 卖票:剩余ticket=3 卖票:剩余ticket=5 卖票:剩余ticket=4 卖票:剩余ticket=3 卖票:剩余ticket=2 卖票:剩余ticket=1 卖票:剩余ticket=2 卖票:剩余ticket=1
以上程序通过继承Thread类实现多线程,程序中启动了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源(Ticket)共享的目的。
范例2:实现Runable接口可以资源共享
package test; public class MyRunableThreadDemo1 { public static void main(String args[]) { MyRunableThread1 mrt = new MyRunableThread1(); Thread t1 = new Thread(mrt); Thread t2 = new Thread(mrt); Thread t3 = new Thread(mrt); t1.start(); t2.start(); t3.start(); } static class MyRunableThread1 implements Runnable { private int ticket = 5; @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) if (ticket > 0)// 当余票大于0则买票 { System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票 } } } }
程序运行结果:
从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。
可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:
- 适合多个相同程序代码的线程去处理同一资源的情况。
- 可以避免由于java单继承特性带来的局限
- 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。
——————————————————————————————————————
范例2-2:增加输出线程名称的功能(ps:2012-6-9)
假如我们需要知道到底是哪一个线程在卖票,那么我们就必须输出线程的名称,对上述实例进行稍作修改该即可完成
首先是在实例化线程的时候可以传入线程名称,代码如下:
package test; public class MyRunableThreadDemo2 { public static void main(String args[]) { MyRunableThread1 mrt = new MyRunableThread1(); Thread t1 = new Thread(mrt, "t1"); Thread t2 = new Thread(mrt, "t2"); Thread t3 = new Thread(mrt, "t3"); t1.start(); t2.start(); t3.start(); } static class MyRunableThread1 implements Runnable { private int ticket = 5; @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票 } } } }
上述程序运行结果如下:
t1在卖票:剩余ticket=4
t3在卖票:剩余ticket=2
t2在卖票:剩余ticket=3
t2在卖票:剩余ticket=1
t3在卖票:剩余ticket=0
但是多次你运行发现结果各不相同。
————————————————————————————————————————————
2.多线程的同步
多次运行范例2我们发现得到的结果可能都不相同。下面列举两个可能的输出结果
范例2可能的输出结果1
卖票:剩余ticket=4 卖票:剩余ticket=5 卖票:剩余ticket=2 卖票:剩余ticket=3 卖票:剩余ticket=1
范例2可能的输出结果2
卖票:剩余ticket=4 卖票:剩余ticket=2 卖票:剩余ticket=1 卖票:剩余ticket=5 卖票:剩余ticket=1
为了更形象地说明线程同步,我们在范例2中加入进程延时机制Thread.sleep(300);。代码如下所示:
package test; public class MyRunableThreadDemo2 { public static void main(String args[]) { MyRunableThread1 mrt = new MyRunableThread1(); Thread t1 = new Thread(mrt, "t1"); Thread t2 = new Thread(mrt, "t2"); Thread t3 = new Thread(mrt, "t3"); t1.start(); t2.start(); t3.start(); } static class MyRunableThread1 implements Runnable { private int ticket = 5; @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) if (ticket > 0) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票 } } } }
再次运行多线程主程序,得到的结果如下:
出现票数为负的情况是因为:
线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,这样当线程1 ticket--之后ticket==0了,线程2再次执行ticket--那么ticket==-1。相当于多执行了一次 ticket--
3.两种线程同步方法
为了解决范例2中出现的问题,我们通过引入同步机制来解决问题。同步又分为同步代码块和同步方法两种类型。
范例3:同步代码块
package test; public class MyRunableThreadDemo3 { public static void main(String args[]) { MyRunableThread1 mrt = new MyRunableThread1(); Thread t1 = new Thread(mrt, "t1"); Thread t2 = new Thread(mrt, "t2"); Thread t3 = new Thread(mrt, "t3"); t1.start(); t2.start(); t3.start(); } static class MyRunableThread1 implements Runnable { private int ticket = 200; @Override public void run() { //错误 // synchronized (this) { // while (ticket > 0) { // //// try { //// Thread.sleep(3); //// } catch (InterruptedException e) { //// e.printStackTrace(); //// } // System.out.println(Thread.currentThread().getName() // + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票 // } // } /** * 同步代码块 之前说到ticket出现负数的原因是线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断, * 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1。相当于多执行了一次ticket--, * 因此我们将synchronized(this)放在了if(ticket>0)之前。for(int i=0;i<100;i++)用来表示连续执行100次。这里synchronized在 * for循环后面,因此每个线程都执行100次,彼此都有可能锁冲突。 * */ for(int i=0;i<100;i++) synchronized(this) { if(ticket>0) { try{ Thread.sleep(30); }catch(InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票 } } } } }
之前说到ticket出现负数的原因是“线程1在执行 --ticket之前,线程2 进入了 if (ticket > 0) 这个判断, 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1,相当于多执行了一次ticket--”,
因此我们将synchronized(this)放在了if(ticket>0)之前。我们还可以发现外部有一个执行一般次的for循环for(int i=0;i<100;i++),这用来表示run方法中的这synchronized代码块会被执行100次。需要注意的是只有执行完synchronized代码块才会释放锁。因此每一个线程都有100次可能出现锁冲突,一个线程需要等待另外一个线程执行完synchronized代码块中的内容以后才可以访问这个代码块。
范例4:同步方法
package test; public class MyRunableThreadDemo4 { public static void main(String args[]) { MyRunableThread1 mrt = new MyRunableThread1(); Thread t1 = new Thread(mrt, "t1"); Thread t2 = new Thread(mrt, "t2"); Thread t3 = new Thread(mrt, "t3"); t1.start(); t2.start(); t3.start(); } static class MyRunableThread1 implements Runnable { private int ticket = 200; @Override public void run() { for (int i = 0; i < 100; i++) { sale(); } } public synchronized void sale() { if (ticket > 0) { try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票 } } } }
这里将操作ticket资源的内容单独抽取出来作为一个方法来调用,然后同步该方法,保证同一时间只有一个进程调用该方法。
4生产者消费者案例
范例5:
package test; public class ThreadDeadLock { public static void main(String args[]) { Info info = new Info(); // info作为参数传入两个线程当中 ProducerThread pt = new ProducerThread(info); ConsumerThread ct = new ConsumerThread(info); Thread producer = new Thread(pt, "producer"); Thread consumer = new Thread(ct, "consumer"); producer.start(); consumer.start(); } //资源类 static class Info { private String name = "name"; private String content = "content"; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } // 生产者线程 static class ProducerThread implements Runnable { private Info info = null; // 构造函数,其参数是资源 public ProducerThread(Info info) { this.info = info; } @Override public void run() { // boolean flag=false; for (int i = 0; i < 10; i++) { this.info.setName("name" + i); try { Thread.sleep(90); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.info.setContent("content" + i); } } } static class ConsumerThread implements Runnable { private Info info = null; // 构造函数,其参数是资源 public ConsumerThread(Info info) { this.info = info; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.info.getName() + ":-->" + this.info.getContent()); } } } }
程序输出:
范例5存在两个问题:
- 问题1:假设ProducerThread线程刚设置了name信息,还没有设置content信息,此时程序就切换到了ConsumerThread线程,那么ConsumerThread线程获取的是当前name与之前的content,所以输出结果会出现(比如:name1:-->content0)。这是因为name与content没有一起设置的原因,或者是说name与content信息不同步。
- 问题2:生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。(比如出现name1:-->content0 name1:-->content0,这个可以通过调节sleep来控制)
问题1 解决:加入同步
如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。
范例6
package test; public class ThreadDeadLock2 { public static void main(String args[]) { Info info = new Info(); // info作为参数传入两个线程当中 ProducerThread pt = new ProducerThread(info); ConsumerThread ct = new ConsumerThread(info); Thread producer = new Thread(pt, "producer"); Thread consumer = new Thread(ct, "consumer"); producer.start(); consumer.start(); } // 资源类 static class Info { private String name; private String content; //getter and setter public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } // 获取name与content信息 public synchronized void get() { try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.getName() + ":-->" + this.getContent()); } // 设置name与content信息 public synchronized void set(String name, String content) { this.setName(name); try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.setContent(content); } } // 生产者线程 static class ProducerThread implements Runnable { private Info info = null; // 构造函数,其参数是资源 public ProducerThread(Info info) { this.info = info; } @Override public void run() { for (int i = 0; i < 10; i++) { this.info.set("name" + i, "content" + i); } } } static class ConsumerThread implements Runnable { private Info info = null; // 构造函数,其参数是资源 public ConsumerThread(Info info) { this.info = info; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.info.get(); } } } }
程序运行结果
从程序的运行结果中可以发现,问题1:信息错乱的问题已经解决,但是依然存在问题2:重复读取的问题,以及漏读信息的问题(比如上述输出中name9:-->content9重复读取,而name5:-->content5被漏读了)。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。
Object类是所有类的父类,在此类中有以下几种方法是对线程操作有所支持的,如下表所示:
如果想让生产者不重复生产,消费者不重复消费,可以设置有一个标志位,假设标志位为boolean型变量,如果标志位内容为true,则表示可以生产,但是不能取走,如果标志位内容为false,则表示可以取走,不能生产。操作流程如下:
问题解决2——加入等待与唤醒
package edu.sjtu.erplab.thread; class Info{ private String name="name"; private String content="content"; private boolean flag=true; public synchronized void set(String name,String content) { if(!flag)//标志位为false,不可以生产 { try { super.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.setName(name); try { Thread.sleep(30); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.setContent(content); flag=false;//修改标志位为false,表示生产者已经完成资源,消费者可以消费。 super.notify();//唤醒消费者进程 } public synchronized void get() { if(flag) { try { super.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { Thread.sleep(30); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.getName()+":-->"+this.getContent()); flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。 super.notify();//唤醒生产者进程。 } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } class Producer implements Runnable{ private Info info=null; public Producer(Info info) { this.info=info; } @Override public void run() { boolean flag=false; for(int i=0;i<10;i++) if(flag) { this.info.set("name+"+i, "content+"+i); flag=false; } else { this.info.set("name-"+i, "content-"+i); flag=true; } } } class Consumer implements Runnable{ private Info info=null; public Consumer(Info info) { this.info=info; } @Override public void run() { for(int i=0;i<10;i++) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.info.get(); } } } public class ThreadDeadLock { public static void main(String args[]) { Info info=new Info(); Producer pro=new Producer(info); Consumer con=new Consumer(info); new Thread(pro).start(); new Thread(con).start(); } }
程序运行结果:
name-0:-->content-0 name+1:-->content+1 name-2:-->content-2 name+3:-->content+3 name-4:-->content-4 name+5:-->content+5 name-6:-->content-6 name+7:-->content+7 name-8:-->content-8 name+9:-->content+9
ps:添加注释,解释Object.wait()与this.wait()(2012-5-12)
package edu.sjtu.erplab.thread; class Info{ private String name="name"; private String content="content"; private boolean flag=true;//标识位,表示生产者是否可以进行生产,true表示可以,false表示不可以。 //生产者线程调用的方法 public synchronized void set(String name,String content) { if(!flag)//如果不可以进行生产,则对Info对象加锁 { try { super.wait();//让拥有Info对象的生产者线程进入等待状态 //this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果可以进行生产,则设置name与content属性 this.setName(name); // try { // Thread.sleep(30); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } this.setContent(content); System.out.println("生产:"+this.getName()+":-->"+this.getContent()); flag=false;//修改标志位为false,表示可以消费,不能生产。 super.notify();//唤醒拥有Info对象的线程 } //消费者线程调用的方法 public synchronized void get() { if(flag)//如果flag==true表示可以生产不可以消费, { try { super.wait();//让拥有Info资源的消费者线程进入等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // try { // Thread.sleep(30); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } System.out.println("消费:"+this.getName()+":-->"+this.getContent()); flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。 super.notify();//唤醒生产者进程。 } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } //生产者线程 class Producer implements Runnable{ //生产者线程拥有的资源 private Info info=null; //构造函数 public Producer(Info info) { this.info=info; } @Override public void run() { boolean flag=false; for(int i=0;i<10;i++) if(flag) { this.info.set("name+"+i, "content+"+i); flag=false; } else { this.info.set("name-"+i, "content-"+i); flag=true; } } } //消费者线程 class Consumer implements Runnable{ //消费者线程拥有的资源 private Info info=null; //构造函数 public Consumer(Info info) { this.info=info; } @Override public void run() { for(int i=0;i<10;i++) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.info.get(); } } } public class ThreadDeadLock { public static void main(String args[]) { Info info=new Info(); Producer pro=new Producer(info); Consumer con=new Consumer(info); new Thread(pro).start(); new Thread(con).start(); } }
运行结果
生产:name-0:-->content-0 消费:name-0:-->content-0 生产:name+1:-->content+1 消费:name+1:-->content+1 生产:name-2:-->content-2 消费:name-2:-->content-2 生产:name+3:-->content+3 消费:name+3:-->content+3 生产:name-4:-->content-4 消费:name-4:-->content-4 生产:name+5:-->content+5 消费:name+5:-->content+5 生产:name-6:-->content-6 消费:name-6:-->content-6 生产:name+7:-->content+7 消费:name+7:-->content+7 生产:name-8:-->content-8 消费:name-8:-->content-8 生产:name+9:-->content+9 消费:name+9:-->content+9
相关推荐
在Java编程中,多线程并发是...总之,Java的多线程并发实例可以帮助我们更好地理解和实践线程控制、同步机制以及经典的设计模式,提升我们的编程能力。通过不断学习和实践,我们可以编写出高效、安全的多线程并发程序。
最后,Java并发库还包含了很多其他有用的工具,如Semaphore(信号量)用于控制同时访问特定资源的线程数量,CyclicBarrier(循环屏障)和CountDownLatch(计数器门锁)用于多线程间的协作,以及Lock接口及其实现如...
### Java多线程并发知识点详解 #### 一、Java多线程并发简介 在现代软件开发中,特别是在Java这样的主流编程语言中,多线程并发技术是提高程序执行效率、优化资源利用的关键手段之一。本篇文章将深入探讨Java中的...
但同时,多线程并发也会引入一些问题,如数据竞争和同步问题。 为了解决这些问题,Java提供了多种同步机制。`synchronized`关键字用于控制对共享资源的访问,确保同一时间只有一个线程可以执行特定代码块,从而避免...
本项目聚焦于使用Java的Socket进行多线程并发控制,并结合Hibernate ORM框架与MySQL数据库进行数据存储。下面将详细阐述这些技术及其应用。 首先,Java Socket是Java提供的用于实现网络上不同计算机间进程通信的...
Java多线程并发实战与源码分析是Java开发中至关重要的一部分,它涉及到程序性能优化、系统资源高效利用以及复杂逻辑的正确同步。本书主要聚焦于Java多线程的基础理论和实际应用,虽然书中实例和源码相对较少,但仍然...
综上所述,"java多线程查询数据库"是一个涉及多线程技术、线程池管理、并发控制、分页查询等多个方面的复杂问题。通过理解和掌握这些知识点,我们可以有效地提高数据库操作的效率和系统的响应速度。
Java多线程与并发编程是Java开发中至关重要的一部分,它涉及到如何高效地利用CPU资源,以实现程序的并行执行。在操作系统层面,多任务和多进程是通过分配不同的内存空间来实现的,而线程则共享同一进程的内存,这...
Java多线程与并发编程是Java语言中用于处理多任务执行的关键技术,它能够帮助开发者设计出能够有效应对高并发请求的应用程序。在现代的线上(Online)和离线(Offline)应用中,合理利用多线程技术可以大幅提高系统...
本文将围绕“Java多线程高并发相关资料收集”这一主题,详细探讨这两个领域的核心知识点。 首先,多线程是指在单个程序中同时执行多个线程。Java提供了一个强大的多线程支持,允许开发者创建、管理和控制多个执行...
同步控制是并发程序必不可少的重要手段,本文我们将通过重入锁、读写锁、信号量、倒计数器和循环栅栏以及他们的实例来介绍Java并发程序中的同步控制。 目录线程安全 Thread Safety重入锁 ReentrantLock读写锁 ...
资源名称:Java多线程与并发库高级应用视频教程22集资源目录:【】01传统线程技术回顾【】02传统定时器技术回顾【】03传统线程互斥技术【】04传统线程同步通信技术【】04传统线程同步通信技术_分割纪录【】05线程...
然而,为了更好地管理和控制线程,Java并发包提供了如`ExecutorService`、`Future`、`Callable`等高级接口和类,它们简化了多线程编程,并提供了更好的资源管理。 在处理数据库数据时,我们通常会使用JDBC(Java ...
Java多线程同步是Java编程中关键的并发概念,它涉及到如何在多个线程访问共享资源时保持数据的一致性和完整性。`java.util.concurrent`包是Java提供的一个强大的并发工具库,它为开发者提供了多种线程安全的工具,...
Java多线程并发编程是Java开发中的重要领域,特别是在服务器端和高并发应用中不可或缺。`java.util.concurrent`包提供了丰富的工具类,帮助开发者更好地管理线程和优化并发性能。以下将详细介绍标题和描述中涉及的...
在IT领域,多线程和高并发是两个关键概念,特别是在Java编程中,它们对于构建高效、可扩展的系统至关重要。下面将详细解释这两个概念及其在Java中的实现和应用。 多线程是指在一个应用程序中同时运行多个独立的执行...
总之,掌握Java多线程的生命周期、创建、启动、同步以及线程池的使用是编写高效、稳定并发程序的基础。理解这些知识点对于解决并发编程中的问题,比如资源竞争、死锁、线程安全性等问题,至关重要。在实际开发中,...
Java 多线程之并发锁 Java 中的多线程编程是指在一个程序中同时运行多个线程,以提高程序的执行效率和响应速度。在多线程编程中,线程间的同步是非常重要的,因为不同的线程可能会同时访问同一个共享资源,导致数据...
在Java编程中,多线程并发...以上是Java多线程并发访问的主要解决方案,理解并熟练运用这些技术,可以有效地解决并发编程中遇到的问题,提升程序的稳定性和效率。在实际开发中,应根据具体需求选择合适的并发控制策略。