Day15
线程
一、多线程的创建和启动
概念
- Java的JVM允许程序运行多个线程,通过java.lang.Thread类来实现。
- Thread类的特性:
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,把run()方法的主体称为线程体。
通过该Thread对象的start()方法来调用这个线程。
run():存放多线程中执行的代码;
start():启动线程,开始执行run()方法。
Thread类
构造方法:
- Thread():创建新的Thread对象。
- Thread(String threadname):创建线程并指定线程名称。
- Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run()方法。
- Thread(Runnable target, String name):创建新的Thread对象 。
创建线程的两种方式
1.继承Thread类,步骤:
- 1.定义子类继承Thread类。
- 2.子类中重写Thread类中的run()方法。
- 3.创建Thread子类对象,即创建了线程对象。
- 4.调用Thread类的start()方法:启动线程,执行run()方法。
package thread;
/*
第一种方法:
(1)继承Thread类, 实现run()方法;
(2)调用Thread类的start()方法.
作用: 开启线程, 执行run()方法.
*/
class MyThread1 extends Thread{
MyThread1(){}
MyThread1(String name){
super(name);//调用父类构造函数
}
public void run() {
for(int i=0; i<100; i++){
//Thread.currentThread()获取当前线程对象
//getName()获取线程名称
System.out.println(Thread.currentThread().getName() + ":\t" + i);
}
}
}
public class TestThread1 {
public static void main(String[] args) {
Thread t1 = new MyThread1();
t1.setName("MyThread-1");//设置线程名称
t1.start();
new MyThread1("MyThread-2").start();//设置线程名称
}
}
注1:各线程之间、各线程与主程序之间的执行顺序不定。先执行主程序,再分为多个分支执行各个线程,
注2:run()和start()的区别:
run()方法中存放的是多线程要运行的代码,strat()是启动线程并调用run()方法中的代码。
若直接调用run()方法,则和普通单线程程序没有区别,程序从头至尾顺次执行,因为没有开启线程。
2.实现Runnable接口,步骤:
- 1.定义子类,实现Runnable接口。
- 2.子类中重写Runnable接口中的run()方法。
- 3.通过Thread类含参构造器创建线程对象。
- 4.将Runnable接口的实现类对象作为实际参数传递给Thread类的构造方法中。
- 5.调用Thread类的start()方法:开启线程,执行Runnable子类接口的run()方法。
package thread;
/*
第二种方法:
(1)实现Runnable接口, 实现run()方法;
(2)建立Runnable对象和Thread类对象, 将接口对象传入类对象的构造函数中;
(3)调用Thread类的start()方法.
*/
class MyThread2 implements Runnable{
public void run() {
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName() + ":\t" + i);
}
}
}
public class TestThread2 {
public static void main(String[] args) {
Runnable r = new MyThread2();
Thread t1 = new Thread(r, "MyThread-1");
t1.start();
new Thread(r, "MyThread-2").start();
}
}
继承方式和实现方式的联系与区别
区别:
- 继承时,线程代码存放在Thread子类run()方法中;
- 实现时,线程代码存放在Runnable接口子类的run()方法中。
实现接口方式的好处:
-
避免了单继承的局限性。
-
多个线程可以共享同一个接口实现类的对象,适合多个相同线程来处理同一份资源。
一般使用实现接口方式来实现多线程。
练习
分别用继承和实现两种方式模拟售票系统。
package thread;
/*
* 分别用继承和实现两种方式实现售票系统
*/
//继承Thread类
class InheritThread extends Thread{
private static int ticket = 100;//票的总数, 静态的目的是使多个线程共享该数据
InheritThread() {}
InheritThread(String name) {
super(name);
}
public void run() {
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张票, 剩余票数为:\t" + --ticket);
}
}
}
//实现Runnable接口
class ImpRunnable implements Runnable{
private int ticket = 500;
public void run() {
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张票, 剩余票数为:\t" + --ticket);
}
}
}
//主线程
public class TestThread {
public static void main(String[] args) {
//如果不将ticket设置为静态, 则每一个线程对象都有一份ticket,
//总票数为1500, 不合要求
// new InheritThread("窗口1").start();
// new InheritThread("窗口2").start();
// new InheritThread("窗口3").start();
//实现方式时, 传入Thread对象的是接口对象, 只要传入的是同一个接口对象,
//那么各线程共享同一份ticket数据, 若传入不同接口对象, 则每个对象各有一份数据
Runnable r = new ImpRunnable();
new Thread(r, "窗口1").start();
new Thread(r, "窗口2").start();
new Thread(r, "窗口3").start();
}
}
二、Thread类的相关方法
方法
-
void start():启动线程,并执行对象的run()方法。
-
run():线程在被调度时执行的操作。
-
static currentThread():返回当前线程。
-
String getName():返回线程的名称。
-
void setName(String name):设置线程名称。
-
toString():[线程名, 优先级, 线程组] 获取线程的详细信息
//新建线程的同时,为线程命名 Thread t1 = new Thread(t, "Thread_1"); //后期命名 Thread t2 = new Thread(new ImpRunnable()); t2.setName("Thread_2"); //获取线程名称 Thread t3 = new Thread(new ImpRunnable()); t3.getName();//为显示命名时,系统默认为线程命名,返货Thread-1 //静态方法:返回当前线程 Thread.currentThread(); //返回当前线程的名称 Thread.currentThread().getName();
-
static void yield():线程让步(挂起)。
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程;
若队列中没有同级或更高优先级的线程,忽略此方法。
例1:package thread.thread; /* * new Thread().yield();暂停当前线程, 执行其他线程 */ /** * 正常结果是两个线程争夺执行权, 不规则打印. * 加上yield()方法之后, 线程A在循环打印时每打印一次会释放一次执行权, 线程B开始打印, * B每次打印也会读到一次yield(), 执行权交给A, 故结果是A和B交替打印. * * @author 14251 * */ class Yield implements Runnable{ @Override public void run() { for(int x=0; x<50; x++) { System.out.println(Thread.currentThread().toString() + "-----" + x); Thread.yield(); } } } public class ThreadYield { public static void main(String[] args) { Yield p = new Yield(); Thread t1 = new Thread(p); Thread t2 = new Thread(p); t1.start(); t2.start(); } }
例2:
package thread; public class ImpRunnable implements Runnable{ int count = 0; @Override public void run() { System.out.println("开始执行线程 " + Thread.currentThread().getName()); for(int i = 0; i < 5; i++) { //当i为偶数时,进行让步 if(i % 2 == 0) { Thread.yield(); } count++; System.out.println("执行次数:" + count); } System.out.println("线程" + Thread.currentThread().getName() + "执行完毕!"); } }
-
join():当A线程执行到了B线程的 .join() 方法时,A释放执行权,等待B线程执行完毕,A再开始执行。该方法使得低优先级的线程也可以获得执行 。(一般用于main方法中,代码块内用wait()。)
package thread.thread; /* * new Thread.join(); */ class Join implements Runnable{ @Override public void run() { for(int x=0; x<50; x++) { System.out.println(Thread.currentThread().getName() + "-----" + x); } } } /* * 不加join()时, main、thread1、thread2交替执行, 加上join方法之后, 当前执行线程放弃处理机, 供其他线程争夺. * 也就是说, 调用join()的线程只是请求获得处理机, 迫使正在占有处理机的线程放弃执行权, 之后具体哪个线程获得 * 执行权还要竞争, 而非哪个线程调用join()就立即获得处理机运行. 而之前由于join()方法放弃执行权的线程, 只有当 * 调用join的线程执行完毕后才能争夺处理机. */ public class JoinThread { public static void main(String[] args) { Join j = new Join(); Thread t1 = new Thread(j); Thread t2 = new Thread(j); //当前处理机在主线程 t1.start(); t2.start(); try { t1.join();//请求运行, 主线程放弃处理机, 当前就绪线程有t1和t2, t1、t2争夺执行权, //只有t1执行完毕后, 主线程才能争夺执行权 } catch (InterruptedException e) { e.printStackTrace(); } for(int x=0; x<50; x++) { System.out.println(Thread.currentThread().getName() + "-----" + x); } // t2.start();//此时当t1调用join()时, 线程只有主线程和t1, 主线程放弃执行权, 由于没有其他线程和t1竞争, //故t1执行, t1执行完毕后, 主线程与t2争夺执行权. } }
-
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队。
该方法会抛出InterruptedException异常。package thread; public class ImpRunnable implements Runnable{ int count = 0; @Override public void run() { System.out.println("开始执行线程 " + Thread.currentThread().getName()); for(int i = 0; i < 5; i++) { try { Thread.sleep(1000);//当前线程每隔1000ms执行一次,即睡眠时间为1000ms(能明显看出控制台输出变慢) } catch (InterruptedException e) { e.printStackTrace(); } count++; System.out.println("执行次数:" + count); } System.out.println("线程" + Thread.currentThread().getName() + "执行完毕!"); } }
-
stop():强制结束线程。已过时。
如何结束线程? 结束线程可以通过控制while循环次数实现,只要控制了循环,就可以控制线程的执行与否。package thread.thread; class Stop1 implements Runnable{ public boolean flag = true; public void run() { while(flag) { System.out.println(Thread.currentThread().getName() + "-----stop1"); } } } class Stop2 implements Runnable{ int num = 50;//设置循环次数 public void run(){ while(num>0) { System.out.println(Thread.currentThread().getName() + " " + num-- + "-----stop2"); } } } public class StopThread{ public static void main(String[] args) { Stop1 s1 = new Stop1(); Stop2 s2 = new Stop2(); Thread t1 = new Thread(s1); Thread t2 = new Thread(s2); t1.start(); t2.start(); /* t1是无限循环, 如何结束线程呢? 1. 设置多长时间后结束 2. 设置循环几次后结束 */ //主线程循环100次后结束线程t1 int num = 50; while(true) { if(num-- >= 0) { System.out.println(Thread.currentThread().getName() + " " + num-- + "-----main"); }else { s1.flag = false;//改变循环标记 break; } } } }
-
void interrupt():结束线程的等待状态,恢复到运行状态,会出现InterruptedException。如果线程都处于等待状态,则无法继续执行也无法主动停止,这时需要使用该方法激活线程。
package thread.thread; /** * new Thread().interrupt(); * 结束线程的等待状态, 恢复到运行状态, 会出现InterruptedException * @author 14251 * */ class TestInterrupted implements Runnable{ private boolean flag = true; @Override public synchronized void run() { while(flag) { try { wait();//线程一直处于等待状态 }catch(InterruptedException e) {//使用Thread().interrupt()时会出现中断异常 System.out.println(Thread.currentThread().getName() + "发生中断异常"); flag = false; } System.out.println(Thread.currentThread().getName() + "run方法开始执行"); } } } public class ThreadInterrupted { public static void main(String[] args) { TestInterrupted t = new TestInterrupted(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); t2.start(); int num = 50; while(true){ if(num-- == 0) { t1.interrupt();//设置50次之后, 若线程仍处于等待状态, 则强制恢复到运行状态, //这时可以操作改变标记, 终止线程 break; } } } }
-
void setDaemon(boolean flag):将线程设置为守护线程。
守护线程:当其他线程运行完毕之后,不管守护线程是否执行完毕,必须停止。
应用:当某个线程需要利用其他线程的执行结果时,可以将该线程设置为守护线程,当提供数据的线程终止运行时,该线程再运行下去没有任何意义,故应该随着提供数据线程的终止而终止。package thread.thread; class Protect { private boolean flag = true; public void set() { int num = 100; while(num > 0) { System.out.println(Thread.currentThread().getName() + "\t" + num--); } } public void get(){ while(flag) { System.out.println(Thread.currentThread().getName() + ": get"); } } } public class ProtectedThread { public static void main(String[] args) { Protect p = new Protect(); new Thread(new Runnable() { @Override public void run() { p.set(); } }).start(); Thread t = new Thread(new Runnable() { @Override public void run() { p.get(); } }); t.setDaemon(true);//当Thread-0打印了100次之后, thread-1也停止运行 t.start(); } }
-
boolean isAlive():返回boolean,判断线程是否存活。
线程的优先级
线程的优先级用数字1-10来表示,数字越大,优先级越高,默认为5。
线程的优先级控制:
- Thread.MAX_PRIORITY; ----- 10
- Thread.MIN _PRIORITY; ----- 1
- Thread.NORM_PRIORITY; ----- 5
方法:
-
setPriority(int newPriority) :设置线程的优先级。
-
getPriority() :返回线程优先值。
package thread.thread; /* * Thread.setPriority();设置线程优先级,范围 1-10, 默认5 * Thread.MAX_PRIORITY, Thread.MIN_PRIORITY, Thread.NORM_PRIORTY * Thread.currentThread().toString(); [线程名, 优先级, 线程组] 获取当前线程的详细信息 */ class Priority implements Runnable{ @Override public void run() { for(int x=0; x<50; x++) { System.out.println(Thread.currentThread().toString() + "-----" + x); } } } public class ThreadPriority { public static void main(String[] args) { Priority p = new Priority(); Thread t1 = new Thread(p); Thread t2 = new Thread(p); //设置优先级 t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); for(int x=0; x<50; x++) { System.out.println(Thread.currentThread().toString() + "-----" + x); } } }
三、线程的生命周期
JDK中用Thread.State枚举表示了线程的几种状态。要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件。
- 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能。
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态。join()
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止。stop()
四、线程同步
临界区问题:一次只允许一个进程(线程)进入临界区访问共享资源。
解决线程安全问题的两种方法:同步机制Synchronized
- 1. 同步方法:
public synchronized void show (String name){ } - 2. 同步代码块:
synchronized (对象){ //对象充当同步锁的作用, 可以是任何对象, 当对象相同时锁相同
// 需要被同步的代码;
}
1. 同步的前提:
(1)必须要有两个或两个以上线程;
(2)必须是多个线程使用同一个锁。
2. 同步代码块:
(1)售票
package thread;
//以售票代码为例
class MyRunnable implements Runnable{
private int ticket = 100;
public void run() {
Object obj = new Object();
synchronized(obj) {//窗口售票不会出现问题, 汇总剩余票数时才可能出现问题
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张票, 剩余票数为:\t" + --ticket);
}
}
}
}
public class SellTicket {
public static void main(String[] args) {
Runnable r = new MyRunnable();
new Thread(r, "窗口1").start();
new Thread(r, "窗口2").start();
new Thread(r, "窗口3").start();
}
}
(2)银行存款
package thread;
/*
* 模拟顾客去银行存钱
* 3个客户去存钱, 分3次每次存1000元
* 是否存在线程安全问题?
* 如何找问题?
* 1.明确哪些代码是多线程运行代码
* 2.明确哪些是共享数据
* 3.明确多线程运行代码中哪些语句是操作共享数据的。
* 本问题中, 将顾客看作是多个线程, 顾客存钱的过程不会出现问题,
* 当存完钱向银行系统中汇总钱数是有可能出现问题, 即多个线程同时存完钱
* 同时向银行汇总钱数, 会出现问题. 解决办法是在汇总钱数sum时加同步锁.
*/
class Bank{
private int sum;
public void save(int money) {
Object obj = new Object();
synchronized(obj) {
sum += money;
System.out.println(Thread.currentThread().getName() + " 存款完毕, 当前总额为:\t" + sum);
}
}
}
class Customer implements Runnable{
private Bank b = new Bank();
public void run() {
for(int i=0; i<3; i++) {
b.save(1000);
}
}
}
public class BankExample {
public static void main(String[] args) {
Runnable r = new Customer();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
3. 同步方法:
package thread;
/**
* synchronized-同步锁,锁的是调用该方法的对象,当有多个线程访问共享资源时,互斥进行。
* 若不同对象调用该方法,则还会出现线程同步问题,
* 如下代码所示,当使用account1, account2两个不同对象调用线程执行synchronized方法时,结果依然错误;
* 使用account对象传入两个线程访问synchronized方法时,结果才正确。
* @author MCC
*
*若方法是static方法,则synchronized时,对于所有对象都起作用。
*/
public class MyThread {
public static void main(String[] args) {
// Account account1 = new Account();
// Account account2 = new Account();
// AccountThread a1 = new AccountThread(account1, 2000);
// AccountThread a2 = new AccountThread(account2, 2000);
Account account = new Account();
AccountThread a1 = new AccountThread(account, 2000);
AccountThread a2 = new AccountThread(account, 2000);
Thread t1 = new Thread(a1, "T1");
Thread t2 = new Thread(a2, "T2");
t1.start();
t2.start();
}
}
//账户类
class Account{
public static double money = 3000;
public synchronized void getMoney(double m) {
if(m > money) {
System.out.println(Thread.currentThread().getName() + "余额不足, 剩余金额为:\t" + money);
}else {
System.out.println(Thread.currentThread().getName() + "已取走" + m + "元");
money -= m;
System.out.println(Thread.currentThread().getName() + "余额为:\t" + money);
}
}
}
//线程类
class AccountThread implements Runnable{
Account account;
double m;
public AccountThread() {}
public AccountThread(Account account, double m) {
this.account = account;
this.m = m;
}
@Override
public void run() {
account.getMoney(m);
}
}
/*
* 问题解决
* T1已取走2000.0元
* T1余额为: 1000.0
* T2余额不足, 剩余金额为: 1000.0
*/
4. 同步方法与静态同步方法的锁:
- 同步方法的锁是 this
- 静态同步方法的锁是该方法所在类的字节码文件的对象 (Xxx.class)
package thread;
class Sell implements Runnable{
private static int ticket = 100;
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
// Sell(boolean flag){
// this.flag = flag;
// }
@Override
public void run() {
if(flag==true) {
// synchronized(this) {//同步方法的锁是this
synchronized(Sell.class) {//静态同步方法的锁是该方法所在类的字节码文件的对象(Xxx.class)
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张票, 剩余票数为:\t" + --ticket);
}
}
}else {
code();
}
}
public synchronized static void code() {
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张票, 剩余票数为:\t" + --ticket);
}
}
}
public class TestMethodLock {
public static void main(String[] args) {
Sell r = new Sell();
new Thread(r, "窗口1").start();
r.setFlag(false);
new Thread(r, "窗口2").start();
}
}
五、懒汉式
解决单例设计模式中懒汉式的线程安全问题:
package thread;
/*
* 解决懒汉式的线程安全问题
*/
public class Single {
private static Single s = null;
private Single() {}
public static Single getInstance() {
/*
* 每次都会判断一次同步锁, 耗费资源,
* 二次改进: 只有s==null时才需要判断锁, 若s!=null只需返回s即可, 不需要进入内层判断锁
*/
if(s==null) {
synchronized(Single.class) {
if(s==null) {//多线程时会出现安全问题
s = new Single();
}
}
}
return s;
}
}
六、线程死锁
- 死锁:
不同的线程分别占用对方需要的资源并且不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。 - 代码中的体现:
同步中嵌套同步,而且各个同步的锁还不相同(被其他线程占用)。
package thread;
class MyCode implements Runnable{
private boolean flag;
MyCode(boolean flag){
this.flag = flag;
}
// Object obj1 = new Object();
// Object obj2 = new Object();
@Override
public void run() {
if(flag==true) {
// while(true) {
synchronized(Lock.lock1) {
System.out.println("flag==true-1");
synchronized(Lock.lock2) {
System.out.println("flag==true-2");
}
}
// }
}else {
// while(true) {
synchronized(Lock.lock2) {
System.out.println("flag==false-1");
synchronized(Lock.lock1) {
System.out.println("flag==false-2");
}
}
// }
}
}
}
class Lock{
public static Object lock1 = new Object();
public static Object lock2 = new Object();
}
public class DeadLock {
public static void main(String[] args) {
new Thread(new MyCode(true)).start();
new Thread(new MyCode(false)).start();
}
}
七、线程通信
- wait():令当前线程挂起并放弃CPU、同步资源,当前线程进入就绪状态。
- notify():唤醒就绪队列中优先级最高的线程。
- notifyAll():唤醒就绪队列中的全部线程。
注意:java.lang.Object提供的上述三个方法只有在同步方法或同步代码块中才能使用,并且要使用Object对象调用。
问题1:wait()与sleep()的区别?
wait():释放资源,释放锁;
sleep():释放资源,不释放锁。
问题:为什么这些方法要定义在Object类中?
因为等待唤醒机制在操作同步问题时,是对拥有同一个锁的线程进行操作,只有同一个锁上的wait()线程,才能被同一个锁的notify()唤醒,不可以唤醒不同锁的线程,而锁可以是任意对象,所有可以被任意对象调用的方法应该定义在Object类中。
package thread;
/*
* 假设有个资源系统, 线程A向系统中存入一个人的姓名与年龄, 线程B从中取出该人的信息,
* 通过分析可知, A与B不能同时进行, 设置一个标记flag, 当取false时代表A可以存资源,
* 取true时, 代表B可以取资源
*/
class Resource{
private String name;
private String sex;
private int count;
private boolean flag = false;
public synchronized void input(String name, String sex) {
while(flag==true) {
try {
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
System.out.println(Thread.currentThread().getName() + "\t" +
this.name + "\t" + this.sex + "\t" + ++count);
this.flag = true;
this.notify();
}
public synchronized void output() {
while(flag==false) {
try {
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t" +
this.name + "\t" + this.sex + "\t" + count);
this.flag = false;
this.notify();
}
}
class Input implements Runnable{
private Resource res;
Input(Resource res){
this.res = res;
}
public void run() {
int x = 0;
while(true) {
if(x == 0) {
res.input("李华", "男");
}else {
res.input("小红", "女");
}
x = (x + 1)% 2;
}
}
}
class Output implements Runnable{
private Resource res;
Output(Resource res){
this.res = res;
}
public void run() {
while(true) {
res.output();
}
}
}
public class ThreadCommunication {
public static void main(String[] args) {
Resource r = new Resource();
Thread t1 = new Thread(new Input(r), "输入线程");
Thread t2 = new Thread(new Output(r), "输出线程");
t1.start();
t2.start();
}
}
生产者与消费者
一、两个线程,一个线程负责向资源库中输入资源(生产者),另一个线程负责从资源库中取出资源(消费者),JDK1.4及之前版本:
问题:为什么使用notifyAll()?
因为使用notify时唤醒的是排在就绪队列中优先级最高的线程,优先级相同时唤醒排在第一位的线程,这时由于有多个生产者,生产者可能排在第一位,因此被唤醒的可能不是消费者而是生产者,结果是所有线程继续等待,都得不到执行,使用notifyAll()后,所有线程都被唤醒,消费者线程则可以继续执行,不会造成一直等待现象。
package thread;
/*
* JDK1.4以及之前版本
*/
class ResourceLibrary{
private String name;
private int count;
private boolean flag = false;
public synchronized void product(String name) {
while(flag==true) {
try {
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
System.out.println(Thread.currentThread().getName() + "\t" + "生产" + this.name + "\t" + ++count);
this.flag = true;
this.notifyAll();
}
public synchronized void consume() {
while(flag==false) {
try {
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t" + "消费" + this.name + "\t" + count);
this.flag = false;
this.notifyAll();//notify()唤醒就绪队列第一位的线程, 该线程可能是生产者线程, 则会造成所有线程均处于
//等待状态, 无法继续执行, 因此使用notifyAll()唤醒所有线程, 此时排在后面的第一个消费者线程可以获得处理机
//继续执行.
}
}
class Producer implements Runnable{
private ResourceLibrary rl;
Producer(ResourceLibrary rl){
this.rl = rl;
}
public void run() {
while(true) {
rl.product("商品");
}
}
}
class Consumer implements Runnable{
private ResourceLibrary rl;
Consumer(ResourceLibrary rl){
this.rl = rl;
}
public void run() {
while(true) {
rl.consume();
}
}
}
public class ConsumerProducer {
public static void main(String[] args) {
ResourceLibrary rl = new ResourceLibrary();
Thread t1 = new Thread(new Producer(rl), "生产者-1");
Thread t2 = new Thread(new Producer(rl), "生产者-2");
Thread t3 = new Thread(new Consumer(rl), "消费者-1");
Thread t4 = new Thread(new Consumer(rl), "消费者-2");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
二、JDK1.5(JDK5)的新特性,引入了信号量机制,即多个同步锁。
JDK1.4及之前 | JDK5 |
---|---|
synchronized | Lock.lock(),lock.unlock() |
锁(对象) | Condition |
wait() | await() |
notify() | signal() |
notifyAll() | signalAll() |
Lock类中提供了实例化Condition对象的方法:lock.newComdition(); 新版下等待唤醒机制不再定义在Object类中,而是定义在Condition类中。
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//JDK1.5之后的线程通信, 以生产者消费者为例
class ResourceLib{
private String name;
// private static int count = 5;
private int count;
private boolean flag = false;
private final Lock lock = new ReentrantLock();//锁, 必须为同一把锁, 获得锁的线程才能进入临界区(ResourceLib)存取资源
private final Condition notFull = lock.newCondition();//生产者信号量
private final Condition notEmpty = lock.newCondition();//消费者信号量
public void product(String name) throws InterruptedException{
lock.lock();
try {
while(flag==true) {
notFull.await();
}
this.name = name;
System.out.println(Thread.currentThread().getName() + "\t" + "生产" +
this.name + "\t" + ++count);
this.flag = true;
notEmpty.signal();
}finally {//保证lock.unlock()一定会执行
lock.unlock();
}
}
public void consume() throws InterruptedException{
lock.lock();
try {
while(flag==false) {
notEmpty.await();
}
System.out.println(Thread.currentThread().getName() + "\t" + "消费" +
this.name + "\t" + count);
this.flag = false;
notFull.signal();
}finally {
lock.unlock();
}
}
}
class NewProducer implements Runnable{
private ResourceLib rl;
NewProducer(ResourceLib rl){
this.rl = rl;
}
public void run() {
while(true) {
try {
rl.product("商品");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
class NewConsumer implements Runnable{
private ResourceLib rl;
NewConsumer(ResourceLib rl){
this.rl = rl;
}
public void run() {
while(true) {
try {
rl.consume();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Jdk5PC {
public static void main(String[] args) {
ResourceLib rl = new ResourceLib();
Thread t1 = new Thread(new NewProducer(rl), "生产者-1");
Thread t2 = new Thread(new NewProducer(rl), "生产者-2");
Thread t3 = new Thread(new NewConsumer(rl), "消费者-1");
Thread t4 = new Thread(new NewConsumer(rl), "消费者-2");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
三、假设库存为5,当库存为0时生产者开始生产,当库存大于0时消费者可以消费。
package thread.thread;
import java.util.concurrent.locks.*;
/*
* 生产者生产时拒绝消费者消费, 反之亦然
*/
class Resource{
private static int count;//记录产品库存
private Lock lock = new ReentrantLock();//锁
private Condition notFull = lock.newCondition();//生产者信号量
private Condition notEmpty = lock.newCondition();//消费者信号量
//生产者
public void product() throws InterruptedException{
lock.lock();
try {
if(count == 0) {//最大库存为5
System.out.println("库存不足, 开始生产");
while(count != 5) {
System.out.println(Thread.currentThread().getName() +
": 已生产 1 件" + "库存为: " + ++count + " 件");
notEmpty.signal();
}
}else {
notFull.await();
}
}finally {
lock.unlock();
}
}
//消费者
public void consume() throws InterruptedException{
lock.lock();
try {
if(count != 0) {
System.out.println("库存有余, 可以消费");
while(count > 0) {
System.out.println(Thread.currentThread().getName() +
": 已消费 1 件" + "库存为: " + --count + " 件");
notFull.signal();
}
}else {
notEmpty.await();
}
}finally {
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run() {
while(true) {
try {
r.product();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run() {
while(true) {
try {
r.consume();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class PConsumer {
public static void main(String[] args) {
Resource r = new Resource();
new Thread(new Producer(r), "生产者1").start();
new Thread(new Producer(r), "生产者2").start();
new Thread(new Consumer(r), "消费者1").start();
new Thread(new Consumer(r), "消费者2").start();
}
}
四、生产者将产品交给店员(Clerk),消费者从店员处取走产品,店员持有固定数量的产品(比如5),生产者生产产品时拒绝消费者消费,消费者消费产品时拒绝生产者生产。
1. 内部类实现:
package thread;
/**
* 生产者与消费者
* @author MCC
*
*/
public class ProducerConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();//同步对象
//生产者
new Thread(new Runnable() {
@Override
public void run() {
synchronized (clerk) {
while(true) {
if(Clerk.production != 5) {//商品上限为5,有空位则可以生产
System.out.println("[" + Thread.currentThread().getName() + "]" + "库存为:" + Clerk.production + ", 开始生产!");
while(Clerk.production < 5) {//生产上限为5
Clerk.production++;//开始生产
System.out.println("[" + Thread.currentThread().getName() + "]" + "已生产, 库存为:" + Clerk.production);
}
System.out.println("[" + Thread.currentThread().getName() + "]" + "生产完毕!");
clerk.notify();//生产完毕之后,唤醒消费者
}else {
try {
clerk.wait();//如果目前线程是消费者,则等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
},"Producer").start();
//消费者
new Thread(new Runnable() {
@Override
public void run() {
synchronized (clerk) {
while(true) {
if(Clerk.production != 0) {//商品有库存,则可以消费
System.out.println("[" + Thread.currentThread().getName() + "]" + "库存为:" + Clerk.production + ", 可以消费!");
while(Clerk.production > 0) {
Clerk.production--;//消费商品
System.out.println("[" + Thread.currentThread().getName() + "]" + "已消费, 库存为:" + Clerk.production);
}
System.out.println("[" + Thread.currentThread().getName() + "]" + "消费完毕!");
clerk.notify();//消费完毕之后,唤醒生产者
}else {
try {
clerk.wait();//如果目前线程是生产者,则等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
},"Consumer").start();;
}
}
class Clerk{
public static int production = 0;//店铺持有的库存量
}
2. 分类实现:
package thread;
public class ProducerConsumer2 {
public static void main(String[] args) {
//传入t1, t2的对象均是同一个Clerk对象,不会出现错误
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Thread t1 = new Thread(new ImpRunnable1(producer), "生产者");//生产者线程
Thread t2 = new Thread(new ImpRunnable2(consumer), "消费者");//消费者线程
t1.start();
t2.start();
}
}
//店铺类,负责协调进程,除了初始化production以外,还负责传入synchronized中负责同步进程
class Clerk{
public static int production = 0;
}
//实现producer线程
class ImpRunnable1 implements Runnable{
Producer producer;
public ImpRunnable1(Producer producer) {
this.producer = producer;
}
@Override
public void run() {
producer.product();
}
}
//实现consumer线程
class ImpRunnable2 implements Runnable{
Consumer consumer;
public ImpRunnable2(Consumer consumer) {
this.consumer = consumer;
}
@Override
public void run() {
consumer.consumer();
}
}
//生产者类
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
public void product() {
synchronized(clerk) {
while(true) {
if(Clerk.production != 5) {
System.out.println("[" + Thread.currentThread().getName() + "]" + "库存为:" + Clerk.production + ", 开始生产!");
while(Clerk.production < 5) {
Clerk.production++;
System.out.println("[" + Thread.currentThread().getName() + "]" + "已生产, 库存为:" + Clerk.production);
}
System.out.println("[" + Thread.currentThread().getName() + "]" + "生产完毕!");
clerk.notify();
}else {
try {
clerk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消费者类
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
public void consumer() {
synchronized(clerk) {
while(true) {
if(Clerk.production != 0) {
System.out.println("[" + Thread.currentThread().getName() + "]" + "库存为:" + Clerk.production + ", 可以消费!");
while(Clerk.production > 0) {
Clerk.production--;
System.out.println("[" + Thread.currentThread().getName() + "]" + "已消费, 库存为:" + Clerk.production);
}
System.out.println("[" + Thread.currentThread().getName() + "]" + "消费完毕!");
clerk.notify();
}else {
try {
clerk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
总结
开发中多线程的使用:
什么时候使用多线程?
程序中的几部分代码需要同时运行,为例提高效率,可以使用多线程。
开发中如果需要使用多线程,推荐使用内部类实现。
package thread.thread;
/**
* 什么时候使用多线程?
* 程序中的几部分代码需要同时运行, 为例提高效率, 可以使用多线程.
* 开发中如果需要使用多线程, 推荐使用内部类实现.
* @author 14251
*
*/
public class DevelopThread {
public static void main(String[] args) {
/*
* 假设有如下三部分代码需要同时执行, 否则由于第一部分代码执行
* 时间过长会影响其后代码的执行, 使用多线程加内部类完成.
*/
//线程1
new Thread(new Runnable() {
@Override
public void run() {
for(int x=1; x<=100; x++) {
System.out.println(Thread.currentThread().getName() + "-----" + x);
}
}
}).start();
//线程2
Runnable r = new Runnable() {
@Override
public void run() {
for(int x=1; x<=100; x++) {
System.out.println(Thread.currentThread().getName() + "-----" + x);
}
}
};
new Thread(r).start();
//主线程
for(int x=1; x<=100; x++) {
System.out.println(Thread.currentThread().getName() + "-----" + x);
}
}
}