黑马程序员——5.多线程
来源:互联网 发布:淘宝发安能物流好恶心 编辑:程序博客网 时间:2024/05/01 16:52
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
多线程:
进程:正在进行中的程序(直译)
线程:进程中一个负责程序执行的控制单元(执行路径)
线程控制着进程的执行
- 一个进程中可以有多个执行路径,称之为多线程。
- 一个进程中至少要有一个线程。
- 开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
- 执行main函数的线程,该线程的任务代码都定义在main函数中。
- 负责垃圾回收的线程。
创建线程方式一:继承Thread类
- 定义一个类继承Thread类。
- 覆盖Thread类中的run方法。
- 直接创建Thread的子类对象创建线程。
- 调用start方法开启线程并调用线程的任务run方法执行。
//需要多线程执行的类要继承Thread类class Demo extends Thread{ private String name ; Demo(String name){ this.name = name; } //Run方法里定义的是线程要运行的任务代码 public void run(){ for(int x = 0; x < 10; x++){ //Thread.currentThread ():获取调用这个方法的线程对象; //.getName():通过线程对象获取线程名称 System.out.println(name + "...x=" + x + "...ThreadName=" + Thread.currentThread ().getName()); } }}class ThreadDemo{ //主线程 public static void main(String[] args){ //创建类的两个对象 Demo d1 = new Demo("旺财"); Demo d2 = new Demo("xiaoqiang"); //开启线程,调用run方法。 d1.start(); //开启额外的线程1 d2.start(); //开启额外的线程2 //线程1、线程2会与主线程抢占cpu资源 for(int x = 0; x < 20; x++){ System.out.println("x = " + x + "...over..." + Thread.currentThread().getName()); } }}
创建线程方式二:实现Runnable接口
- 定义类实现Runnable接口。
- 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
- 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
- 调用线程对象的start方法开启线程。
实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
- 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
//创建类实现Runnable接口class Demo implements Runnable{ //将要运行的代码封装到run方法中 public void run(){ show(); } public void show(){ for(int x = 0; x < 20; x++){ //获取线程名称 System.out.println(Thread.currentThread().getName() + "..." + x); } }}class ThreadDemo{ public static void main(String[] args){ //创建Demo类的一个对象 Demo d = new Demo(); //把Demo类对象作为参数创建Thread的两个对象 Thread t1 = new Thread(d); Thread t2 = new Thread(d); //执行Thread类对象,开启线程 t1.start(); t2.start(); }}
线程安全问题:
- 多个线程在操作共享的数据。
- 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
同步:
用于解决线程安全问题
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
使用同步的前提:
- 必须有两个或两个以上线程
- 必须是多个线程使用同一个锁
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
把需要被同步的代码放进synchronized里面
同步代码块的格式:
synchronized(任意对象(即锁)){ 需要被同步的代码;}
class Bank{ private int sum ; public void add(int num){ //用synchronized把需要同步的代码括起来,锁设为this synchronized(this ){ sum = sum + num; System. out.println("sum = " + sum); } }}class Cus implements Runnable{ private Bank b = new Bank(); //把需要多线程执行的代码放在run方法里面 public void run(){ for(int x = 0; x < 3; x++){ b.add(100); } }}class BankDemo{ public static void main(String[] args){ Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); //开启新线程 t1.start(); t2.start(); }}
同步函数:
在需要同步的代码的函数上加上synchronized修饰符,锁默认固定为this
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
class Bank{ private int sum ; //在有安全问题的函数上加上synchronized修饰符 public synchronized void add(int num){ sum = sum + num; System.out.println("sum = " + sum); }}
多线程下的单例模式:
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式会有安全问题,因为他满足了同步问题的条件:
1. 多个线程在操作共享的数据。 2. 操作共享数据的线程代码有多条。
所以要对懒汉式进行同步操作。
因为每次调用到对象都要先判断对象是否存在,如果直接使用同步之后再判断这种方式的话,创建对象之后的判断也会同步,效率太低,因此要使用双重判断的方式。
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 ; }}
死锁:
死锁常见情景之一:同步的嵌套。
当一段代码需要拿到锁1跟锁2,而另一段代码需要拿到锁2跟锁1,就很容易出现一个线程拿到了锁1,而另一个线程拿到锁2,都在等待对方的锁,而又不释放锁,就会造成死锁。写代码时要注意避免这种情况。
线程间通信:
多个线程在处理同一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
- wait():释放执行权,释放锁,让线程处于冻结状态,被wait的线程会被存储到线程池中。
- notify():唤醒线程池中的一个线程(任何一个都有可能)。
- notifyAll():唤醒线程池中的所有线程。
线程间通信容易出现死锁问题,要注意
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 con = lock .newCondition(); public void set(String name){ //获取锁lock lock.lock(); try{ //判断标记,为假就进行生产操作,为真就进入等待池 while(flag ) try{ //线程释放锁,进入等待池 con.await(); } catch(InterruptedException e){ e.printStackTrace(); } //进行生产操作 this.name = name + count; count++; System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name); //生产操作完成后,把标记置换为真 flag = true ; //生产完成后唤醒该锁等待池中所有线程 con.signalAll(); }finally{ //最后一定要释放锁 lock.unlock(); } } public void out(){ //获取锁lock lock.lock(); try{ //判断标记,为真就进行消费操作,为假就进入等待池 while(!flag ) try{ con.await(); } catch(InterruptedException e){ e.printStackTrace(); } //进行消费操作 System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name); //消费操作完成后,把标记置换为假 flag = false ; //生产完成后唤醒该锁等待池中所有线程 con.signalAll(); }finally{ //最后一定要释放锁 lock.unlock(); } }}class Producer implements Runnable{ private Resource r ; //通过构造函数传递资源对象 Producer(Resource r){ this.r = r; } public void run(){ while(true ){ //循环生产商品“烤鸭” r.set( "烤鸭"); } }}class Consumer implements Runnable{ private Resource r ; //通过构造函数传递资源对象 Consumer(Resource r){ this.r = r; } public void run(){ while(true ){ //循环消费商品 r.out(); } }}class ProducerConsumerDemo { public static void main(String[] args){ Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); //通过Producer、Consumer对象创建线程 Thread t0 = new Thread(pro); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); Thread t3 = new Thread(con); //启动线程 t0.start(); t1.start(); t2.start(); t3.start(); }}
/*
使用一个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 con = lock .newCondition(); //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者 Condition producer_con = lock .newCondition(); Condition consumer_con = lock .newCondition(); public void set(String name){ lock.lock(); try{ while(flag ) try{ //这时进入的是producer_con的等待池 producer_con.await(); } catch(InterruptedException e){ e.printStackTrace(); } this.name = name + count; count++; System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name); flag = true ; //生产完之后唤醒consumer_con等待池中的一个线程来消费 consumer_con.signal(); } finally{ lock.unlock(); } } public void out(){ lock.lock(); try{ while(!flag ) try{ //进入的是consumer_con的等待池 consumer_con.await(); } catch(InterruptedException e){ e.printStackTrace(); } flag = false ; //消费完之后唤醒producer_con等待池中的一个线程接着生产 producer_con.signal(); System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name); } finally{ lock.unlock(); } } } class Producer implements Runnable{ private Resource r ; Producer(Resource r){ this.r = r; } public void run(){ while(true ){ r.set( "烤鸭"); } } } class Consumer implements Runnable{ private Resource r ; Consumer(Resource r){ this.r = r; } public void run(){ while(true ){ r.out(); } } } class ProducerConsumerDemo { public static void main(String[] args){ Resource r = new Resource(); Producer pro = new Producer(r); Consumer con = new Consumer(r); Thread t0 = new Thread(pro); Thread t1 = new Thread(pro); Thread t2 = new Thread(con); Thread t3 = new Thread(con); t0.start(); t1.start(); t2.start(); t3.start(); } }
线程的停止:
线程执行完毕后需要停止线程,停止线程一般使用判断条件的方法
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成。
class StopThread implements Runnable{ private boolean flag = true; public void run(){ //while方法读到flase标记时退出循环,这时run方法中所有代码执行完毕,线程会自动停止 while(flag ){ System. out.println(Thread.currentThread().getName() + "..."); } } public void setFlag(){ 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 = 1; for(;;){ if(++num == 50){ //当num==50时,把标记置换为假 st.setFlag(); break; } System. out.println("main..." + num); } System. out.println("over" ); }}
但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。强制动作会发生InterruptedException,一定要记得处理。
class StopThread implements Runnable{ private boolean flag = true; public synchronized void run(){ while(flag){ try{ //把线程放入等待池 wait(); } //mian线程使用interrupt()方法强制将该线程恢复到运行状态,因此抛出了InterruptedException异常 catch(InterruptedException e){ System.out.println(Thread.currentThread().getName() + "..." + e); //这时将标记置换为假,退出while循环,线程停止 flag = false; } System.out.println(Thread.currentThread().getName() + "......"); } } public void setFlag(){ 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 = 1; for(;;){ if(++num == 50){ //num==50时,强制唤醒线程 t1.interrupt(); t2.interrupt(); break; } System.out.println( "main..." + num); } System.out.println( "over"); }}
线程类的其他方法
setDaemon():
即后台线程,setDaemon()把线程改成守护线程,这个操作必须在线程启动前调用。守护线程跟一般线程没什么区别,只有一点,当当前运行的所有的线程都是守护线程时,守护线程会自动退出。
Join():
申请当前线程的执行权,当前线程会等待申请线程执行完再执行
setPriority():
设置优先级,优先级priority:1-10,默认优先级是5,优先级大的获取cpu执行权的可能性要大一点。参数一般使用Thread.MAX_PRIORITY(即10),Thread.MIN_PRIORITY(即1),Thread.NORM_PRIORITY(即5),
String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- iperf 2.0.5的bug
- 51单片机之闹钟制作
- 多线程系列二——java线程间的互斥与同步
- PreferenceActivity
- SSH搭建web java
- 黑马程序员——5.多线程
- Valid Parentheses
- 拓扑排序
- 51单片机之蓝牙控制风扇
- 观后感
- String StringBuilder StringBuffer 对比总结
- 圆圈中剩下的最后的数字--20150928
- 习题6-3 二叉树的重建 UVa 536
- 微软解释关于Windows 10 收集用户数据那点事