黑马程序员——Java中 多线程笔记
2015-06-28 22:35
501 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、多线程概述
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
二、创建线程的第一种方式:
1、定义类,继承Thread
2、重写run()方法
3、创建Thread类的子类对象
4、用子类对象,调用Tread类的方法start()方法,开启线程
示例:
三、创建线程的第二种方式:
1、创建实现类 来实现Runnable接口、重写run方法
2、创建实现类对象
3、创建线程对象,传递参数为实现类对象
4、调用start方法
实现接口方式的好处:
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,
数据有效分离,较好的体现了面向对象的设计思想。
示例:
四、创建线程的第三种方式:(需要结合线程池来实现)
1、创建实现类 实现Callable接口,重写call方法。
2、工厂类调用静态方法,创建可以指定线程个数的 线程池 对象. 里面存有2个线程
3、线程池对象调用submit(Callable t)方法;参数为实现接口的子类对象
4、调用get()方法,获取call方法中的返回值。
示例:
五、Thread类的基本获取和设置方法
1)获取线程名字
Thread类中方法 String getName()
在一个不是Thread子类中获取名字:
Thread类中静态方法
static Thread currentThread()
返回正在运行的,当前线程对象 Thread类对象
继续使用线程对象的 getName()获取线程的名字
Thread类的方法 void setName(String name)设置线程名
Thread(String name) 用的是Thread类构造方法
子类使用super访问父类构造器
在Thread子类中,获取线程名字,直接使用父类方法getName()
在不是Thread子类中,通用代码,Thread.currentThread().getName()
示例:
六、线程的优先级的获取和设置
Thread类方法getPriority() 获取到优先级
Thread类方法setPriority(int t) 设置优先级
示例;
七、线程休眠,
Thread类的静态方法sleep(long 毫秒值)
这个方法写在哪个线程,哪个线程就会休眠
当休眠时间到了,现象自己醒来继续执行
示例:
八、线程加入方法join,等待当前线程终止
join()方法,使用方法的线程会优先执行完毕,剩下的线程抢CPU的时间
示例:
九、线程的让步
方法Thread类静态方法 void yield
当线程执行到yield方法的时候,主动将自己的CPU时间让出去
yield写在哪个线程中,哪个线程就会出现让步的效果
示例:
十、Thread类的方法
setDaemon(boolean b)
将线程设置为后台线程,守护线程,如果正在运行的所有线程都是守护线程的时候
JVM就退出 setDaemon(true) 必须写在start()之前
main start开启了t0线程,main没有代码了,结束了
示例:
十一、线程安全问题:
1)实现线程安全的基本思想:
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
2)同步的特点
同步的前提
多个线程
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,
这是很耗费资源的,无形中会降低程序的运行效率。
3)同步代码块
格式:
synchronized(对象){
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
示例:
十二、同步方法解决安全问题
同步方法
就是把同步关键字加到方法上。保证这个方法中的所有代码都是线程安全的
同步方法中的锁
在非静态方法中,锁就是本来对象。
自静态方法中,锁就是本类的.class文件进入内存后的对象
选择使用
如果锁对象是this,就可以考虑使用同步方法。
否则能使用同步代码块的尽量使用同步代码块。
示例:同步写在方法上
JDK5以后提供了一个新的锁对象Lock
void lock()
void unlock()
ReentrantLock
示例:
十三、死锁问题(重要面试)
死锁概述
死锁是由于系统资源不足以及对资源的访问顺序不当造成的一组线程
都互相等待对方的资源而陷入阻塞状态,如果多个线程都处于阻塞状态
而无法被唤醒时,就构成了死锁。同步中嵌套同步就容易造成死锁,
同步函数中有同步代码块,同步代码块中还有同步函数。
同步弊端
效率低
如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码
是指两个或者两个以上的线程在执行的过程中,
因争夺资源产生的一种互相等待现象
十四、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
等待唤醒机制方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。
同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程(一般是第一个)。
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,
只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,
而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
示例:通过唤醒机制实现数据依次出现
一、多线程概述
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
二、创建线程的第一种方式:
1、定义类,继承Thread
2、重写run()方法
3、创建Thread类的子类对象
4、用子类对象,调用Tread类的方法start()方法,开启线程
示例:
// 测试类 class ThreadTest { public static void method() { // 创建Thread子类对象 ThreadFirst tf0 = new ThreadFirst(); ThreadFirst tf1 = new ThreadFirst(); // 调用Thread类中的方法 tf0.start(); tf1.start(); } } class ThreadFirst extends Thread { // 重写run方法 public void run() { for (int x = 0; x < 100; x++) { System.out.println("run方法 " + Thread.currentThread().getName() + " " + x); } } }
三、创建线程的第二种方式:
1、创建实现类 来实现Runnable接口、重写run方法
2、创建实现类对象
3、创建线程对象,传递参数为实现类对象
4、调用start方法
实现接口方式的好处:
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,
数据有效分离,较好的体现了面向对象的设计思想。
示例:
class ThreadCecond implements Runnable{ //重写run方法 public void run(){ System.out.println("第二种创建方式"); } } class TreadTest2{ public static void method_1(){ //创建实现类接口对象 ThreadCecond tcc = new ThreadCecond(); //创建线程对象,传递参数实现接口的对象 Thread t0= new Thread(tcc); Thread t1 = new Thread(tcc); //调用Star()方法 t0.start(); t1.start(); } }
四、创建线程的第三种方式:(需要结合线程池来实现)
1、创建实现类 实现Callable接口,重写call方法。
2、工厂类调用静态方法,创建可以指定线程个数的 线程池 对象. 里面存有2个线程
3、线程池对象调用submit(Callable t)方法;参数为实现接口的子类对象
4、调用get()方法,获取call方法中的返回值。
示例:
// 定义实现类 class ThreadThree implements Callable<String>{ //重写call方法 public String call(){ return "你好"; } } class ThreadTest3{ public static void mehtod_3() throws Exception{ //工厂类调用静态方法,创建可以指定线程个数的 线程池 对象. 里面存有2个线程 ExecutorService ex = Executors.newFixedThreadPool(2); //线程池对象调用submit(Callable t)方法;参数为实现接口的子类对象,表示提交了用于执行的任务, //返回一个表示任务的未决结果的 Future。 Future<String> f1 = ex.submit(new ThreadThree()); Future<String> f2 = ex.submit(new ThreadThree()); //该 Future 的 get() 方法在成功完成时将会返回该任务的结果 //f1调用get()方法,获取call方法中的返回值。 System.out.println(f1.get()); System.out.println(f2.get()); } }
五、Thread类的基本获取和设置方法
1)获取线程名字
Thread类中方法 String getName()
在一个不是Thread子类中获取名字:
Thread类中静态方法
static Thread currentThread()
返回正在运行的,当前线程对象 Thread类对象
继续使用线程对象的 getName()获取线程的名字
Thread类的方法 void setName(String name)设置线程名
Thread(String name) 用的是Thread类构造方法
子类使用super访问父类构造器
在Thread子类中,获取线程名字,直接使用父类方法getName()
在不是Thread子类中,通用代码,Thread.currentThread().getName()
示例:
class ThreadName extends Thread{ ThreadName(){ super("旺财"); } public void run(){ // System.out.println(getName()); System.out.println(Thread.currentThread().getName()); } } public class ThreadDemo1 { //"main" public static void main(String[] args) { ThreadName tn = new ThreadName(); tn.setName("小强"); tn.start(); /*String name = Thread.currentThread().getName(); System.out.println(name);*/ } }
六、线程的优先级的获取和设置
Thread类方法getPriority() 获取到优先级
Thread类方法setPriority(int t) 设置优先级
示例;
示例: class ThreadPriority extends Thread{ public void run(){ for(int x = 0 ; x < 50 ; x++){ System.out.println(getName()+"run..."+x); } } } public class ThreadDemo2 { public static void main(String[] args) { ThreadPriority tp = new ThreadPriority(); ThreadPriority tp1 = new ThreadPriority(); //调用Thread类的方法 getPriority()获取线程优先级,返回int int x = tp.getPriority(); System.out.println(x); x = tp1.getPriority(); System.out.println(x); //将tp线程优先级设置到最高级 tp.setPriority(Thread.MAX_PRIORITY); tp.start(); tp1.start(); System.out.println(Thread.currentThread().getPriority()); } }
七、线程休眠,
Thread类的静态方法sleep(long 毫秒值)
这个方法写在哪个线程,哪个线程就会休眠
当休眠时间到了,现象自己醒来继续执行
示例:
class ThreadSleep extends Thread{ public void run(){ for(int x = 0 ; x < 5 ;x++){ //休眠2秒钟 try{ Thread.sleep(2000); }catch(InterruptedException ex){ } System.out.println("run方法"+x); } } } public class ThreadDemo3 { public static void main(String[] args) { //Thread.sleep(1000*60*15); ThreadSleep ts = new ThreadSleep(); ts.start(); } }
八、线程加入方法join,等待当前线程终止
join()方法,使用方法的线程会优先执行完毕,剩下的线程抢CPU的时间
示例:
class ThreadJoin extends Thread{ public void run(){ for(int x = 0 ; x < 30 ; x++){ System.out.println(getName()+" run.."+x); } } } public class ThreadDemo4 { public static void main(String[] args) throws InterruptedException{ //创建2个线程 new2个Thread子类对象 ThreadJoin t0 = new ThreadJoin(); ThreadJoin t1 = new ThreadJoin(); t0.start(); t0.join(); t1.start(); for(int x = 0 ; x < 30 ; x++){ System.out.println(" main.."+x); } } }
九、线程的让步
方法Thread类静态方法 void yield
当线程执行到yield方法的时候,主动将自己的CPU时间让出去
yield写在哪个线程中,哪个线程就会出现让步的效果
示例:
class ThreadYield extends Thread{ public void run(){ for(int x = 0 ; x < 50 ;x++){ //线程让步 Thread.yield(); System.out.println(getName()+"run.."+x); } } } public class ThreadDemo5 { public static void main(String[] args) { ThreadYield t0 = new ThreadYield(); ThreadYield t1 = new ThreadYield(); t0.start(); t1.start(); } }
十、Thread类的方法
setDaemon(boolean b)
将线程设置为后台线程,守护线程,如果正在运行的所有线程都是守护线程的时候
JVM就退出 setDaemon(true) 必须写在start()之前
main start开启了t0线程,main没有代码了,结束了
示例:
class ThreadDaemon extends Thread{ public void run(){ while(true){ System.out.println("run.."); } } } public class ThreadDemo6 { public static void main(String[] args) { ThreadDaemon t0 = new ThreadDaemon(); //设置为守护线程 t0.setDaemon(true); t0.start(); } }
十一、线程安全问题:
1)实现线程安全的基本思想:
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
2)同步的特点
同步的前提
多个线程
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,
这是很耗费资源的,无形中会降低程序的运行效率。
3)同步代码块
格式:
synchronized(对象){
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
示例:
/* 用同步代码块来解决 三个窗口售卖电影票100张的安全问题 */ class TicketRunnable implements Runnable{ private int ticket = 100; private Object obj = new Object(); public void run(){ while(true){ //添加同步代码块 synchronized(obj){ if(ticket > 0){ //捕获异常 try{Thread.sleep(5);}catch(Exception ex){} System.out.println(Thread.currentThread().getName()+" 出售第.."+ticket--); } } } } } public class ThreadDemo1 { public static void main(String[] args) { //创建Thread对象,传递Runnable接口实现类对象 TicketRunnable tr = new TicketRunnable(); Thread t0 = new Thread(tr); Thread t1 = new Thread(tr); Thread t2 = new Thread(tr); t0.start(); t1.start(); t2.start(); } }
十二、同步方法解决安全问题
同步方法
就是把同步关键字加到方法上。保证这个方法中的所有代码都是线程安全的
同步方法中的锁
在非静态方法中,锁就是本来对象。
自静态方法中,锁就是本类的.class文件进入内存后的对象
选择使用
如果锁对象是this,就可以考虑使用同步方法。
否则能使用同步代码块的尽量使用同步代码块。
示例:同步写在方法上
/* * 多线程模拟银行的存钱 * 假定账户余额是0,储户到银行去存钱 * 在两个窗口同时存钱,每次存100元,每个窗口存3次 * 余额 100 200 300 400 500 600 */ //定义银行类,功能存钱 class Bank{ private static int sum = 0; public static synchronized void add(int num){ // synchronized(Bank.class){ sum = sum + num; System.out.println(sum); // } } } //定义储户类,实现Runnable接口 class Cust implements Runnable{ private Bank b = new Bank(); public void run(){ for(int x = 0 ; x < 3 ; x++){ b.add(100); } } } public class ThreadDemo { public static void main(String[] args) { Cust c = new Cust(); Thread t0 = new Thread(c); Thread t1 = new Thread(c); t0.start(); t1.start(); } }
JDK5以后提供了一个新的锁对象Lock
void lock()
void unlock()
ReentrantLock
示例:
/* * 售票案例,使用JDK5提供新的锁 */ import java.util.concurrent.locks.*; class TicketRunnable implements Runnable{ private int ticket = 100; private Lock lock = new ReentrantLock(); //定义Lock接口的实现类对象 public void run(){ while(true){ // synchronized(this){ lock.lock(); if(ticket > 0){ try{Thread.sleep(5);}catch(Exception ex){} System.out.println(Thread.currentThread().getName()+" 出售第.."+ticket--); } // } lock.unlock(); } } } public class ThreadDemo1 { public static void main(String[] args) { TicketRunnable t = new TicketRunnable(); Thread t0 = new Thread(t); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t0.start(); t1.start(); t2.start(); } }
十三、死锁问题(重要面试)
死锁概述
死锁是由于系统资源不足以及对资源的访问顺序不当造成的一组线程
都互相等待对方的资源而陷入阻塞状态,如果多个线程都处于阻塞状态
而无法被唤醒时,就构成了死锁。同步中嵌套同步就容易造成死锁,
同步函数中有同步代码块,同步代码块中还有同步函数。
同步弊端
效率低
如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码
是指两个或者两个以上的线程在执行的过程中,
因争夺资源产生的一种互相等待现象
/* * 多线程争夺同一资源(对象锁) 产生的假死现象 -- 死锁 */ class ThreadDead implements Runnable{ private boolean flag; ThreadDead(boolean flag){this.flag=flag;} public void run(){ while(true){ if(flag){ //如果变量值是true,先进去A,在进去B synchronized(LockA.locka){ System.out.println("if...locka"); synchronized(LockB.lockb){ System.out.println("if...lockb"); } } }else{ //变量的值是false,先进去B,在进去A synchronized(LockB.lockb){ System.out.println("else...lockb"); synchronized(LockA.locka){ System.out.println("else...locka"); } } } } } } public class ThreadDemo2 { public static void main(String[] args) { ThreadDead ta = new ThreadDead(true); ThreadDead tb = new ThreadDead(false); new Thread(ta).start(); new Thread(tb).start(); } } //定义两个对象,当作同步中的锁,保证对象唯一 A B class LockA{ public static final LockA locka = new LockA(); } class LockB{ public static final LockB lockb = new LockB(); }
十四、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
等待唤醒机制方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。
同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程(一般是第一个)。
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,
只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,
而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
示例:通过唤醒机制实现数据依次出现
//定义学生对象 class Student{ String name; String sex; boolean flag = false; } //定义生产者线程,对Student类的成员赋值 class Input implements Runnable{ Student s ; Input(Student s){this.s = s;} public void run(){ int x = 0 ; while(true){ synchronized(s){ //对标记判断,如果是真,等待 if(s.flag) //等待 try{ s.wait();}catch(Exception ex){} if(x%2==0){ s.name = "张三"; s.sex = "男"; }else{ s.name = "李四"; s.sex = "女"; } x++; //赋值完成后,标记改成true s.flag = true; s.notify(); } } } } //定义消费者线程,对Student类成会变量打印 class Output implements Runnable{ Student s; Output(Student s){this.s=s;} public void run(){ while(true){ synchronized(s){ //判断标记,是假,不能赋值等待 if(!s.flag) //等待 try{s.wait();}catch(Exception ex){} System.out.println(s.name+"..."+s.sex); //标记改成false s.flag = false; s.notify(); } } } } public class ThreadDemo3 { public static void main(String[] args) { Student s = new Student(); Input in = new Input(s); Output out = new Output(s); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); } } 示例:改进为同步方法实现 /* * 线程通信代码,进行优化 * 成员变量私有,get set */ class Student{ private String name; private String sex; boolean flag ; //提供get set public synchronized void set(String name,String sex){ if(flag) try{this.wait();}catch(Exception ex){} this.name = name; this.sex = sex; flag=true; this.notify(); } public synchronized void get(){ if(!flag) try{this.wait();}catch(Exception ex){} System.out.println(name+"..."+sex); flag = false; this.notify(); } } class Input implements Runnable{ private Student s; Input(Student s){this.s=s;} public void run(){ int x = 0 ; while(true){ if(x%2==0) s.set("张三", "男"); else s.set("李四", "女"); x++; } } } class Output implements Runnable{ private Student s; Output(Student s){this.s = s;} public void run(){ while(true) s.get(); } } public class ThreadDemo4 { public static void main(String[] args) { Student s = new Student(); Input in = new Input(s); Output out = new Output(s); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); } }
相关文章推荐
- 黑马程序员——Java基础之继承,抽象类,接口
- 企业面试大全!职场必备。。。。。
- 面试题13:在O(1)时间删除链表结点
- 黑马程序员——Java基础:IO(二):File类、Properties类、打印流、序列流......
- 黑马程序员——OC基础学习(二)---对象方法和类方法的学习知识总结
- 面试jsp题动态包含和静态包含
- 剑指offer 面试题10
- 剑指Offer--面试题29:数组中出现超过一半的数字
- 黑马程序员——网络编程
- 黑马程序员——反射
- 黑马程序员——IO流
- 黑马程序员——集合框架
- 黑马程序员——String
- 黑马程序员——面向对象(四)
- 黑马程序员——面向对象(三)
- 黑马程序员——面向对象(二)
- 黑马程序员——面向对象(一)
- 黑马程序员——Java基础组成
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用
- 关于Java类加载双亲委派机制的思考(附一道面试题)