一、多线程——线程间通信——示例代码
class Resource { String name; String sex; } class Input implements Runnable { private Resource r; Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { if(x==0) { r.name = "mike"; r.sex = "man"; } else { r.name = "丽丽"; r.sex = "女"; } x = (x+1)%2; } } } class Output implements Runnable { private Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { System.out.println(r.name+"..."+r.sex); } } } class InputOutputDemo { 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(); } }
此程序存在安全问题:运行结果name和sex有可能不对应。二、多线程——线程间通信——解决安全问题
解决安全问题:加锁
因为是两个线程,必须使用同一个锁;锁是一个对象,可选择同一个对象r作为锁
代码如下:这段代码解决了姓名和性别不对应的问题,但是引发的新问题是:存入一个资源时,这个资源被重复取走多次;或者存入很多个资源却只取走了一个资源。class Resource { String name; String sex; } class Input implements Runnable { private Resource r; Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { if(x==0) { synchronized(r)//r对象是唯一的 { r.name = "mike"; r.sex = "man"; } } else { synchronized(r) { r.name = "丽丽"; r.sex = "女女女"; } } x = (x+1)%2; } } } class Output implements Runnable { private Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { synchronized(r) { System.out.println(r.name+"..."+r.sex); } } } } class InputOutputDemo { 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(); } }
那么,如何实现存入一个资源就取走一个资源呢?请看下一节。三、线程间通信——等待唤醒机制
1、 线程运行时,内存中会建立一个线程池;等待线程都存在线程池当中。
2、 当notify()时,唤醒的都是线程池中的线程。
3、 如果线程池中有很多等待的线程,那么notify()时,唤醒的是第一个等待的线程。
4、 notifyAll();唤醒线程池中所有等待的线程;
5、 wait; notify(); notifyAll(); 都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
同时,wait; notify(); notifyAll();这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程,才可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
6、为什么这些操作线程的方法要定义 Object 类中呢?
锁可以是任意对象,所以可以被任意对象调用的方法定义 Object 类中。
示例代码:class Resource { String name; String sex; boolean flag = false; } class Input implements Runnable { private Resource r; Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { 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 Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { 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) { 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(); } }
四、多线程间通信——代码优化
class Resource { 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可以省略 this.name = name; this.sex = sex; flag = true; this.notify(); //此处this可以省略 } public synchronized void out() { if(!flag) try{this.wait();}catch(Exception e){} //此处this可以省略 System.out.println(name+".."+sex); flag = false; this.notify(); //此处this可以省略 } } class Input implements Runnable { private Resource r; Input(Resource 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; } } } class Output implements Runnable { private Resource r; Output(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class InputOutputDemo { public static void main(String[] args) { Resource r = new Resource(); 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(); */ } }
五、线程间通信——生产者消费者
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(con); Thread t3 = new Thread(pro); 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{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{this.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(); } } }
结论:
当出现多个生产者消费者的线程时,需要用 while 和 notifyAll() 。
因为 if 只判断一次; while 每次都需要判断,只有 while(false)时,才向下执行。
同时,如果用 notify() 容易出现只唤醒本方线程的情况,有可能导致所有线程都处于等待状态。六、线程间通信——生产者消费者JDK5.0升级版
java.util.concurrent.locks.Lock
-ReentrantLock
public interface Lock
public class ReentrantLock extends Object implements Lock,Serializable
java.util.concurrent.locks.Condition
public interface Condition
JDK1.5 中提供了多线程升级解决方案。
将同步 synchronized 替换成显示的 Lock 操作。
将 Object 中的 wait,notify notifyAll,替换成了 Condition 对象的await(), signal(), signalAll() 。
该对象可以由 Lock 锁的newCondition()方法获取。
Lock: 替代了 synchronized
lock
unlock
newCondition()
Condition: 替代了Object wait notify notifyAll
await();
signal();
signalAll();
该示例中,实现了本方只唤醒对方操作。
示例代码:import java.util.concurrent.locks.*; 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(con); Thread t3 = new Thread(pro); 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; private final Lock lock = new ReentrantLock(); private final Condition condition_pro = lock.newCondition(); private final 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) { } } } }
七、多线程——停止线程
如何停止线程?
只有一种,run方法结束。
1、定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。
2、使用 interrupt (中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注: stop 方法已经过时,不再使用。
特殊情况:
当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
代码:class StopThread implements Runnable { private boolean flag = true; public synchronized void run() { int x = 0; while (flag) { try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"...Exception: "+x); flag = false; //当中断状态清除后,改变标记标记 } System.out.println(Thread.currentThread().getName()+"...run: "+x); x++; } } 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 (num++ == 60) { //st.changeFlag(); t1.interrupt();//清除线程的中断状态 t2.interrupt();//清除线程的中断状态 break; } System.out.println(Thread.currentThread().getName()+"..."+num); } System.out.println("over"); } } //IllegalMonitorStateException
八、多线程——守护线程
void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
======主线程是守护线程。
======守护线程,可以理解为后台线程。
代码:class StopThread implements Runnable { public void run() { int x = 0; while (true) { System.out.println(Thread.currentThread().getName()+"...run: "+x); x++; } } } class StopThreadDemo2 { 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) { break; } System.out.println(Thread.currentThread().getName()+"..."+num); } System.out.println("over"); } }
九、多线程—— join 方法
void join()
等待该线程终止。
join:
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
代码:class Demo implements Runnable { public void run() { for (int x=0 ;x<30 ;x++ ) { System.out.println(Thread.currentThread().getName()+": "+x); } } } 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.join(); t2.start(); t2.join(); for (int x=0 ;x<30 ;x++ ) { System.out.println(Thread.currentThread().getName()+": "+x); } System.out.println("over"); } }
十、多线程——优先级 & yield 方法
String toString(); 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
线程组:
谁开启该线程,该线程就属于哪个组。
例如:主线程main开启线程0,那么线程0就属于main组。
优先级:
优先级一共有10级,1——10,数字越大,优先级越高,获取执行权的概率就越大;默认为5 。
改变优先级:
t1.setPriority(Thread.MAX_PRIORITY);
MAX_PRIORITY:10
NORM_PRIORITY:5
MIN_PRIORITY:1
yield:
暂停当前正在执行的线程对象,并执行其他线程。
暂缓线程的运行,可以达到线程交替运行的效果。
调用方法:
Thread.yield();
优先级示例代码:class Demo implements Runnable { public void run() { for (int x=0 ;x<30 ;x++ ) { System.out.println(Thread.currentThread().toString()+": "+x); //toString()替换getName(); } } } class PriorityDemo { 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); //设置优先级; t2.start(); for (int x=0 ;x<30 ;x++ ) { System.out.println(Thread.currentThread().getName()+": "+x); } System.out.println("over"); } }
yield()方法代码:class Demo implements Runnable { public void run() { for (int x=0 ;x<30 ;x++ ) { System.out.println(Thread.currentThread().toString()+": "+x); Thread.yield(); } } } class YieldDemo { public static void main(String[] args) throws Exception { Demo d = new Demo(); Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start(); t2.start(); System.out.println("over"); } }
开发中什么时候使用多线程?
当某些代码需要同时被执行时,就用单独的线程进行封装。
示例代码(匿名内部类方式):class ThreadTest { public static void main(String[] args) { //线程0运行代码 new Thread() { public void run() { for (int x=0 ;x<30 ;x++ ) { System.out.println(Thread.currentThread().getName()+": "+x); } } }.start(); //线程1运行代码 Runnable r = new Runnable() { public void run() { for (int y=0 ;y<30 ;y++ ) { System.out.println(Thread.currentThread().getName()+": "+y); } } }; new Thread(r).start(); //主线程运行代码 for (int z=0 ;z<30 ;z++ ) { System.out.println(Thread.currentThread().getName()+": "+z); } } }