黑马程序员——java基础---多线程
来源:互联网 发布:明星基金经理 知乎 编辑:程序博客网 时间:2024/05/09 04:11
黑马程序员——java基础—多线程
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
1、多线程的概念
进程、线程、多线程的概念:
- 进程:正在进行中的程序。
- 线程:进程中一个负责程序执行的控制单元(执行路径)。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
2、创建线程方式一:继承Thread类
定义一个类继承Thread类。
覆盖Thread类中的run方法。
直接创建Thread的子类对象创建线程。
调用start方法开启线程并调用线程的run方法执行。
3、创建线程方式二:实现Runnable接口
定义类实现Runnable接口。
覆盖接口中的run方法,并将线程的任务代码封装到run方法中。
通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
调用线程对象的start方法开启线程。
实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
- 避免了java单继承的局限。所以,创建线程的第二种方式较为常用。
4、线程的安全问题
线程安全问题产生的原因:
- 多个线程在操作共享的数据。
- 操作共享数据的线程代码有多条。
线程安全问题的解决方案
思路:就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。(或者在函数上加synchronized修饰符即可)
同步代码块的格式:
synchronized(对象) { 需要被同步的代码 }
同步的特点:
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须是多个线程并使用同一把锁。
利用同步代码块解决安全问题案例:
- 需求:储户,两个,每个都到银行存钱,每次存100元,共存三次。
思路:
定义一个Cons(储户)类并实现Runnable接口;
复习Cons类中的run方法,执行Bank(银行)类中存储这个动作;
将存储这个动作上锁,一次只能让有一个客户存钱。
创建两个线程并开启线程。
- 代码:
class Bank{ private int sum; public void add(int num) { synchronized(this)//保证每次只有一个线程在调用 { sum+=num; System.out.println("sum="+sum); } }}//实现Runnable接口class Cons implements Runnable{ Bank b=new Bank(); //复写Runnable中的run方法。 public void run() { for(int x=0;x<3;x++) b.add(100); }}class BankDemo { public static void main(String[] args) { //把资源进行封装,传入给线程 Cons c=new Cons(); //创建两个线程 Thread t1=new Thread(c); Thread t2=new Thread(c); //开启线程 t1.start(); t2.start(); }}
- 输出结果:
5、多线程下的单例模式
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式存在安全问题,可以使用同步函数解决。
- 代码:
class Single{ private Single(){} private static Single s=null; public static Single getInstance() { //第一次判断,提高了效率,若对象已存在就不用判断锁了 if(s==null) { //保证每次只有一个线程在调用 synchronized(Single.class) { if(s==null) s=new Single(); } } return s; }}
6、死锁
定义:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限地阻塞,因此程序不可能正常终止。
产生死锁的原因:
- 因为系统资源不足。
- 进程运行推进的顺序不合适。
- 资源分配不当等。
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
示例代码:
class Test implements Runnable{ //flag是标识 private boolean flag; Test(Boolean flag) { this.flag=flag; } public void run() { if(flag) { while (true) { //锁a synchronized(MyLock.locka) { System.out.println(Thread.currentThread().getName()+"……if lacka……"); } //锁b synchronized(MyLock.lockb) { System.out.println(Thread.currentThread().getName()+"……if lackb……"); } } } else { while (true) { //锁b synchronized(MyLock.lockb) { System.out.println(Thread.currentThread().getName()+"……else lackb……"); } //锁a synchronized(MyLock.locka) { System.out.println(Thread.currentThread().getName()+"……else lacka……"); } } } }}class MyLock{ public static final Object locka=new Object(); public static final Object lockb=new Object();}class DeadLockDemo { public static void main(String[] args) { Test t1=new Test(true); Test t2=new Test(false); //创建线程 Thread d1=new Thread(t1); Thread d2=new Thread(t2); //开启线程 d1.start(); d2.start(); }}
7、线程间通信
- 等待/唤醒机制涉及的方法啊:
- wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
- notify():唤醒线程池中的一个线程(任何一个都有可能)
- notifyAll():唤醒线程池中的所有线程。
- wait和sleep的区别:
- wait可以指定时间也可以不指定,sleep必须指定时间。
- 在同步中,对CPU的执行权和锁的处理不同。
- wait:释放执行权,释放锁。
- sleep:释放执行权,不释放锁。
- 示例(生产者-消费者问题):
- 代码:
class Resource{ private String name; private String sex; private boolean flag=false; public synchronized void set(String name,String sex) { //flag为true时,线程t1冻结,等待唤醒 if(flag) try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } this.name=name; this.sex=sex; flag=true; notify(); } public synchronized void out() { //flag为false时,线程t2冻结,等待唤醒 if(!flag) try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name+"……"+sex); flag=false; notify(); }}//输入,实现Runable接口class Input implements Runnable{ Resource r; Input(Resource r) { this.r=r; } public void run() { int x=0; while(true) { if (x==0) { r.set("小明","男"); } else { r.set("小红","女"); } x=(x+1)%2; } }}//输出,实现Runnable接口class Output implements Runnable{ Resource r; Output(Resource r) { this.r=r; } public void run() { while(true) { r.out(); } }}class ResourceDemo{ public static void main(String[] args) { //封装资源 Resource r=new Resource(); //创建任务 Input in=new Input(r); Output out=new Output(r); //创建线程 Thread t1=new Thread(in); Thread t2=new Thread(out); //开启线程 t1.start(); t2.start(); }}
- 输出结果:
8、JDK1.5新特性
- JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。
- Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
- lock():获取锁。
- unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
- Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
- Condition接口中的await方法对应于Object中的wait方法。
- Condition接口中的signal方法对应于Object中的notify方法。
- Condition接口中的signalAll方法对应于Object中的notifyAll方法。
- 示例(生产者、多消费者问题:一个Lock、两个Condition)
- 代码:
import java.util.concurrent.locks.*;class Resource{ private String name; private int count=1; private boolean flag=false; //创建一个锁对象 Lock lock=new ReentrantLock(); //通过已有的锁获取该锁上的监视对象 Condition pro_con=lock.newCondition(); Condition con_con=lock.newCondition(); public void set(String name) { //获取锁 lock.lock (); try { while(flag) { try { //生产者监视器冻结 pro_con.await(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name=name+count; count++; System.out.println(Thread.currentThread().getName()+"……生产"+this.name); flag=true; //唤醒消费者监视器 con_con.signal(); } finally { //释放锁 lock.unlock(); } } public void out() { //获取锁 lock.lock(); try { while(!flag) { try { //消费者监视器冻结 con_con.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"…… 消费"+name); flag=false; //唤醒生产者监视器 pro_con.signal(); } finally { //释放锁 lock.unlock(); } }}//输入class Producer implements Runnable{ Resource r; Producer(Resource r) { this.r=r; } public void run() { while(true) { r.set("烤鸭"); } }}//输出class Consumer implements Runnable{ Resource r; Consumer(Resource r) { this.r=r; } public void run() { while(true) { r.out(); } }}class ProducerConsumerDemo2{ 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(); }}
- 输出结果:
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础多线程
- 黑马程序员——Java基础->多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础--多线程
- 黑马程序员——java基础--多线程
- 黑马程序员——Java基础--- 多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础-多线程
- 黑马程序员——Java基础:多线程
- 单文件上传工具类封装解析
- 在一个uiViewController中加载一个通过xib创建的uiView,结果界面不正确
- 递归算法1
- Twitter-Snowflake,64位自增ID算法详解
- JSON字符串与NSDictionary和NSArray之间的转化
- 黑马程序员——java基础---多线程
- IO流的使用(二)
- 递归算法2
- C++设计模式-Observer观察者模式
- C# override与new的区别
- cxf客户端
- 5.补发----数据的物理结构,线性结构与链式结构
- 6.补发-关于数据类型
- AFNetworking (2.6.1)出现错误code = 1016和3084 的解决方案