黑马程序员——java的多线程
2015-06-27 15:51
621 查看
------- android培训、java培训、期待与您交流! ----------
前言:通过观看毕向东老师的java基础视频,查漏补缺,将一些自己掌握的还不牢固的知识写出来,希望和大家交流分享。
一、进程与线程
进程:正在执行的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:把进程中的功能分成各个部分,每个部分有一个线程控制。它是进程中的一个独立的控制单元。
多线程:在一个进程中有多个线程执行的方式,就叫做多线程。如在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
多线程的意义:多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
二、创建线程的方式
两种方法:继承Thread类和实现Runnable接口(建议使用,因为java是单继承,多实现,一旦继承Thread类后就不能继承其他类了)。
1)继承方式步骤:
1.定义类继承Thread。
2.复写Thread中的run方法。 目的:将自定义代码存储在run方法中,让线程运行。
3.创建定义类的实例对象。相当于创建一个线程。
4.用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
代码示例:
执行是随机、交替执行的,每一次运行的结果都会不同
2)实现方式步骤:
1.定义类实现Runnable的接口。
2.覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
3.通过Thread类创建线程对象。
4.将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
5.调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
代码示例:
三、线程的状态
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
图示:
四、线程的安全
1.导致线程出现安全问题的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
简单来说:a、多个线程访问出现延迟。b、线程随机性
2.解决办法——线程同步:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
使用synchronized(同步)关键字
同步有两种方式:同步代码块和同步函数
a、同步代码块
格式:
synchronized(监听类对象){
需要被同步的代码
}
同步可以解决安全问题的根本原因就在那个对象上,其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
代码示例:
格式: 在函数上加上synchronized修饰符即可。
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
代码示例:
a.同步的前提
1)必须要有两个或者两个以上的线程。
2)必须是多个线程使用同一个锁。
b.同步的利弊
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
c.如何寻找多线程中的安全问题
1)明确哪些代码是多线程运行代码。
2)明确共享数据。
3)明确多线程运行代码中哪些语句是操作共享数据的。
五、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
如:类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
经典示例:
当同步中嵌套同步时,就有可能出现死锁现象。
代码示例:
运行结果:程序卡住,不能继续执行
七、线程间通信
多个线程在操作同一个资源,但是操作的动作不同。
使用wait(),notify(),notifyAll()这几个方法来实现线程的通信。
代码示例:生产者与消费者问题
几个问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
八、join方法
当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。
九、setPriority()方法用来设置优先级
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级
十、yield()方法可以暂停当前线程,让其他线程执行。
-------
android培训、java培训、期待与您交流! ----------
前言:通过观看毕向东老师的java基础视频,查漏补缺,将一些自己掌握的还不牢固的知识写出来,希望和大家交流分享。
一、进程与线程
进程:正在执行的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:把进程中的功能分成各个部分,每个部分有一个线程控制。它是进程中的一个独立的控制单元。
多线程:在一个进程中有多个线程执行的方式,就叫做多线程。如在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
多线程的意义:多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
二、创建线程的方式
两种方法:继承Thread类和实现Runnable接口(建议使用,因为java是单继承,多实现,一旦继承Thread类后就不能继承其他类了)。
1)继承方式步骤:
1.定义类继承Thread。
2.复写Thread中的run方法。 目的:将自定义代码存储在run方法中,让线程运行。
3.创建定义类的实例对象。相当于创建一个线程。
4.用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
代码示例:
package thread; /** * 用继承的方式创建线程 * @author songwenju * */ class Test extends Thread{ public Test(String name) { super(name); } //覆写run方法 @Override public void run() { //打印线程内部信息,当执行输出20次后线程结束。 for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+"---->"+i); } } } public class ThreadByExtend{ public static void main(String[] args) { //开启一个线程 new Test("线程1").start(); //开启第二个线程 new Test("线程2").start(); //打印主线程的信息 for (int i = 0; i < 20; i++) { System.out.println("主线程---->"+i); } } }运行结果:
执行是随机、交替执行的,每一次运行的结果都会不同
2)实现方式步骤:
1.定义类实现Runnable的接口。
2.覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
3.通过Thread类创建线程对象。
4.将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
5.调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
代码示例:
package thread; /** * 实现Runnable接口来创建线程类 * @author songwenju * */ class Ticket implements Runnable{ private int num = 20; //实现run方法 @Override public void run() { while (true) { if (num > 0) { System.out.println(Thread.currentThread().getName()+"卖出第————>"+num--+"张票"); }else { break; } } } } public class ThreadByImplement { public static void main(String[] args) { //定义一个线程对象。 Ticket ticket = new Ticket(); //开启三个线程:使用Thread(Runnable target, String name)这个构造方法分配新的 Thread 对象。 new Thread(ticket,"窗口一").start(); new Thread(ticket,"窗口二").start(); new Thread(ticket,"窗口三").start(); } }结果:
三、线程的状态
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
图示:
四、线程的安全
1.导致线程出现安全问题的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
简单来说:a、多个线程访问出现延迟。b、线程随机性
2.解决办法——线程同步:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
使用synchronized(同步)关键字
同步有两种方式:同步代码块和同步函数
a、同步代码块
格式:
synchronized(监听类对象){
需要被同步的代码
}
同步可以解决安全问题的根本原因就在那个对象上,其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
代码示例:
package thread; /** * 为run方法加同步代码块 * @author songwenju * */ class Ticket implements Runnable{ private int num = 50; //实现run方法 @Override public void run() { while (true) { synchronized (Ticket.class) { if (num > 0) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖出第————>"+num--+"张票"); }else { break; } } } } } public class ThreadByImplement { public static void main(String[] args) { //定义一个线程对象。 Ticket ticket = new Ticket(); //开启三个线程:使用Thread(Runnable target, String name)这个构造方法分配新的 Thread 对象。 new Thread(ticket,"窗口一").start(); new Thread(ticket,"窗口二").start(); new Thread(ticket,"窗口三").start(); } }b、同步函数
格式: 在函数上加上synchronized修饰符即可。
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
代码示例:
package thread; /** * 为run方法加同步代码块 * @author songwenju * */ class Ticket2 implements Runnable{ private int num = 50; //实现run方法 @Override public void run() { for (int i = 0; i < 1000; i++) { sale(); } } public synchronized void sale(){ if ( 4000 num > 0) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖出第————>"+num--+"张票"); } } } public class ThreadDemo2 { public static void main(String[] args) { //定义一个线程对象。 Ticket2 ticket = new Ticket2(); //开启三个线程:使用Thread(Runnable target, String name)这个构造方法分配新的 Thread 对象。 new Thread(ticket,"窗口一").start(); new Thread(ticket,"窗口二").start(); new Thread(ticket,"窗口三").start(); } }3.注意事项:
a.同步的前提
1)必须要有两个或者两个以上的线程。
2)必须是多个线程使用同一个锁。
b.同步的利弊
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
c.如何寻找多线程中的安全问题
1)明确哪些代码是多线程运行代码。
2)明确共享数据。
3)明确多线程运行代码中哪些语句是操作共享数据的。
五、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
如:类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
经典示例:
package thread; /** * 加同步的单例模式——懒汉式 * @author songwenju * */ public class Single { private static Single s = null; private Single(){} public static Single getInstance() { if (s == null) { synchronized (Single.class) { if (s == null) { s = new Single(); } } } return s; } }六、死锁
当同步中嵌套同步时,就有可能出现死锁现象。
代码示例:
package thread; /** * 一个死锁程序 * @author songwenju * */ //定义一个类来实现Runnable,并覆写run方法。 class LockTest implements Runnable{ private boolean flag; public LockTest(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { while (true) { synchronized (LockClass.locka) { System.out.println(Thread.currentThread().getName()+"--->if_locka"); synchronized (LockClass.lockb) { System.out.println(Thread.currentThread().getName()+"--->if_lockb"); } } } }else { while (true) { synchronized (LockClass.lockb) { System.out.println(Thread.currentThread().getName()+"--->else_lockb"); synchronized (LockClass.locka) { System.out.println(Thread.currentThread().getName()+"--->else_locka"); } } } } } } //定义两个锁 class LockClass{ static Object locka = new Object(); static Object lockb = new Object(); } public class DeadLock { public static void main(String[] args) { //创建2个进程,并启动 new Thread(new LockTest(true),"线程1").start(); new Thread(new LockTest(false),"线程2").start(); } }
运行结果:程序卡住,不能继续执行
七、线程间通信
多个线程在操作同一个资源,但是操作的动作不同。
使用wait(),notify(),notifyAll()这几个方法来实现线程的通信。
代码示例:生产者与消费者问题
package thread; /** * 生产者与消费者问题 * 假设有一个容器 * 如果里面为空时,一个线程负责生产资源 * 如果不为空,一个线程负责消费资源 * @author songwenju * */ //资源 class Resource{ private String name; private String weight; //为true时表示有值可取,为false时表示需要新值放入。 private boolean flag = false; public synchronized void setInput(String name, String weight){ if (flag) { try { wait();//如果有资源,等待被消费 } catch (Exception e) { e.printStackTrace(); } } this.name = name; this.weight = weight; System.out.println("生产者生产了"+name + ",重量为:"+weight); flag = true;//表示有资源 notify();//唤醒等待 } public synchronized void getOutput(){ if (!flag) { try { wait();//如果没有资源,等待被生产 } catch (Exception e) { e.printStackTrace(); } } System.out.println("消费者消费了 "+name +",重量 为:"+weight); flag = false;//表示资源被消费 notify();//唤醒等待 } } //控制生产者的线程 class Input implements Runnable{ private Resource r; public Input(Resource r) { this.r = r; } @Override public void run() { int x = 0; for (int i = 0; i < 100; i++) { if (x == 0) { r.setInput("牛奶", "10kg"); }else { r.setInput("面板", "20kg"); } x = (x+1)%2; //控制交替打印 } } } //控制消费者的线程 class Output implements Runnable{ private Resource r; public Output(Resource r) { this.r = r; } @Override public void run() { while (true) { r.getOutput(); } } } public class ResourceDemo { public static void main(String[] args) { Resource r = new Resource(); //表示操作的是同一个资源 new Thread(new Input(r)).start(); //开启生产者线程 new Thread(new Output(r)).start(); //开启消费者线程 } }执行结果:
几个问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
八、join方法
当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。
九、setPriority()方法用来设置优先级
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级
十、yield()方法可以暂停当前线程,让其他线程执行。
-------
android培训、java培训、期待与您交流! ----------
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统