java基础--8.多线程
来源:互联网 发布:网络大电影计划书 编辑:程序博客网 时间:2024/06/06 01:40
一、基本概念:程序 - 进程 - 线程
1. 程序 - 进程 - 线程
2.进程与多线程
3. 何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
需要一些后台运行的程序时。
二、线程的创建和启动
1. 创建线程的两种方式
(1)区别与联系
publicclass Thread extends Objectimplements Runnable
1>区别
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
2>比较哪个好?实现的方式较好。
① 解决了单继承的局限性。
② 如果多个线程有共享数据的话,建议使用实现方式,同时,共享数据所在的类可以作为Runnable接口的实现类。
(2) Thread类的有关方法
构造方法:
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,实现Runnable的run方法
Thread(Runnable target, Stringname):创建新的Thread对象
(3)线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。
2. 继承Thread类
1) 定义子类继承Thread类。
2) 子类中重写Thread类中的run方法。
3) 创建Thread子类对象,即创建了线程对象。
4) 调用线程对象start方法:启动线程,调用run方法
/* * 创建一个子线程,完成1-20之间自然数的输出。同样地,主线程执行同样的操作 * 创建多线程的第一种方式:继承java.lang.Thread类 */ //1.创建一个继承Thread的子类,表示为一个线程class SubThread extends Thread{ //2.重写Thread的run方法.方法内实现此子线程要完成的功能 public void run(){ for (int i = 1; i <= 20; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } }} public class TestThread { public static void main(String[] args) { //3.创建一个子类对象 SubThread st = new SubThread(); //4.调动现成的Start(),启动此线程;调用相应的run方法 //一个线程只能够执行一次start() //不能通过Thread实现类对象的run()去启动一个线程 st.start(); for (int i = 1; i <= 20; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
3.实现Runnable接口
1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。
3)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中。
5)调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
/* * 创建多线程的方式二:通过实现的方式 * * 对比一下继承的方式 vs 实现的方式 * 1.联系:public class Thread implements Runnable * 2.哪个方式好?实现的方式优于继承的方式 * why? ① 避免了java单继承的局限性 * ② 如果多个线程要操作同一份资源(或数据),更适合使用实现的方式 *///1.创建一个实现了Runnable接口的类class PrintNum1 implements Runnable { //2.实现接口的抽象方法 public void run() { // 子线程执行的代码 for (int i = 1; i <= 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }} public class TestThread2 { public static void main(String[] args) { //3.创建一个Runnable接口实现类的对象 PrintNum1 p = new PrintNum1(); //要想启动一个多线程,必须调用start() //4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程 Thread t1 = new Thread(p); //5.调用start()方法:启动线程并执行run() t1.start();//启动线程;执行Thread对象生成时构造器形参的对象的run()方法。 //再创建一个线程 Thread t2 = new Thread(p); t2.start(); }}
4.线程的优先级及调度
线程的优先级控制:MAX_PRIORITY(10);
涉及的方法:
getPriority():返回线程优先值
setPriority(intnewPriority) :改变线程的优先级
线程创建时继承父线程的优先级
5.使用多线程的优点
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
源代码文件:http://download.csdn.net/detail/qq_26553781/9751501
三、线程的生命周期
JDK中用Thread.State枚举表示了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
四、线程的同步机制(重点、难点)
解决线程安全问题
1.问题
前提:
如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题?
解决方式:
要求一个线程操作共享数据时,只有当其完成操作完成共享数据,其它线程才有机会执行共享数据。
2.例题
模拟火车站售票程序,开启三个窗口售票。TestWindow1.java
3.Synchronized的使用方法
(1)方式一:同步代码块:
谁是共享数据?
synchronized(同步监视器){
//操作共享数据的代码
}
注:
1.同步监视器:俗称锁,任何一个类的对象都可以才充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁!
2.使用实现Runnable接口的方式创建多线程的话,同步代码块中的锁,可以考虑是this。如果使用继承Thread类的方式,慎用this!
对于静态方法而言,使用当前类本身充当锁
3.共享数据:多个线程需要共同操作的变量。 明确哪部分是操作共享数据的代码。
class Window2 implements Runnable { int ticket = 100;// 共享数据 public void run() { while (true) { synchronized (this) {//this表示当前对象,本题中即为w if (ticket > 0) { try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"售票,票号为:" + ticket--); } } } }} public classTestWindow2 { public static void main(String[] args) { Window2w = newWindow2(); Threadt1 = newThread(w); Threadt2 = newThread(w); Threadt3 = newThread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); }}
(2)方式二:同步方法:
将操作共享数据的方法声明为synchronized
publicsynchronizedvoidshow (String name){
….
}
//操作共享数据的代码
注:
1.对于非静态的方法而言,使用同步的话,默认锁为:this。如果使用在继承的方式实现多线程的话,慎用!
2..对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。 Class clazz = Singleton.class
class Window4 implements Runnable { int ticket = 100;// 共享数据 public void run() { while (true) { show(); } } public synchronized void show() { if (ticket > 0) { try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "售票,票号为:" +ticket--); } }} public classTestWindow4 { public static void main(String[] args) { Window4w = newWindow4(); Threadt1 = newThread(w); Threadt2 = newThread(w); Threadt3 = newThread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); }}
4.互斥锁
(1)概述
在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
同步的局限性:导致程序的执行效率要降低
同步方法(非静态的)的锁为this。同步方法(静态的)的锁为当前类本身。
(2)单例设计模式之懒汉式
//关于懒汉式的线程安全问题:使用同步机制//对于一般的方法内,使用同步代码块,可以考虑使用this。//对于静态方法而言,使用当前类本身充当锁。class Singleton{ private Singleton() { // TODO Auto-generated constructor stub } private static Singleton instance = null; public static SingletongetInstance(){ if (instance == null) { synchronized (Singleton.class) { if (instance != null) { instance = new Singleton(); } } } return instance; }} public classTestSingleton { public static void main(String[] args) { Singletons1 = Singleton.getInstance(); Singletons2 = Singleton.getInstance(); System.out.println(s1 == s2); }}
(3) 释放锁的操作
当前线程的同步方法、同步代码块执行结束
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
(4)例题
银行有一个账户。
有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的
拓展问题:可否实现两个储户交替存钱的操作。需要使用线程通信!
/** * 银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。 * 1.是否涉及多线程?是。有2个储户(2种方式) * 2.是否共享数据?有,同一个账户 * 3.考虑线程同步(2种方式处理线程安全) */ class Account { double balance; //余额 public Account() { // TODO Auto-generated constructor stub } //存钱 public synchronized void deposit(double amt){ balance += amt; try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+balance); }} class Customer extends Thread{ Accountaccount; public Customer(Accountaccount) { this.account = account; } public void run(){ for (int i = 0; i < 3; i++) { account.deposit(1000); } }} public classTestAccount { public static void main(String[] args) { Accountaccount = newAccount(); Customercustomer1 = newCustomer(account); Customercustomer2 = newCustomer(account); customer1.setName("甲"); customer2.setName("乙"); customer1.start(); customer2.start(); }}
(5) 线程的死锁问题
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
解决方法
专门的算法、原则
尽量减少同步资源的定义
package thread.dead; //死锁的问题:处理线程同步时容易出现。//不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁//写代码时,要避免死锁!public classTestDeadLock { static StringBuffer sb1 = new StringBuffer(); static StringBuffer sb2 = new StringBuffer(); public static void main(String[] args) { new Thread() { public void run() { synchronized (sb1) { try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } sb1.append("A"); synchronized (sb2) { sb2.append("B"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); new Thread() { public void run() { synchronized (sb2) { try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } sb1.append("C"); synchronized (sb1) { sb2.append("D"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); } }
五、线程的通信
1.线程通信
Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
(1) wait() 方法
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
(2) notify()/notifyAll()
在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
2. 例 题
使用两个线程打印1-100. 线程1, 线程2 交替打印
//线程通信。如下的三个关键字使用的话,都得在同步代码块或同步方法中。//wait():一旦一个线程执行到wait(),就释放当前的锁。//notify()/notifyAll():唤醒wait的一个或所有的线程//使用两个线程打印 1-100. 线程1, 线程2 交替打印 class PrintNum implements Runnable { int num = 1; Objectobj= newObject(); public void run() { while (true) { synchronized (obj) { obj.notify(); if (num <= 100) { try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ ":" +num); num++; }else{ break; } try { obj.wait(); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public classTestCommunication { public static void main(String[] args) { PrintNump = newPrintNum(); Threadt1 = newThread(p); Threadt2 = newThread(p); t1.setName("甲"); t2.setName("乙"); t1.start(); t2.start(); }}
3. 经典例题:生产者/消费者问题
l 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
l 这里可能出现两个问题:
Ø 生产者比消费者快时,消费者会漏掉一些数据没有取到。
Ø 消费者比生产者快时,消费者会取相同的数据。
/* * 生产者/消费者问题 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下, * 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下, * 如果店中有产品了再通知消费者来取走产品。 分析: 1.是否涉及到多线程的问题?是!生产者、消费者 2.是否涉及到共享数据?有!考虑线程的安全 3.此共享数据是谁?即为产品的数量 4.是否涉及到线程的通信呢?存在这生产者与消费者的通信 */class Clerk{//店员 int product; public synchronized void addProduct(){//生产产品 if(product >= 20){ try { wait(); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ product++; System.out.println(Thread.currentThread().getName()+ ":生产了第" + product + "个产品"); notifyAll(); } } public synchronized void consumeProduct(){//消费产品 if(product <= 0){ try { wait(); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+ ":消费了第" + product + "个产品"); product--; notifyAll(); } }} class Producer implements Runnable{//生产者 Clerkclerk; public Producer(Clerk clerk){ this.clerk = clerk; } public void run(){ System.out.println("生产者开始生产产品"); while(true){ try { Thread.currentThread().sleep(100); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.addProduct(); } }}class Consumer implements Runnable{//消费者 Clerkclerk; public Consumer(Clerk clerk){ this.clerk = clerk; } public void run(){ System.out.println("消费者消费产品"); while(true){ try { Thread.currentThread().sleep(10); }catch(InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.consumeProduct(); } }} public classTestProduceConsume { public static void main(String[] args) { Clerkclerk = newClerk(); Producerp1 = newProducer(clerk); Consumerc1 = newConsumer(clerk); Threadt1 = newThread(p1);//一个生产者的线程 Threadt3 = newThread(p1); Threadt2 = newThread(c1);//一个消费者的线程 t1.setName("生产者1"); t2.setName("消费者1"); t3.setName("生产者2"); t1.start(); t2.start(); t3.start(); }}
源代码文件:http://download.csdn.net/detail/qq_26553781/9758503
- java基础--8.多线程
- Java基础/Java多线程
- Java基础-多线程基础篇
- java多线程基础
- Java多线程编程基础
- java多线程开发基础
- Java多线程基础
- Java -- 多线程技术基础
- 【java】多线程基础
- Java基础:多线程
- Java语言基础:多线程
- Java语言基础:多线程
- Java语言基础:多线程
- java多线程基础分析
- Java多线程编程基础
- java 多线程基础
- Java基础_多线程
- Java多线程基础
- android 动态设置actionbar背景色(代码修改)
- 随笔记
- linux下gunicorn+flask项目的部署
- 170209
- JSON 之FastJson解析
- java基础--8.多线程
- Vue.js——60分钟快速入门
- VS2015 Cordova Ionic移动开发
- LintCode 427:Generate Parentheses
- poj 1054The Troublesome Frog
- cocos2dx camera lua的设置
- ES6常用新特性总结
- java_ee_sdk-7u2的安装与 启动
- 判断一个整数是否是2的阶次方数