JavaSE学习笔记——多线程

线程

一、多线程的创建和启动

概念

  • 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
synchronizedLock.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);
		}
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值