java多线程这一篇就够了

1 线程的特点及进程和线程的区别:

  • 轻量级进程
  • 独立调度的基本单位
  • 可并发执行
  • 共享进程资源
区别进程线程
根本区别作为资源分配的单位调度和执行的单位
开销每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销线程可以看成轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小
所处环境再操作系统中能同时运行多个任务(程序)再同一应用中有多个顺序流同时执行
分配内存系统在运行的时候会为每个进程分配不同的内存区域除了CPU之外,不会为线程分配内(线程所使用的资源是它所属的进程的资源),线程组只能共享资源
包含关系没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。

2 两种线程创建方式的比较

  • 方式一:继承Thread类方式的多线程
    优势:编写简单
    劣势:无法继承其它父类(因为java是单继承)
  • 方式二:实现Runnable接口方式的多线程
    优势:可以继承其它类,多线程可共享同一个Runnable对象。
    劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThread()方法
    实现Runnable接口方式要通用一些。
public
class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

点进源码可以发现Thread类其实也是实现了Runnable接口

3 Thread类常用方法

方法功能
static Tread currentThread()得到当前线程
getName()返回线程名称
setName(String name)设置线程名称
int getPriority获得线程的优先级数值
viod setPriortiry设置线程的优先级数值
void start调用run()方法启动线程,开始执行线程
void run()存放线程体代码
isAlive()判断线程是否还”活着“,即线程是否还未终止

示例1

public class JoinThread extends Thread{
	public JoinThread() {
		super();
	}
	public void run() {
		for (int i = 1; i <=10; i++) {
			System.out.println(i+this.getName()+"=============");
		}
	}
	public static void main(String[] args) {//main方法是一个单独的线程
		for (int i = 1; i <=20; i++) {
			System.out.println(i+"-------csdn------");
		
		if (i == 5) {
			JoinThread joinThread = new JoinThread();
			joinThread.start();
			try {
				joinThread.join();//谁调用join方法,谁就强占cpu资源,直至执行结束
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
		}
	}
}

示例2

class TortoiseRunnable  implements Runnable {
	@Override
	public void run() {
		//Thread.currentThread().setName("乌龟线程");
		while(true){
			System.out.println("乌龟领先了-----"+Thread.currentThread().getName());
		}
	}
}

public class TestThreadTwo {
	public static void main(String[] args) {
		Thread.currentThread().setName("兔子线程");
		TortoiseRunnable tr = new TortoiseRunnable();		
		Thread th = new Thread(tr);
		th.setName("乌龟线程");
		th.start();
//		Thread th2 = new Thread(tr);
//		Thread th3 = new Thread(tr);
		while(true){
			System.out.println("兔子领先了-----"+Thread.currentThread().getName());
		}
	}
}

线程的生命周期图解

在这里插入图片描述

4 线程的控制方法

方法作用补充
join () -强势阻塞指定线程等到另一个线程完成以后再继续执行调用Jion方法的线程,完全获取cpu的使用权,直到这个线程结束为止,其他线程处于阻塞等待状态。
sleep () -睡眠使线程停止运行一段时间,将处于阻塞状态如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!
yield () -投降让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态如果调用了yield方法之后,没有其他等待执行的线程,这个时候当前线程就会马上恢复执行!
setDaemon(true)- 可以将指定的线程设置成后台线程(也叫守护线程)创建后台线程的线程结束时,后台线程也随之消亡只能在线程启动之前把它设为后台线程
stop() 停止结束线程(不推荐使用)

示例1:优先级别

/**
 * 查看和修改线程的优先级别
 * 
 * 1.如何查看优先级别
 * getPriority()
 * 
 * 2.如何修改优先级别
 * setPriority(7);
 * 
 * 3.优先级别的级别
 *  public final static int MIN_PRIORITY = 1;
 *  public final static int MAX_PRIORITY = 10;
 *  public final static int NORM_PRIORITY = 5;

注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。
*/
class TortoiseThread extends Thread{
	/**
	 * 线程体
	 */
	@Override
	public void run() {
		//this.setName("乌龟线程");
		while(true){
			System.out.println("乌龟领先了-----"+this.getName()+",优先级别:"+this.getPriority());
		}
	}
}

public class TestThread1 extends Object{

	public static void main(String[] args) {
		Thread.currentThread().setName("兔子线程");
		Thread.currentThread().setPriority(8);
		
TortoiseThread tt = new TortoiseThread();//新生状态
		//tt.run();
		tt.setName("乌龟线程");
		tt.setPriority(7);
		tt.start();//启动线程  就绪状态
		while(true){
			System.out.println("兔子领先了----"+Thread.currentThread().getName()
					+",优先级别:"+Thread.currentThread().getPriority());
		}
	}
}

示例2:join

/**
 * join  阻塞指定线程等到另一个线程完成以后再继续执行 
 * 注意事项:join应该在start之后
*/
public class TestJoin extends Thread{
	public TestJoin() {
		super();		
	}
	public TestJoin(String name) {
		super(name);
	}
	public void run(){
		for(int i=0;i<3;i++){
			System.out.println(i+"---"+this.getName()+"----");
		}
	}	
	public static void main(String[] args) {
		for(int i=0;i<10;i++){			
			System.out.println(i+"----main-----------");
			if(i==5){
				TestJoin tj = new TestJoin("程咬金");
				
				tj.start();
				try {
					tj.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}	
}

运行结果

0----main-----------
1----main-----------
2----main-----------
3----main-----------
4----main-----------
5----main-----------
0---程咬金----
1---程咬金----
2---程咬金----
6----main-----------
7----main-----------
8----main-----------
9----main-----------

示例3:Sleep

/**
 * 功能1:预备  3 2 1 开始
*/
public class TestSleep {
	public static void main(String[] args) {
		System.out.println("预备");
		for(int i=3;i>0;i--){
			System.out.println(i);
			//sleep 1秒
			try {
				Thread.sleep(1000);				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
		System.out.println("开始");
	}
}

运行结果

预备
3(输出后睡眠一秒)
2(输出后睡眠一秒)
1(输出后睡眠一秒)
开始

示例4:yield() 投降

/**
 * Thread.yield()
 * 让出CPU,进入就绪状态,继续排队抢占CPU
 * 如果调用了yield方法之后,没有其他等待执行的线程,这个时候当前线程就会马上恢复执行!

 下面程序达到一种交替输出的效果,但是不明显!有点随机性
*/
public class TestYield {
	public static void main(String[] args) {
		//创建两个线程
		FirstThread  ft  = new FirstThread();
		SecondThread st = new SecondThread();
		//启动两个线程
		ft.start();
		st.start();
	}
}

class FirstThread extends Thread{
	public void run(){
		for(int i=1;i<=10;i++){
			System.out.println("firstThread "+i);
//			try {
//				Thread.sleep(10);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			Thread.yield();
		}
	}
}
class SecondThread extends Thread{
	public void run(){
		for(int i=1;i<=10;i++){
			System.out.println("secondThread "+i);
//			try {
//				Thread.sleep(10);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			Thread.yield();
		}
	}
}

Thread.sleep(10)后的运行结果

firstThread 1
secondThread 1
firstThread 2
secondThread 2
firstThread 3
secondThread 3
firstThread 4
secondThread 4
firstThread 5
secondThread 5
secondThread 6
firstThread 6
secondThread 7
firstThread 7
firstThread 8
secondThread 8
secondThread 9
firstThread 9
firstThread 10
secondThread 10

示例5:Daemon-守护线程

  • 线程对象. setDaemon (true) ;设置为守护线程
  • 线程有两类:用户线程(前台线程)、守护线程(后台线程)
  • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
  • 垃圾回收器线程属于守护线程。
/**
 * Daemon后台程序
 * 功能:如果main线程结束后,由main线程启动的线程也应该随之结束。
 * 解决:定义为后台线程
 * setDaemon(true);
*/
public class TestSetDaemon extends Thread{
	public void run(){
		while(true){
			System.out.println("-----"+this.getName()+"------");
		}
	}
	
	public static void main(String[] args) {
		TestSetDaemon tsd = new TestSetDaemon();
		tsd.setDaemon(true);//必须放在start之前
		tsd.start();		
		
		for(int i=0;i<100;i++){
			System.out.println("-------main-----------"+i);
		}
	}
}

5 线程同步

同步代码块synchronized (obj){ }
同步方法private synchronized void makeWithdrawal(int amt) {}

同步监视器

  • synchronized (obj){ }中的obj称为同步监视器
  • 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器。
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this,也就是该对象本身。

同步监视器的执行过程

  • 第一个线程访问,锁定同步监视器,执行其中代码
  • 第二个线程访问,发现同步监视器被锁定,无法访问,等待
  • 第一个线程访问完毕,解锁同步监视器。
  • 第二个线程访问,发现同步监视器未锁,锁定并访问
线程同步的好处解决了线程安全问题
线程同步的缺点–性能下降(会带来死锁)
死锁(多线程编程时应该注意避免死锁的发生当两个线程相互等待对方释放“锁”时就会发生死锁,出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

同步示例1:四个窗口卖票不安全问题

功能3:
public class TicketRunnable implements Runnable {
	private int tickNum=200;
	@Override
	public void run() {
		while(tickNum>0){
			//1
	System.out.println(Thread.currentThread().getName()+"窗口卖出第"+tickNum+"张票");
			//----切换
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//2
			tickNum--;
		}
	}
}
------------------------------------------------------------------------------
/**
 * 经典应用:模拟线程的切换,让线程在此处交替执行
*/
public class TestTicket{
	public static void main(String[] args) {
		//创建四个窗口线程
		TicketRunnable tr = new TicketRunnable();
		Thread th1 = new Thread(tr);
		Thread th2 = new Thread(tr);
		Thread th3= new Thread(tr);
		Thread th4 = new Thread(tr);
		//开始卖票
		th1.start();
		th2.start();
		th3.start();
		th4.start();
	}
}

运行结果(同一张票会卖出多次)

Thread-0窗口卖出第44张票
Thread-1窗口卖出第44张票
Thread-2窗口卖出第44张票
Thread-2窗口卖出第41张票
Thread-0窗口卖出第40张票
Thread-1窗口卖出第41张票

同步示例2:使用线程同步解决四个窗口卖票安全问题

public class TestTicket {
	public static void main(String[] args) {
		//1.
		TicketRunnable tr = new TicketRunnable();
		//TicketRunnable2 tr = new TicketRunnable2();
		Thread th1 = new Thread(tr);//1、新生状态
		Thread th2 = new Thread(tr);
		Thread th3 = new Thread(tr);
		Thread th4 = new Thread(tr);
		
		th1.start();//2、就绪状态
		th2.start();
		th3.start();
		th4.start();
	}
}

/**
解决:方式一  同步代码块
用Runnable接口  共享票数 
最终解决:线程同步(后面解决)
synchronized 
sleep的作用
 * 阻塞当前的线程,指定的时间后解除阻塞
 * 在此时间内,即使cpu是空闲的,线程亦不会解除阻塞
 * @author enbo
 * @date 2018年3月1日
 */
class TicketRunnable implements Runnable{
    private Integer num = 50;
	@Override
	public void run() {//3.运行状态
		 while(num > 0){//循环
		 synchronized (this) {
		    if(num <=0){
		    	  break;
		    }
	System.out.println(Thread.currentThread().getName()+" 卖了第"+(num)+"张票!");
//			try {
//				Thread.sleep(10);//放弃cpu的执行权  睡眠状态  挂起
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			//切换
			num--;//
		  }
		}
	}
}
/**
解决:方式二:同步方法
用Runnable接口  共享票数 
最终解决:线程同步(后面解决)
synchronized 
sleep的作用
 * 阻塞当前的线程,指定的时间后解除阻塞
 * 在此时间内,即使cpu是空闲的,线程亦不会解除阻塞

 * @author enbo
 * @date 2018年3月1日
 */
class TicketRunnable2 implements Runnable{
    private int num = 50;
	@Override
	public void run() {//3.运行状态
		 while(num > 0){//
			if(num <= 0){
				break;
			}else{
				sellTickt();
			}
		}
	}
	private synchronized void sellTickt() {
		if(num > 0){
	System.out.println(Thread.currentThread().getName()+" 卖了第"+(num)+"张票!");
			try {
		           Thread.sleep(10);//放弃cpu的执行权  睡眠状态  挂起
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//切换
			num--;//
		}
	 }
}

同步示例3:使用同步方法(TicketRunnable2)解决四个窗口卖票安全问题(写法2)

class TicketRunnable2 implements Runnable{
    private int num = 50;
	public void run(){
		while(true){
			boolean flag = sellOne();
			if(flag == true){
				break;
			}
		}
	}

	private synchronized boolean sellOne() {
		if(num<=0){
			return true;
		} else {
		System.out.println(Thread.currentThread().getName()+" 卖了第"+(num)+"张票!");
				try {
			           Thread.sleep(10);//放弃cpu的执行权  睡眠状态  挂起
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				num--;//
				return false;
		}
	}
}

死锁

/**
 * 模拟两个哲学家吃饭的问题
 * 创建连个锁对象
 */
class MyLock {
    //两个锁(两根筷子)
	public static Object a = new Object();
	public static Object b = new Object();
}

class Boy extends Thread{
    @Override
    public void run() {
    	synchronized (MyLock.a) {
			System.out.println("男孩拿到了a筷子");
			synchronized(MyLock.b){
				System.out.println("男孩拿到了b筷子");
				System.out.println("男孩可以吃东西啦!");
			}
		}
    }
}

class Girl extends Thread{
	@Override
    public void run() {
    	synchronized (MyLock.b) {
			System.out.println("女孩拿到了b筷子");
			synchronized(MyLock.a){
				System.out.println("女孩拿到了a筷子");
				System.out.println("女孩可以吃东西啦!");
			}
		}
    }
}

public class Test {
	public static void main(String[] args) {
		Boy boy = new Boy();
		Girl girl = new Girl();
		boy.start();
		girl.start();
	}
}


/**
 * 增加睡眠时间解决死锁问题
 */
//public class Test {
//	public static void main(String[] args) throws Exception {
//		Boy boy = new Boy();
//		Girl girl = new Girl();
//		boy.start();
//		Thread.sleep(100);//增加睡眠,解除死锁问题
//		girl.start();
//	}
//}

6 线程通信

•应用场景:生产者和消费者问题
–假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
–如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
–如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
•分析
•这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
•对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
•对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费

•在生产者消费者问题中,仅有synchronized是不够的
•synchronized可阻止并发更新同一个共享资源,实现了同步
•synchronized不能用来实现不同线程之间的消息传递(通信)

方法名作用
final void wait()表示线程一直等待,直到其他线程通知
void wait(long timeout)线程等待指定毫秒参数时间
fianl void wait(long timeout,int nanos)线程等待指定毫秒,微秒的时间
final void notify()唤醒一个处于等待状态的线程
final coid notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先执行
注意:均是java.lang.Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常(java.lang.IllegalMonitorStateException)

案例一(生产者生产一件,消费者消费一件)

生产者生产面包,消费者消费面包,要求仓库有面包时,提醒消费者消费,生产者出于等待状态;当没有面包的时候,提醒生产者生产,消费者处于等待。即生产一件,消费一件。

基本思路:
1.创建面包类,构建相关的属性和方法。
2.创建面包仓库类,面包类对象传入,并创建sychronized修饰的同步方法——input和output方法。
3.创建两个线程,生产者product和消费者consumer,分别继承Runnable,重写run()方法,并分别调input和output方法,保证同步。
4.在主方法创建Thread,并start。
/**
* 面包类实体类
*/
class Bread {
    private int id;
    private String productName;

    public Bread() {
    }
    public Bread(int id, String productName) {
        this.id = id;
        this.productName = productName;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getProductName() {
        return productName;
    }
    public void setProductName(String productName) {
        this.productName = productName;
    }
}

/**
* 面包容器实体类
*/
class BreadCon {
    private Bread con;
    private boolean flag = false;   //判断仓库是否有面包,false表示没有

    public synchronized void input(Bread b) throws InterruptedException {
        if (flag) {
            this.wait();	//如果仓库有面包,则处于阻塞状态,等待消费者消费后唤醒
        }
        this.con = b;
        System.out.println(Thread.currentThread().getName() + "生产了" 
        + b.getId() + "号面包");
        flag = true;
        this.notify();	//仓库没有面包,则生产面包,并将标记改为true,唤醒消费者购买
    }

    public synchronized void output() throws InterruptedException {
        if (!flag) {
            this.wait();
        }
        Bread b= con;  
        con = null;
        System.out.println(Thread.currentThread().getName()+"消费了"
        +b.getId() + "号面包" +" 生产者:"+b.getProductName());
        flag = false;	//修改标记
        this.notify();	//唤醒生产者生产
    }
}

/**
* 生产者类
*/
class Product implements Runnable{
    private BreadCon con;
    public Product(BreadCon con) {
        this.con = con;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 30; i++) {
            Bread b = new Bread(i, Thread.currentThread().getName());
            try {
                this.con.input(b);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
* 消费者类
*/
class Consumer implements Runnable {
    private BreadCon con;
    public Consumer(BreadCon con) {
        this.con = con;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 30; i++) {
            try {
                con.output();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
* 测试类
*/
public class test {
    public static void main(String[] args) {
        //创建容器
        BreadCon con = new BreadCon();
        //生产
        Product product = new Product(con);
        //消费
        Consumer consumer = new Consumer(con);
        //线程对象启动
        new Thread(product, "面包生产商A").start();
        new Thread(consumer, "消费者1").start();
    }
}

运行结果

面包生产商A生产了1号面包
消费者1消费了1号面包 生产者:面包生产商A
面包生产商A生产了2号面包
消费者1消费了2号面包 生产者:面包生产商A
面包生产商A生产了3号面包
消费者1消费了3号面包 生产者:面包生产商A
面包生产商A生产了4号面包
......

如果生产方不止一个,消费方也不止一个,如何设计?

我们分别为生产者和消费者创建两个线程

new Thread(product, "面包生产商A").start();
new Thread(product, "面包生产商B").start();
new Thread(consumer, "消费者1").start();
new Thread(consumer, "消费者2").start();

运行结果(可能产生死锁和错误状态)

面包生产商A生产了1号面包
Exception in thread "消费者2" 消费者1消费了1号面包 生产者:面包生产商A
面包生产商B生产了1号面包
面包生产商A生产了2号面包
消费者1消费了2号面包 生产者:面包生产商A
面包生产商B生产了2号面包
面包生产商A生产了3号面包
消费者1消费了3号面包 生产者:面包生产商A
面包生产商B生产了3号面包
java.lang.NullPointerException
面包生产商A生产了4号面包
	at com.csqf.thread._a.BreadCon.output(test.java:56)
	at com.csqf.thread._a.Consumer.run(test.java:95)
	at java.lang.Thread.run(Thread.java:745)
消费者1消费了4号面包 生产者:面包生产商A

解决:案例二(生产方不止一个,消费方也不止)

将 if 判断语句改为 while 循环判断语句,保证被唤醒后,不再继续执行生产面包代码,而是返回进行循环判断判断
将 notify 改为 notifyAll ,避免发生死锁情况。

//这里只针对面包容器类进行修改
public class BreadCon {
    private Bread con;
    private boolean flag = false;   //判断仓库是否有面包,false表示没有

    public synchronized void input(Bread b) throws InterruptedException {
        while (flag) {
            this.wait();
        }
        this.con = b;
        System.out.println(Thread.currentThread().getName() + "生产了" + b.getId()
         + "号面包");
        flag = true;
        this.notifyAll();
    }
    public synchronized void output() throws InterruptedException {
        while (!flag) {
            this.wait();
        }

        /*Bread b= con;
        con = null;*/
        System.out.println(Thread.currentThread().getName()+"消费了"+con.getId() 
        + "号面包" +" 生产者:"+con.getProductName());
        flag = false;
        this.notifyAll();
    }
}

虽然解决了错误和死锁问题,但如果生产者和消费者数量及其庞大,每次notifyAll会将无关的线程也唤醒,增加了无效的判断时间,效率大打折扣,所以这里采用Condition接口,配合Lock接口下的ReentrantLock实现类解决效率问题。

采用Condition接口,配合Lock接口下的ReentrantLock实现类解决效率问题。

sychronized的锁方式,只能让生产者和消费者进入同一个阻塞队列,所以notifyAll会将队列中所有线程唤醒。

Condition接口则提供了两个队列,一个消费队列,一个生产队列,进行锁的微操,甚至可以自定义提供三个甚至更多队列,实现多线程的同步,并保证效率。

方法的使用

//创建Lock对象
Lock lock = new ReentrantLock();

//根据需求创建自定义数量的Condition条件队列
Condition condition = lock.newCondition();

//使某一队列等待,相当于wait()
condition.await();

//使某一队列唤醒,相当于notify()
condition.signal(); 

代码

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 面包类实体类
*/
class Bread {
    private int id;
    private String productName;

    public Bread() {
    }
    public Bread(int id, String productName) {
        this.id = id;
        this.productName = productName;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getProductName() {
        return productName;
    }
    public void setProductName(String productName) {
        this.productName = productName;
    }
}

/**
* 面包容器实体类
*/
//这里只提供核心代码块
class BreadCon {
  /*这里必须用-1,否则出现索引越界异常,因为后面消费者消费index--会出现index=-1的情况
   *为了避免因为生产者生产的时候breads[index]调取异常
   *会将index++放到赋值之前,保证索引从0开始
   */
  private int index = -1;
  private Bread[] breads = new Bread[5];

  //创建ReentrantLock锁对象,并创建两个阻塞队列,一个是生产者proCon,一个是消费者conCon
  private Lock lock = new ReentrantLock();
  private Condition proCon = lock.newCondition();
  private Condition conCon = lock.newCondition();

  public BreadCon() {
  }

  public void input(Bread bread) {
      lock.lock();    //上锁

      try {
          while (index > 3) {
              proCon.await(); //如果面包数量超过数组容量,则生产者处于生产阻塞队列
          }
          index++;    //因为Index是从-1开始,所以放到赋值之前,从0开始索引
          breads[index] = bread;
          System.out.println("生产者:" + Thread.currentThread().getName() 
          + "生产了" + bread.getId() + "号面包");
          conCon.signal();    //如果有面包,则将消费队列消费者唤醒
      } catch (InterruptedException e) {
          e.printStackTrace();
      } finally {
          lock.unlock(); //解锁
      }
  }

  public void output()  {
      lock.lock();

      try {
          while (index < 0) {
              try {
                  conCon.await();     //如果没有面包,则将消费者置于消费阻塞队列
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          Bread b = breads[index];
          breads[index] = null;
          System.out.println("消费者:" + Thread.currentThread().getName() 
          + "消费了" + b.getId() + "号面包,生产者是:" + b.getProductName());
          index--;
          proCon.signal();        //每消费一次面包,可以唤醒生产者生产
      } finally {
          lock.unlock();
      }
  }
}
/**
* 生产者类
*/
class Product implements Runnable{
    private BreadCon con;
    public Product(BreadCon con) {
        this.con = con;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 30; i++) {
            Bread b = new Bread(i, Thread.currentThread().getName());
            this.con.input(b);
        }
    }
}

/**
* 消费者类
*/
class Consumer implements Runnable {
    private BreadCon con;
    public Consumer(BreadCon con) {
        this.con = con;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 30; i++) {
            con.output();
        }
    }
}

/**
* 测试类
*/
public class test {
    public static void main(String[] args) {
        //创建容器
        BreadCon con = new BreadCon();
        //生产
        Product product = new Product(con);
        //消费
        Consumer consumer = new Consumer(con);
        //线程对象启动
        new Thread(product, "面包生产商A").start();
        new Thread(product, "面包生产商B").start();
        new Thread(consumer, "消费者1").start();
        new Thread(consumer, "消费者2").start();
    }
}

案例二:三个线程交替输出A B C ,输出20遍

这里需要构筑三个Condition队列锁。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class PrintABC {
    private int flag = 1;   //1表示A, 2表示B, 3表示C
    private Lock lock = new ReentrantLock();
    private Condition aCon = lock.newCondition();
    private Condition bCon = lock.newCondition();
    private Condition cCon = lock.newCondition();

    public void printA() {
        lock.lock();
        try {
            while (1 != flag) {
            	//这里注意,必须将阻塞放到循环判断中
            	//这样最后一次循环的时候,A被唤醒,可以继续执行下面的代码,依次唤醒BC,线程退出
            	//否则,如果将后面代码放到判断里,将里面的阻塞放到外面,则A被唤醒,却不能调用signal()方法,导致B/C一直被阻塞,main线程退出,但是子线程还在,发生死锁,后面循环也不会继续
                aCon.await();
            }
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName());
            bCon.signal();
            flag = 2;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            while (2 != flag) {
                bCon.await();
            }
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName());
            cCon.signal();
            flag = 3;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            while (3 != flag) {
                cCon.await();
            }
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName());
            aCon.signal();
            flag = 1;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

//测试方法
public class test {
    public static void main(String[] args) {
        PrintABC printABC = new PrintABC();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    printABC.printA();
                }
            }
        }, "A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    printABC.printB();
                }
            }
        }, "B").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    printABC.printC();
                }
            }
        }, "C").start();
    }
}

7 认识线程池

线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。

线程池
线程容器,可设定线程分配的数量上限
将预先创建的线程对象存入池中,并重用线程池中的线程对象
避免频繁的创建和销毁

线程池原理(图解)

在这里插入图片描述原理:将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。**

常用的线程池接口和类(所在包java.util.concurrent):

  • Executor:线程池的顶级接口。(查看API)
    — void execute(Runnable command);

  • ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。

  • Executors工厂类:通过此类可以获得一个线程池。
    —通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。
    参数:指定线程池中线程的数量。
    —通过newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,无上限。

案例示例(线程池的创建)
在这里插入图片描述

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 演示线程池的创建
 * Executor:线程池的顶级接口。
    --- void execute(Runnable command);
   
   ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
     --- ThreadPoolExecutor
        ---ScheduledThreadPoolExecutor
       以上两个线程池创建实现类可以创建线程,但是不推荐(一般用线程池工具类更方便、简单)
   
   Executors:创建线程池的工具类(推荐)
       (1)创建固定线程个数的线程池  newFixedThreadPool()
       (2)创建缓存线程池(由任务的多少来决定)newCachedThreadPool()
       (3)创建单线程池 newSingleThreadExecutor()
       (4)创建调度线程池(调度:按 周期、定时 执行)   newScheduledThreadPool()  
 * @author enbo
 * @date 2020年7月26日
 */
public class Test {
	public static void main(String[] args) {
		//1、创建线程(两种方式)
		//1.1、创建固定线程个数的线程池
		ExecutorService  es = Executors.newFixedThreadPool(4);
		//1.2、创建缓存线程池,线程个数由任务个数决定(动态创建)
		//ExecutorService  es = Executors.newCachedThreadPool();
		//1.3、创建单线程线程池(简单演示)
		//ExecutorService  es = Executors.newSingleThreadExecutor();
		//1.4、创建调度线程池(调度:按 周期、定时 执行)  (简单演示)
		//Executors.newScheduledThreadPool(corePoolSize);
		
        //2、创建任务
		//2.1 创建线程任务:卖火车票
		Runnable runnable = new Runnable(){
            private int ticket = 100;
			@Override
			public void run() {
				while(true){
					if(ticket <= 0){
						break;
					}
		System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
					ticket--;
				}
			}
		};
         //3、提交任务
		//2.2 for循环表示提交四次(因为线程池有四个线程)
		for (int i = 0; i < 4; i++) {
			es.submit(runnable);//提交一次,从池中获取一个线程,并启动
		}
		
		//3、关闭线程池
		es.shutdown();//等待所有任务执行完毕,然后关闭线程池
		//es.shutdownNow();//不会等待所有任务执行完毕,立即关闭线程池	
	}
}

Callable接口

Callable接口是创建线程类的第三种方式,前面两种方式分别是继承Thread类和实现Runnable接口。
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
Callable具有泛型返回值、可以声明异常。

public interface Callable<V>{
public V call() throws Exception;
}

案例示例(演示Callable的基本使用)

注意:如果只是为了创建线程,Callable其实看上去有点繁琐,还不如直接用前两种方式还简单一些,但是Callable结合线程池使用,就会很方便了。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * 演示Callable接口的使用
 * 
 * Callable与Runnable接口的区别?
 * Callable具有泛型返回值、可以声明异常,而Runnable接口没有。
 * 
 * 注意:Callable类似 Runnable,但是不能直接交给Thread,需要转换一个可执行任务 FutureTask
 * 而 FutureTask 间接实现了 Runnable接口(查看源码)

功能:使用  Callable   实现 1-100的和
 */
public class Demo2 {
      public static void main(String[] args) throws Exception {
		  //1、创建Callable对象
    	  Callable<Integer> callable = new Callable<Integer>(){
			@Override
			public Integer call() throws Exception {
				System.out.println(Thread.currentThread().getName()+"开始了。。。");
				int sum = 0;
				for (int i = 0; i <= 100; i++) {
					sum+=i;
					Thread.sleep(100);//故意休眠100毫秒
				}
				return sum;
			}
    	  };
    	  
    	 //2、Callable类似 Runnable,但是不能直接交给Thread,需要转换一个可执行任务 FutureTask
    	  FutureTask<Integer> task = new FutureTask<Integer>(callable);
    	  
    	  //3、创建线程,交给Thread
    	  Thread th = new Thread(task);
    	  
    	  //4、启动线程
    	  th.start();
    	 
    	  //5、获取结果(task.get() 会等待 call()方法执行完毕,才会返回)
    	  Integer sum = task.get();
    	  System.out.println(sum);
	  }
}

运行结果

Thread-0开始了。。。
5050

Callable结合线程池使用

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * Callable结合线程池使用
 * 功能:使用线程池计算1-100的和
 * 
 *  Future表示将要执行完任务的结果
 *
 */
public class Demo3 {
	public static void main(String[] args) throws Exception, ExecutionException {
		//1、创建线程池
		ExecutorService  es = Executors.newFixedThreadPool(1);
		//2、提交任务    Future表示将要执行完任务的结果
		Future<Integer> future = es.submit(new Callable<Integer>(){
		//Callable接口是创建线程类的第三种方式,前面两种方式分别是继承Thread类和实现//Runnable接口。

			@Override
			public Integer call() throws Exception {
			System.out.println(Thread.currentThread().getName()+"开始执行了。。。");
				int sum = 0;
				for (int i = 0; i <= 100; i++) {
					sum+=i;
					Thread.sleep(10);//故意休眠10ms
				}
				return sum;
			}
		});	
		//3、获取结果,等待任务执行完毕才返回
		System.out.println(future.get());
		//4、关闭线程池
		es.shutdown();
	}
}

注意:实际工作中,我们一般也是使用 Callable与线程池联合使用!

Future接口(示例案例) : 表示将要完成任务的结果

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * 需求:使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。
 */
public class Demo4 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//1、创建线程池
		ExecutorService es = Executors.newFixedThreadPool(2);
		//2、提交任务
		//第1个任务
		Future<Integer> future1 = es.submit(new Callable<Integer>(){
			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for (int i = 0; i <= 50; i++) {
					sum+=i;
				}
				System.out.println("1-50计算完毕!");
				return sum;
			}
		});
		//第2个任务
		Future<Integer> future2 = es.submit(new Callable<Integer>(){
			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for (int i = 51; i <= 100; i++) {
					sum+=i;
				}
				System.out.println("51-100计算完毕!");
				return sum;
			}
		});
		//3、获取结果
		int sum = future1.get()+future2.get();
		System.out.println("结果是:"+sum);
		//4、关闭线程池
		es.shutdown();
	}
}

同步(图解)

在这里插入图片描述

异步(图解)

在这里插入图片描述

8 (面试题)thread.Join

(有三个线程t1、t2、t3。确保三个线程t1执行完后t2执行,t2执行完成后t3执行)

/**
*把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的*Join()方法,直到线程A执行完毕后,才会继续执行线程B。
*/
public class ThreadTest {
 
	public static void main(String[] args) {
		Thread t1 = new Thread(new Work(null),"线程t1");
		Thread t2 = new Thread(new Work(t1),"线程t2");
		Thread t3 = new Thread(new Work(t2),"线程t3");
		
		t1.start();
		t2.start();
		t3.start();			
	}
	
	static class Work implements Runnable{
		private Thread beforeThread;
		public Work(Thread beforeThread) {
			this.beforeThread = beforeThread;
		}
		@Override
		public void run() {
			if(beforeThread!=null) {
				try {
					//某线程调用该方法,会让其他线程处于等待状态,让其运行完毕,再执行其他线程.   
					beforeThread.join();
					System.out.println("Thread start:"+Thread.currentThread().getName());
				}catch(Exception e){
					e.printStackTrace();
				}
			}else {
				System.out.println("Thread start:"+Thread.currentThread().getName());
			}
			
		}
	}
}

9 (面试题)方法2:使用CountDownLatch

CountDownLatch(闭锁)是一个很有用的工具类,利用它我们可以拦截一个或多个线程使其在某个条件成熟后再执行。它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须大于0。另外它还提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0时就代表条件已成熟,所有因调用await方法而阻塞的线程都会被唤醒。这就是CountDownLatch的内部机制,看起来很简单,无非就是阻塞一部分线程让其在达到某个条件之后再执行。

import java.util.concurrent.CountDownLatch;

public class Thread2 {
    public static void main(String[] args) {
        CountDownLatch c1 = new CountDownLatch(0);//计数器为0
        CountDownLatch c2 = new CountDownLatch(1);//计数器为1
        CountDownLatch c3 = new CountDownLatch(1);//计数器为1

        Thread t1 = new Thread(new Work(c1, c2),"线程t1");
        //c1为0,t1线程可以执行。t1线程的计数器 c2 减1

        Thread t2 = new Thread(new Work(c2, c3),"线程t2");
        //t1的计数器c2为0时,t2才能执行。t2的计数器c3减1

        Thread t3 = new Thread(new Work(c3, c3),"线程t3");
        //t3的计数器c3为0时,t3才能执行

        t1.start();
        t2.start();
        t3.start();
    }
    //定义Work线程类,需要传入开始和结束的CountDownLatch参数
    static class Work implements Runnable {
        private CountDownLatch c1;
        private CountDownLatch c2;
        public Work(CountDownLatch c1, CountDownLatch c2) {
            super();
            this.c1 = c1;
            this.c2 = c2;
        }

        @Override
        public void run() {
            try {
                //当某个线程调用CountDownLatch对象的await方法时,将会阻塞,直到计数器的值变成0才放行。
                c1.await();
                System.out.println("thread start:" + Thread.currentThread().getName());
                c2.countDown(); //本线程计数器减 1
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jsxllht

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值