黑马程序员——多线程
来源:互联网 发布:java 汉字转英文缩写 编辑:程序博客网 时间:2024/06/14 03:36
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
1.概述
进程:一个程序的执行。
线程:程序中单个顺序的流控制。
一个进程中至少要有一个线程,可以含有多个线程。一个进程中的多个线程分享CPU(并发的或以时间片的方式),共享内存(如多个线程访问同一对象)。Java支持多线程,如:Object中wait(),notify()。
多线程解决了多部分代码同时运行的问题。但线程太多,会导致效率的降低。
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1)执行main函数的线程,该线程的任务代码都定义在main函数中。
2)负责垃圾回收的线程。System类的gc方法告诉垃圾回收器调用finalize方法,但不一定立即执行。
线程体是由run()方法来实现的。线程启动后,系统就自动调用run()方法。通常,run()方法执行一个时间较长的操作,如一个循环,显示一系列图片或下载一个文件等。
对线程的基本控制:
1)线程的启动:start()
2)线程的结束:设定一个标记变量,以结束相应的循环及方法。
3)暂时阻止线程的执行:try{ Thread.sleep( 1000 );} catch( InterruptedException e ){ }
4)设定线程的优先级:setPriority(int priority)方法:MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY
线程的状态:
2.创建线程的两种方法
1.继承Thread类
a.定义一个类继承Thread类。
b.覆盖Thread类中的run方法。
c.直接创建Thread的子类对象创建线程。
d.调用start方法开启线程并调用线程的任务run方法执行。
class Demo extends Thread{ private String name; Demo(String name){ this.name = name; } public void show(){ for( int x = 0; x < 10; x++){ //通过Thread的getName方法获取线程的名称。 System.out.println(name + "...x = " + x + "...ThreadName = " + Thread.currentThread().getName()); } }}public class ThreadDemo { public static void main(String[] args){ Demo d1 = new Demo("Amy"); Demo d2 = new Demo("Lucy"); //开启线程,调用run方法 d1.start(); d2.start(); for (int x = 0; x < 20; x++){ System.out.println("x = " + x + "...over..." + Thread.currentThread().getName()); } }}
2.实现Runnable接口
a.定义类实现Runnable接口;
b.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
c.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
d.调用线程对象的start方法开启线程。
实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
class Demo2 implements Runnable{ //覆盖接口中的run方法,将线程的任务代码封装到run方法中。 public void run(){ show(); } public void show(){ for (int x = 0; x < 20 ; x++){ System.out.println(Thread.currentThread().getName()+"..."+x); } }}public class ThreadDemo2 { public static void main(String[] args){ Demo2 d = new Demo2(); //通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。 Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start(); t2.start(); }}
3.线程同步
线程安全问题产生的原因:同时运行的线程需要共享数据。
线程安全问题的解决方案:Java引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个monitor(监视器),它上面一个称为“互斥锁(lock, mutex)”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。关键字synchronized 用来与对象的互斥锁联系。
同步代码块:
synchronized(对象){
需要被同步的代码;
}
同步函数:
synchronized 放在方法声明中。
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
/*售票问题*/class Ticket implements Runnable{ private int num = 100; boolean flag = true; public void run(){ if (flag){ while(true){ //同步代码块,锁是任意的对象,synchronized(this)表示整个方法为同步方法。 synchronized(this){ if (num > 0){ try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"...obj..." + num--); } } } }else while(true) show(); } //同步函数,锁是固定的this public synchronized void show(){ if (num > 0){ try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"...function..." + num--); } }}public class TicketDemo { public static void main(String[] args){ Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false, //那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况 //执行,实验就达不到目的了。 Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } t.flag = false; t2.start(); }}
静态的同步代码块使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
多线程下的单例模式
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
/* * 加同步的单例模式——懒汉式 */public class Single { private static Single s = null; private Single(){} //若使用同步函数,则效率较低,因为每次都需要判断 public static Single getInstance(){ if(s == null){ synchronized(Single.class){ //synchronized(this.getClass) if (s == null) s = new Single(); } } return s; }}
4.死锁
死锁常见情景之一:同步的嵌套。
class Test implements Runnable{ private boolean flag; Test(boolean flag){ this.flag = flag; } public void run(){ if(flag){ while(true) synchronized(MyLock.locka){ System.out.println(Thread.currentThread().getName()+"...if locka..."); synchronized(MyLock.lockb){ System.out.println(Thread.currentThread().getName()+"...if lockb..."); } } }else{ while(true) synchronized(MyLock.lockb){ System.out.println(Thread.currentThread().getName()+"...else lockb..."); synchronized(MyLock.locka){ System.out.println(Thread.currentThread().getName()+"...else locka..."); } } } }}class MyLock{ public static final Object locka = new Object(); public static final Object lockb = new Object();}class DeadLockDemo { public void main(String[] args){ Test a = new Test(true); Test b = new Test(false); Thread t1 = new Thread(a); Thread t2 = new Thread(b); t1.start(); t2.start(); }}
5.线程间通信
wait():让线程处于阻塞状态,被wait的线程会被存储到线程池中。
notify():唤醒线程池中的一个线程(任何一个都有可能)。
notifyAll():唤醒线程池中的所有线程。
/* * 文件架实例,一个存一个取。 */class CubbyHole{ private int index = 0; private int[] data = new int[3]; //向data中输入数,当data满时阻塞,否则向里面存入一个数,并唤醒另一个线程 public synchronized void put(int value){ while(index == data.length){ try{ //阻塞线程 this.wait(); }catch(InterruptedException e){} } data[index] = value; index ++; //唤醒线程 this.notify(); } //从data中取数,当data为空时阻塞,否则从中取出一个数,并唤醒另一个线程 public synchronized int get(){ while(index <= 0){ try{ //阻塞线程 this.wait(); }catch(InterruptedException e){} } index --; int val = data[index]; //唤醒线程 this.notify(); return val; }}
JDK1.5中增加了更多的类,以便更灵活地使用锁机制:java.util.concurrent.locks包,Lock接口、ReentrantLock类,ReadWriteLock接口、ReentrantReadWriteLock类。
JDK1.5中将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
Condition接口:替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
await() —> wait()
signal() —> notify()
signalAll() —> notifyAll()
/* *多生产者-多消费者问题 */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(); 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(); 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; } //复写run() public void run(){ while(true){ r.set("Item"); } }}//消费者class Consumer implements Runnable{ private Resource r; Consumer(Resource r){ this.r = r; } //复写run() public void run(){ while(true){ r.out(); } }}public class ResourceDemo { 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); //生产者线程t0 Thread t1 = new Thread(pro); //生产者线程t1 Thread t2 = new Thread(con); //消费者线程t2 Thread t3 = new Thread(con); //消费者线程t3 t0.start(); t1.start(); t2.start(); t3.start(); }}
停止线程:
1)运行状态的线程:结束循环
2)阻塞状态的线程:使用interrupt()方法,让线程具备CPU的执行资格。强制动作会发生InterruptedException。
class StopThread implements Runnable{ private boolean flag = true; public void run(){ while(flag){ try{ wait(); }catch(InterruptedException e){ System.out.println(Thread.currentThread().getName()+"..."+e); flag = false; } System.out.println(Thread.currentThread().getName()+"......"); } } public void setFlag(){ flag = false; }}public 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){ //使用interrupt()方法来结束阻塞状态的线程 t1.interrupt(); t2.interrupt(); break; } System.out.println("main..."+num); } System.out.println("over"); }}
6.线程类的其他方法
线程有两类:一类是普通线程(非Daemon线程)。在Java程序中,若还有非Daemon线程,则整个程序就不会结束。另一类是Daemon线程(守护线程,后台线程)。如果普通线程结束了,则后台线程自动终止。如垃圾回收线程就是后台线程,使用setDaemon(true)方法。
join():等待该线程终止
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
yield():暂停当前正在执行的线程对象,并执行其他线程。
class Demo1 implements Runnable{ public void run(){ for (int x = 0; x < 50; x++){ System.out.println(Thread.currentThread().getName()+"..."+x); Thread.yield(); //释放执行权 } }}public class JoinDemo { public static void main(String[] args){ Demo1 d = new Demo1(); Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start(); try{ t1.join();//t1线程要申请加入进来,运行。然后,主线程等待t1执行完。 }catch(InterruptedException e){ e.printStackTrace(); } t2.start(); t2.setPriority(Thread.MAX_PRIORITY); for(int x = 0; x < 50; x++){ System.out.println(Thread.currentThread().toString()+"..."+x); } }}
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- 黑马程序员—多线程
- linux命令详解(5):mkdir
- 面向对象相关知识点整理(1)
- 深入浅出web服务
- 阿里云部署Web API 总结(及其他可能部署方式)
- 2016网络红人萧天 最新资料
- 黑马程序员——多线程
- Spring学习(三)ioc工厂bean深入理解
- iOS中比较日期
- android WIFI 调试
- Sql 中 不等于'<>'与 NULL
- Mysql安装及1067错误相关
- NIM备份出现0042-154报错的解决办法
- java 死锁程序
- HashMap,Hashtable以及ConcurrentHashMap的比较(源码)