------
Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
多线程
一、概念
1、进程:是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有一个线程。
java VM 启动时会有一个进程java.exe。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
**扩展:其实更细节的说jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
二、创建多线程
如何在自定义代码中,自定义一个线程呢?
通过对api的查找,java已经提供了对线程这类事物的描述,就是thread类。
1、创建线程的第一种方式:继承Thread类。
步骤: a.定义类继承thread。 b.复写Thread类中的run方法。目的:将自定义代码存储在run方法,让线程运行。
c.调用线程的start方法。
该方法两个作用:启动线程,调用run方法。
**多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
在程序运行时发现运行结果每一次都不同。
因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。说明一点,在某一时刻,只能有一个程序在运行。(多核 除外)。CPU在做着快速的切换,已达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为看成在互相抢夺CPU的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说了算。
2、为什么要覆run方法呢
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。也就是Thread类中的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) { //for(int x;x<400;x++) //System.out.println("hello world!"); Demo d = new Demo();//创建好一个线程。 d.start();//开启线程并执行该线程的run方法。 //d.run();//仅仅是对象调用方法,而线程创建了,并没有运行。 for(int x=0;x<60;x++) System.out.println("hello world!"+x); } }
练习:
/* 创建两个线程,和主线程交替运行。 原来线程都有自己默认的名称。 thread-编号,编号从0开始。 static Thread currentThraed():获取当前线程对象。是静态的 getName():获取线程名称。 设置线程名称:setName或者构造函数。 */ class Test extends Thread { //private String name; Test(String name) { //this.name = name; super(name); } public void run() { for(int x=0; x<60;x++) { System.out.println((Thread.currentThread()==this)+"...."this.getName()+"..run..."+x); } } } class ThreadTest { public static void main(String[] args) { Test t1 = new Test("one---"); Test t2 = new Test("two+++"); t1.start(); t2.start(); // t1.run(); // t2.run(); for(int x=0;x<60;x++) { System.out.println("main..."+x); } } }
3、创建线程的第二种方式:实现Runnable接口 步骤: a.定义类实现Runnable接口。 b.覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。 c.通过Thread类建立线程对象。 d.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。 *为什么要将Runnable接口的子类对象传递给Thread的构造函数? 因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法。就必须明 确该run方法所属对象。
e.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
**(面试必考)实现方式和继承方式有什么区别?
实现方式的好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
4、两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存放在接口的子类的run方法。
示例:
class Ticket implements Runnable//extends Thread { private int tick = 100; 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(); 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(); /* Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket(); t1.start(); t2.start(); t3.start(); t4.start(); */ } }
对以上示例通过分析,发现,打印出0,-1,-2等错票。
5、多线程的运行出现了安全问题。
a.问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执 行,导致共享数据的错误。
b.解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
java对于多线程的安全问题提供了专业的解决模式。
就是:同步代码块。
synchronized(对象)
{
需要被同步的代码
}
对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程及时获取CPU的执行权,也进不去,因为没有获取锁。
6、同步的前提:
a.必须要有两个或者两个以上的线程。
b.必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
class Ticket implements Runnable//extends Thread { private int tick = 1000; Object obj = new Object(); public void run() { while(true) { synchronized(obj) { if(tick>0) { /* 为什么不能抛异常?因为run()方法复写的Runnable接口的方法,父类没有抛异常,子类有异常只能try //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(); } }
练习:
/* 需求: 银行有一个金库。 有两个储户分别存300,每次存100,存3次。 目的:该程序是否有安全问题,如果有,如何解决? 如何找到问题: 1.明确那些代码多线程运行代码。 2.明确共享数据。 3.明确多线程运行代码中那些语句是操作共享数据的。 */ class Bank { private int sum; //Object obj = new Object(); public synchronized void add(int n)//同步函数 { //synchronized(obj)//同步代码块 //{ sum = sum + n; try{Thread.sleep(10);}catch(Exception e){} System.out.println("sum="+sum); /} } } class Cus implements Runnable { private Bank b = new Bank(); 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(); } } 同步函数用的哪一个锁呢? 函数需要被对象调用,那么函数都有一个所属的对象饮用,就是this。 所以同步函数使用的锁是this。 通过该线程进行验证。 使用两个线程来买票。 一个线程在同步代码块中。 一个线程在同步函数中。 都在执行买票动作。 */ class Ticket implements Runnable//extends Thread { 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(); //try{Thread.sleep(10);}catch(Exception e){} t.flag = false; t2.start(); //Thread t3 = new Thread(t); //Thread t4 = new Thread(t); //t3.start(); //t4.start(); } }
7、如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不再是this。因为静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是class。
静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class
应用:单例模式中的体现
//饿汉式。 /* class Single { private static final Single s = new Single; private Single(){} public static Single getInstance() { returen s; } } */ //懒汉式 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; } } } }
死锁: class Test implements Runnable { private boolean flag; Test(boolean flag) { this.flag = flag; } public void run() { if(flag) { synchronized(MyLock.locka) { System.out.println("if locka"); synchronized(MyLock.lockb) { System.out.println("if lockb"); } } } else { synchronized(MyLock.lockb) { System.out.println("else lockb"); synchronized(MyLock.locka) { System.out.println("else locka"); } } } } } class MyLock { static Object locka = new Object(); static Object lockb = new Object(); } 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(); } }
三、多线程间通信
其实就是多个线程在操作同一个资源。但是操作的动作不同。
/* 线程间通讯: 其实就是多个线程在操作同一个资源。 但是操作的动作不同。 */ class Res { private String name; private String sex; private boolean flag = false; public synchronized void set(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.set("mike","man"); else r.set("丽丽","女 女") ; x=(x+1)%2; /* synchronized(r) { if(r.flag) try{r.wait();}catch(Exception e){} if(x==0) { r.name ="mike"; r.sex = "man"; } else { r.name ="丽丽"; r.sex = "女 女"; } x=(x+1)%2; r.flag= true; r.notify(); } */ } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { r.out(); /* synchronized(r) { if(!r.flag) try{r.wait();}catch(Exception e){} System.out.println(r.name+"...."+r.sex); r.flag = false; r.notify(); } */ } } } class InputOutputDemo { public static void main(String[] args) { Res r =new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); /* Input in = new Input(r); Output out = new Output(r); Thread t1 =new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); */ } }
1、多线程间通讯中常用方法
wait:
ntify();
notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
*为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁。只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。不可以对不同锁中的线程唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
示例:
/* 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。 为什么定义notifyAll, 以为需要唤醒对方线程。 因为只有notify,容易出现至唤醒本方线程的情况,导致程序中的所有线程都等待。 */ class ProduceConsumerDemo { 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(); } } class Resource { private String name; private int count = 1; private boolean flag = false; public synchronized void set(String name) { while(flag) try{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(); } } 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(); } } }
JDK1.5升级版
class ProduceConsumerDemo2 { 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(); } } /* JDK1.5中提供了多线程升级解决方案。 将同步synchronized替换成现实Lock操作。 将Object中wait,notify,notifyAll,替换成了Condition对象。 该对象可以Lock锁进行获取。 该事例中,实现了本方只唤醒了对方的操作。 */ class Resource { private String name; private int count = 1; private boolean flag = false; private Lock lock = new ReentrantLock(); //一个锁Lock可以对应对个Condition。 private Condition condition_pro = lock.newCondition(); private Condition condition_con = lock.newCondition(); public void set(String name)throws InterruptedException { lock.lock(); try { while(flag) condition_pro.await(); this.name=name+"---"+count++; System.out.println(Thread.currentThread().getName()+"..生产者"+this.name); flag=true; condition_con.signal(); } finally { lock.unlock();//释放锁的动作一定执行。 } } public void out()throws InterruptedException { lock.lock(); try { while(!flag) condition_con.await(); System.out.println(Thread.currentThread().getName()+"......消费者"+this.name); flag=false; condition_pro.signal(); } finally { lock.unlock(); } } } class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res=res; } public void run() { while(true) { try { res.set("商品"); } catch(InterruptedException e) { } } } } class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res=res; } public void run() { while(true) { try { res.out(); } catch(InterruptedException e) { } } } }
2、停止线程
a.定义循环结束标志 因为线程运行代码一般都是循环,只要控制了循环即可b.使用interrupt(中断)方法。该方法是结束线程的冻结状态,使线程回到运行状态中来。注:stop方法已经过时不在使用。
分析:如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
*特殊情况:
当线程处于冻结状态。就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就 可以操作标记让线程结束。
Thread类提供该方法 interrupt();
class StopThread implements Runnable { private boolean flag = true; public synchronized void run() { while(flag) { try{wait();} 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.setDaemon(true); t2.setDaemon(true); t1.start(); t2.start(); int num = 0; while(true) { if(num++ == 60) { //st.changeFlag(); //t1.interrupt(); //t2.interrupt(); break; } System.out.println(Thread.currentThread().getName()+"......."+num); } } }
3、其他方法演示
/* join: 当A线程执行到了B线程的.join()方法时,A就会等待,等B线程都执行完,A才会执行。 join可以用来临时加入线程执行。 */ class Demo implements Runnable { public void run() { for(int x= 0;x<70;x++) { System.out.println(Thread.currentThread().getName()+"...."+x); Thread.yield();//暂停当前正在执行的线程对象。 } } } class JoinDemo { public static void main(String[] args)throws Exception { Demo d = new Demo(); Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start(); //t1.setPriority(Thread.MAX_PRIORITY);优先级设置(MAX_PRIORITY最高优先级) t2.start(); //主线程等待t1结束 t1.join(); for(int x=0;x<80;x++) { System.out.println("main....."+x); } System.out.println("over"); } }
练习: class ThreadTest2 { public static void main(String[] args) { new Thread() { public void run() { for(int x=0;x<100;x++) { System.out.println(Thread.currentThread().getName()+"......"+x); } } }.start(); for(int x=0;x<100;x++) { System.out.println(Thread.currentThread().getName()+"......"+x); } Runnable r = new Runnable() { public void run() { for(int x=0;x<100;x++) { System.out.println(Thread.currentThread().getName()+"......"+x); } } }; new Thread(r).start(); } } /* class Test1 extends Thread { public void run() { for(int x=0;x<100;x++) { System.out.println(Thread.currentThread().getName()+"......"+x); } } } */