黑马程序员——java学习日记四
来源:互联网 发布:vmware player linux 编辑:程序博客网 时间:2024/05/18 00:14
-----Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、线程的概念
早期为了提高计算机利用效率实现多进程,后来,逐渐有了线程。线程可以看成是一种轻量级进程,是程序执行的最小单元,是进程中的一个独立的控制单元。一个进程中至少有一个线程。java虚拟机启动后会有一个进程java.exe,该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程被称作主线程,是一个前台线程。同时,该进程还有其他线程,例如负责垃圾回收的是一个线程。
二、线程的创建
创建线程的方式一:继承Thread类
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
3,调用线程的start方法, 该方法两个作用:启动线程,调用run方法。
class Demo extends Thread{ public void run() { for(int x=0;x<60;x++) System.out.println("demo run---"+x); }}class ThreadDemo{ public static void main(String[] args) { Demo d = new Demo(); d.start();//启动线程1 并执行run() for(int x=0; x<60; x++) System.out.println("Hello World!=="+x); }}创建线程的第二种方式:实现Runable接口
步骤:
1,定义类实现Runnable接口
2,覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
class Ticket implements Runnable { private int tick =100; //覆盖Runnable接口的run方法 public void run() { while(true) { if(tick > 0) { System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); } } }}class TicketDemo{ public static void main(String[] args) { Ticket t = new Ticket(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); }}三、线程的互斥与同步
线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。
由于程序中常有多个线程操作共享资源,而这些操作对于数据的安全性的要求很高,如果数据在被某一线程操作时,被另一线程修改而发送变化,那个这个操作的结果是不确定,给该程序所要实现的现实意义造成严重影响。
在java中,synchronized代码块实现了线程的同步(相当于给共享资源加了互斥锁):
synchronized(对象) { 需要被同步的代码 }
对象如同锁,持有锁的线程可以在以上代码中执行,没有持有锁的线程即使获得Cpu的执行权,也无法执。
同步的前提:1 必须要有两个或两个以上的线程。2 必须是多个线程使用同一个锁
线程同步的利弊:1 解决了多线程的安全问题 2 多个线程需要判断锁,降低了效率。
1 同步代码块示例:
//简单的卖票程序,多个窗口同时卖票。 class Ticket implements Runnable { private int tick = 1000; Object obj = new Object(); public void run() { while(true) { synchronized(obj) { if(tick>0) { //try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); } } } } } class TicketDemo2 { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); } }2 同步函数示例:
使用两个线程来卖票,一个线程在同步代码块中,一个线程在同步函数中,都在执行卖票动作。该程序验证了同步函数使用的锁对象是类对象本身。
class Ticket implements Runnable{ private int tick = 100; Object obj = new Object(); boolean flag = true; public void run() { if(flag) { while(true) { synchronized(this) { if(tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+ "...code:"+tick--); } } } } else while(true) show(); } public synchronized void show()//this { if(tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"...show...:"+tick--); } }}class ThisLockDemo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start();//线程1启动并执行run() t.flag = false; t2.start(); }}静态同步函数使用的锁是本类对象所属类的字节码对象,可将上述示例改写以验证。
3死锁示例:
由于竞争资源或彼此通信而造成两个或两个以上线程阻塞的现象称为死锁 ,若无外力作用,它们都将无法推进下去。
/*死锁。锁1中套着锁2锁2中套着锁1线程1持有锁1,需求锁2, 线程2持有锁2,需求锁1,两者都持有对方所需资源,但对于己有资源不释放,两个线程处于僵持状态,都无法进行下去*/ class MyLock { static Object locka = new Object();//锁1 static Object lockb = new Object();//锁2}de>class Test implements Runnable{ private boolean flag; Test(boolean flag) { this.flag = flag; } public void run() { if(flag) { while(true) { synchronized(MyLock.locka)//锁1 { System.out.println(Thread.currentThread().getName()+"...if locka "); synchronized(MyLock.lockb)//锁2 { System.out.println(Thread.currentThread().getName()+"..if lockb"); } } } } else { while(true) { synchronized(MyLock.lockb)//锁2 { System.out.println(Thread.currentThread().getName()+"....else lockb"); synchronized(MyLock.locka)//锁1 { System.out.println(Thread.currentThread().getName()+".....else locka"); } } } } }}class DeadLockTest{ public static void main(String[] args) { //锁就是共享资源 Thread t1 = new Thread(new Test(true)); Thread t2 = new Thread(new Test(false)); t1.start(); t2.start(); }}四 停止线程
由于java中的stop方法,而Thread类中并没有提供其他停止线程的方法,所以目前,停止线程的方法就是使run方法结束,开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:当线程处于冻结状态,就不会读取判断是否要进行下一次循环的标记,那么线程就无法结束。当没有指定的方式让冻结的线程恢复到运行状态,这时需要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以让线程结束。查询API文档,可使用Thread类提供的interrupt()方法。
class StopThread implements Runnable{ private boolean flag = true;//该标志用控制线程结束 public synchronized void run() { while(flag) { try { this.wait();//t1,t2 都在这里阻塞,它们不再判断循环标记,所以程序无法停下来 } catch(InterruptedException e) { System.out.println(Thread.currentThread().getName()+"...Exception"); //flag=false;// } System.out.println(Thread.currentThread().getName()+"...run"); } } public void changeFlag() { flag = false; }}class StopThreadDemo{ public static void main(String[] args) { StopThread st = new StopThread(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); t1.start(); t2.start(); int num = 0; while(true) { if(60==num++) { st.changeFlag();//当flag变为假时,t1,t2将结束 // t1.interrupt(); // t2.interrupt(); break; } System.out.println(Thread.currentThread().getName()+"......."+num); } System.out.println("over"); }}
五、生产者消费者问题
生产者消费者问题是一个多线程同步问题的经典案例,该问题描述了两个共享固定大小缓冲区的线程——一个“生产者”和“消费者”。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
建立一个小型生产者--消费者模型分析此问题:一个生产者将生产的数据往一个内存中装(给一片内存区域赋值),一个消费者从该内存中取数据(打印该内存区域的值),并保证输出一个--打印一个。
分析:1 首先要保证两个线程同步
2 可以给共享的资源加一个标记,初始标记为假,代表资源为空,输入输出线程依靠判断该标记来决定生产/输出还是阻塞
步骤: 输入线程判断标记,若标记为假则输入内容,将标记改为真,并唤醒输出线程, 否则进入阻塞状态
输出线程判断标记,若标记为真则将内容取走,将标记改为假,并唤醒输入线程,否则进入阻塞状态
使用到的Thread类中的方法: 1 wait(); 2 notify();
因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
示例1:
class Res{ private String name; private String sex; private boolean flag = false;//设立一个标记用来标志资源是否为空 //生产数据 public synchronized void put(String name,String sex) { if(flag) try{this.wait();}catch(Exception e){} this.name = name; this.sex = sex; flag = true; this.notify(); } //消费数据 public synchronized void out() { if(!flag) try{this.wait();}catch(Exception e){} System.out.println(name+"........"+sex); flag = false; this.notify(); }}//生产者class Input implements Runnable{ private Res r ; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true) { if(x==0) r.put("mike","man"); else r.put("丽丽","女女女女女"); x = (x+1)%2; } }}//消费者class Output implements Runnable{ private Res r ; Output(Res r) { this.r = r; } public void run() { while(true) { r.out(); } }}class InputOutputDemo2{ public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); }}改造一下以上的示例:将单个生产者与消费者变成多个生产者和消费者
class Resource{ //商品名称 private String name; //用来记录生产的商品编号 private int count = 1; private boolean flag = false; //生产商品 public synchronized void set(String name) { if(flag) try{this.wait();}catch(Exception e){} this.name = name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); flag = true; this.notify(); } //消费商品 public synchronized void out() { if(!flag) try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name); flag = false; this.notify(); }}class Producer implements Runnable{ private Resource res; Producer(Resource res) { this.res = res; } public void run() { while(true) { res.set("+商品+"); } }}class Consumer implements Runnable{ private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while(true) { res.out(); } }}class ProducerConsumerDemo { public static void main(String[] args) { Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); }}以下代码会产生如下的错误输出:
Thread-0...生产者...+商品+--689
Thread-3...消费者............+商品+--689
Thread-2...消费者............+商品+--689
Thread-0...生产者...+商品+--690
Thread-1...生产者...+商品+--691
Thread-3...消费者............+商品+--691
错误:一个产品生产出来被消费了多次
分析原因:
由程序可知t1,t2为生产者线程, t3,t4为消费者线程
t1进入set中,执行一遍函数体后判断标记为真,于是t1阻塞, (t1)------阻塞队列
t2进入set中,判断标记为真,执行到wait处,t2阻塞, (t1,t2)
t3执行,t3执行一遍函数体后t3唤醒t1然后阻塞 (t2,t3)
t4执行, 判断标记为假 阻塞 (t2,t3,t4)
t1执行,执行剩余步骤,判断标记为真,唤醒t2 然后阻塞 (t3,t4,t1)
t2执行,没有判断标记,将t1生产的产品覆盖了---------------------------在wait后继续运行,没有判断标记
解决:将if(flag)改为while(flag)
若是使用notify()则导致线程全部等待,程序无法进行下去
解决:使用notifyAll()唤醒全部的线程
修改后的部分代码:
class Resource{ private String name; private int count = 1; private boolean flag = false; public synchronized void set(String name) { while(flag) try{this.wait();}catch(Exception e){} this.name = name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); flag = true; this.notifyAll(); } public synchronized void out() { while(!flag) try{wait();}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name); flag = false; this.notifyAll(); }}JDK1.5 中提供了多线程升级解决方案。
在java.util.concurrent.locks包中有个Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
lock()----加锁
unlock()--解锁
Condition---与锁有关的接口--一个锁上可以有多个condition
await()--取代--wait()
singal()--取代--notify()
Lock--取代--synchronized
以下代码改写了synchronized同步代码块
class Resource{ private String name; private int count = 1; private boolean flag = false; private Lock lock = new ReentrantLock();//创建一把锁 private Condition condition_pro = lock.newCondition();//创建与锁关联的对象 private Condition condition_con = lock.newCondition();//利用锁关联对象调用await,singal等 //设置资源 public void set(String name)throws InterruptedException { lock.lock();//上锁 try { while(flag) condition_pro.await();//使生产者线程阻塞到condition_pro对象关联的线程池 this.name = name+"--"+count++; System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); flag = true; condition_con.signal();//唤醒 消费消费线程池中的消费者线程 } finally { lock.unlock();//解锁 } } //打印资源 public void out() { lock.lock(); try { while(!flag) condition_con.await(); System.out.println(Thread.currentThread().getName()+"...消费者...."this.name"); flag = false; condition_pro.signal(); } finally { lock.unlock(); } }}
这也是一个经典的程序同步问题。一些共享数据被多个线程共享,但其中某些线程可能只要求读数据(称为Reader),另一些进程则要求修改数据(称为Writer)。就共享数据而言,Reader和Writer是两组并发线程共享一组数据区,要求:
(1)允许多个读者同时执行读操作;
(2)不允许读者、写者同时操作;
(3)不允许多个写者同时操作。
java提供了读写锁帮助我们解决相关问题,读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。
示例:
import java.util.Random;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockTest { public static void main(String[] args) { Queue3 q = new Queue3(); Reader r = new Reader(q); Writer w = new Writer(q); new Thread(r).start(); new Thread(r).start(); new Thread(w).start(); new Thread(w).start(); }}/* * 读者写者问题 * */class Reader implements Runnable{ private Queue3 q = null; Reader(Queue3 q) { this.q = q; } public void run() { while(true) { try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } q.get(); } }}class Writer implements Runnable{ private Queue3 q = null; Writer(Queue3 q) { this.q = q; } public void run() { while(true) { q.put(new Random().nextInt(10000)); } }}class Queue3{ //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据 private Object data = null; //创建一把读写锁 ReadWriteLock rwl = new ReentrantReadWriteLock(); public void get() { //上读锁 rwl.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+" be ready to read data!"); try { Thread.sleep((long)(Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" have read data :"+data); } finally { //释放读锁 rwl.readLock().unlock(); } } public void put(Object data) { //上写锁 rwl.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+" be ready to write data!"); try { Thread.sleep((long)(Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } //写数据 this.data = data; System.out.println(Thread.currentThread().getName()+" have write data: "+data); } finally { rwl.writeLock().unlock(); } } }以上程序运行部分结果:
Thread-2 be ready to write data!
Thread-2 have write data: 3029
Thread-3 be ready to write data!
Thread-3 have write data: 2637
Thread-1 be ready to read data!
Thread-0 be ready to read data!
可以发现:写线程的写操作是具有原子性的,读线程的读操作并不具有原子性。
0 0
- 黑马程序员——java学习日记四
- 黑马程序员学习日记四
- 黑马程序员--【学习日记四】——java面向对象(二)
- 黑马程序员[andriod]java基础学习日记四——面向对象的总结
- 黑马程序员——黑马学习日记1-Java基础知识
- 黑马程序员——学习日记2 学习java概述
- 黑马程序员_ JAVA学习日记—JAVA中的多线程
- 黑马程序员java学习日记——基本常识(一)
- 黑马程序员java学习日记——基本常识(二)
- 黑马程序员java学习日记——函数
- 黑马程序员java学习日记——基本常识(一)
- 黑马程序员java学习日记——基本常识(二)
- 黑马程序员java学习日记——函数
- 黑马程序员java学习日记——数组
- 黑马程序员java学习日记——异常和多线程
- 黑马程序员java学习日记——字符串String
- 黑马程序员java学习日记——集合框架
- 黑马程序员——java基础学习日记(10)
- Swift 2.0 异常处理
- aspx+mssql后台登陆注入拿webshell
- 学习Android推送功能笔记(1)
- JAVA多线程 总结
- 浏览器兼容性解决方法
- 黑马程序员——java学习日记四
- iOS的管理机制
- 了解Android中的接口回调机制
- 基于servlet的方式实现文件上传
- iOS学习之sqlite的创建数据库,表,插入查看数据
- python多线程
- goaccess-nginx日志分析工具简介
- Android http网址链接图片的处理及显示
- 短信验证