线程
线程(Thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
需要通过java.lang.Thread类来实现。
Thread类的特征
- 每个线程都是通过某个特定
Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体 - 通过该
Thread对象的start()方法来调用这个线程
创建和启动
两种创建方式,另一种实现Callable接口,只需了解
-
继承
Thread类(局限于Java的单继承限制,不推荐使用)- 定义子类继承
Thread类 - 子类重写
Thread类的run()方法 - 创建
Thread子类对象,即创建了线程对象 - 调用线程对象的
start()方法:启动线程,调用run()方法
public class Test{ public static void main(String[] args){ Thread t = new TestThread(); t.start();//调用了run方法,run方法中的代码会执行,但main方法也会继续,所以输出可能会互相穿插 //并且可能每次运行的结果都不同,看性能变化 System.out.println("--------"); System.out.println("--------"); System.out.println("--------"); } } class TestThread extends Thread{ @Override public void run(){ //此处放需要多线程运行的代码 for(int i = 0; i < 10; i++){ System.out.println("执行了"+i); } } } - 定义子类继承
-
实现
Runnable接口**(推荐使用)**- 定义子类,实现
Runnable接口 - 子类重写
Runnable接口中的run()方法 - 通过
Thread类有参构造函数创建线程对象 - 将
Runnable接口的子类对象作为实际参数传给Thread类的构造方法中 - 调用
Thread类的start()方法:开启线程,调用Runnable子类接口的run()方法
public class Test{ psv main(){ Thread t = new Thread(new TestRunnable()); Thread t2 = new Thread(new TestRunnable(),"thread-1");//第二个参数可以设置线程名称 t.setPriority(1); t2.setPriority(10); //这种创建方法,可以让多个线程处理同一个TestRunnable对象 t.start(); } } class TestRunnable implements Runnable{ @Override public void run(){ //此处放需要多线程运行的代码 for(int i = 0; i < 10; i++){ Thread.sleep(1000);//一秒循环一次 if( i%2 == 0){ Thread.yield();//线程让步 } System.out.println( Thread.currentThread().getName() + "线程执行了" + i); //线程名称可以这么使用 } } } - 定义子类,实现
两种创建方式的区别
- 继承
Thread:线程代码存放Thread子类run方法中。重写run方法 - 实现
Runnable:线程代码存在接口的子类的run方法。实现run方法 - 一般使用实现接口的方法创建
实现方法的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Callable接口创建(了解)
package com.volcano.thread;
import java.util.concurrent.*;
public class TestCallable {
public static void main(String[] args) {
test1();
test2();
}
static void test2(){
System.out.println("测试方式2");
CallableThread thread = new CallableThread();
ExecutorService service= Executors.newFixedThreadPool(1);
Future result = service.submit(thread);
try {
Integer num = (Integer) result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdownNow();
}
static void test1(){
System.out.println("测试方式1");
FutureTask futureTask = new FutureTask(new CallableThread());
new Thread(futureTask).start();
try {
Integer integer = (Integer) futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class CallableThread implements Callable{
//重写call方法,必须要有返回值
@Override
public Integer call(){
System.out.println("CallableThread");
return 11;
}
}
静态代理模式
package com.volcano.thread;
//静态代理模式
//真实对象和代理对象要实现同一个接口
//真实对象只用专注于自己的事情(结婚)
//代理对象帮真实对象做更多的事情(准备工作)
public class StaticProxy {
public static void main(String[] args) {
Person candashuai = new Person("灿大帅");
new WeddingCompany(candashuai).HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
class Person implements Marry{
public Person(String name) {
this.name = name;
}
public String name;
@Override
public void HappyMarry() {
//真实对象专注结婚
System.out.println(name+"快乐地结婚了");
}
}
class WeddingCompany implements Marry{
Person target;
public WeddingCompany(Person target) {
this.target = target;
}
@Override
public void HappyMarry() {
//代理对象,婚庆公司帮你准备
Ready();
target.HappyMarry();
End();
}
public void Ready(){
System.out.println("已经为"+target.name+"准备好了,可以结婚了");
}
public void End(){
System.out.println(target.name+"结婚完毕,收拾现场,结尾款");
}
}
已经为灿大帅准备好了,可以结婚了
灿大帅快乐地结婚了
灿大帅结婚完毕,收拾现场,结尾款
Lambda表达式
- λ希腊字母表中排序第十一位的字母,英文名称lambda
- 避免匿名内部类定义过多
- 其实质属于函数式编程的概念
- 去掉无意义代码,留下核心逻辑
Functional Interface
-
理解Functional Interface(函数式接口)是学习Java8 Lambda表达式的关键所在
-
函数式接口的定义:
-
任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口
public interface Runnable{ public abstract void run(); } -
对于函数式接口,我们可以通过lambda表达式创建该接口的对象
-
推导过程
package com.volcano.thread;
//Lambda表达式的演化过程,按序号顺序看
public class TestLambda {
//2.静态内部类
static class SomeGame2 implements Game{
@Override
public void play() {
System.out.println("I Play SomeGame2");
}
}
public static void main(String[] args) {
Game game=new SomeGame1();
game.play();
game=new SomeGame2();
game.play();
//3.局部内部类
class SomeGame3 implements Game {
@Override
public void play() {
System.out.println("I Play SomeGame3");
}
}
game=new SomeGame3();
game.play();
//4.匿名内部类
game=new Game() {
@Override
public void play() {
System.out.println("I Play SomeGame4");
}
};
game.play();
//5.lambda表达式
game=()->{System.out.println("I Play SomeGame5");};
//若方法中只有一句代码,可以简化去掉大括号,如下
//若()中有参数,可省略类型,要么全部去掉,要么都留着
//若是只有一个参数,()也可以去掉,无参必须有括号
//game=()->System.out.println("I Play SomeGame5");
game.play();
}
}
//0.定义一个函数式接口
interface Game{
void play();
}
//1.类实现接口
class SomeGame1 implements Game{
@Override
public void play() {
System.out.println("I Play SomeGame1");
}
}
I Play SomeGame1
I Play SomeGame2
I Play SomeGame3
I Play SomeGame4
I Play SomeGame5
多线程的核心概念
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
- 多线程会带来额外的开销,如CPU调度时间,并发控制开销
- 程序运行时,即使没有主动创建的线程,后台也会有多个线程,如主线程main(),gc线程
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
- 多线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为干预
Thread类的相关方法
(start和run已讲)
-
String getName()- 获取线程的名称,若创建时,没有指定,则系统默认添加为
Thread-num形式
- 获取线程的名称,若创建时,没有指定,则系统默认添加为
-
void setName(String name)- 设置线程的名称
-
static currentThread()- 静态方法,获取当前线程
线程的优先级
就是哪个线程有较大个概率被执行。**(只是概率,脸黑还是没用)**
优先级是用数字`1-10`表示,数字越大优先级越高,如果没有设置默认优先级是`5`。
-
int getPriority()- 获取优先级
-
void setPriority(int priority)- 设置优先级
-
static void yield()- 线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程。
-
void join()- 当某个程序执行流中调用其他线程的
join方法时,调用线程将被阻塞,直到加入的join线程执行完为止。低优先级线程被join也能执行
public class Test{ public static void main(){ Thread t = new Thread(new TestRunnable()); System.out.println("1"); t.join();//阻塞了main方法,会先把t线程执行完毕再继续 System.out.println("2"); } } - 当某个程序执行流中调用其他线程的
-
static void sleep(int millis)- 单位为毫秒
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- 抛出
InterruptedException异常
-
void stop()- 强制结束线程
-
boolean isAlive()- 获取线程是否存活
线程停止
- 不推荐使用JDK提供的stop(),destroy()方法【已废弃】
- 推荐线程自己停下来
- 建议使用一个标志位进行终止变量
package com.volcano.thread;
public class StopThread implements Runnable {
//建立一个标志位
boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("I am running "+i++);
}
}
public void stop(){
flag=false;
}
public static void main(String[] args) {
StopThread thread = new StopThread();
new Thread(thread).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main is running "+i);
if(i>900){
//通过改变标志符停止线程
thread.stop();
System.out.println("线程停止");
break;
}
}
}
}
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常 InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 网络延迟:方法问题的发生性
- 倒计时:倒计时或者刷新时间
- 每个对象都有一个锁,sleep不会释放锁
程序礼让
- 礼让线程,让当前正在执行的线程停止,但不阻塞
- 让线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功!看CPU心情
package com.volcano.thread;
public class TestYield implements Runnable {
public static void main(String[] args) {
TestYield testYield = new TestYield();
new Thread(testYield,"a").start();
new Thread(testYield,"b").start();
}
@Override
public void run() {
//正常情况为 aabb
System.out.println(Thread.currentThread().getName()+"执行了");
//礼让成功 会交叉出现
Thread.yield();
System.out.println(Thread.currentThread().getName()+"停止了");
}
}
线程强制执行(插队)
- join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
package com.volcano.thread;
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("VIP登场!统统闪开!"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin vip = new TestJoin();
Thread thread = new Thread(vip);
for (int i = 0; i < 500; i++) {
if(i==200){
thread.start();
thread.join();
}
System.out.println("平民排队中~"+i);
}
}
}
#结果太长,减缩版
平民排队中~×200
VIP登场!统统闪开!*1000
平民排队中~×300
线程状态观测
- Thread.State
- NEW,线程尚未启动
- RUNNABLE,线程在Java虚拟机中执行
- BLOCKED,线程被阻塞等待监视器锁定
- WATTING,线程正在等待另一个线程执行特定动作
- TIMED_WAITING,线程正在等待另一个线程执行动作达到指定等待时间
- TERMINATED,线程已退出
- 一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态
package com.volcano.thread;
public class TestThreadState implements Runnable{
public static void main(String[] args) {
TestThreadState state = new TestThreadState();
Thread thread = new Thread(state);
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
while(thread.getState()!=Thread.State.TERMINATED){
System.out.println(thread.getState());
}
System.out.println(thread.getState());
}
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(1);
}
System.out.println("end");
}
}
#截取部分结果
NEW
RUNNABLE
RUNNABLE
...
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
...
1
end
BLOCKED
TERMINATED
线程的优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 线程的优先级用数字表示,范围从1~10
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
- 使用以下方式改变或获取优先级
getPriority()setPriority(int num)
- 只是概率高了,而不是100%
package com.volcano.thread;
public class TestPriority implements Runnable{
public static void main(String[] args) {
System.out.println(Thread.currentThread().getState()+"-->"+Thread.currentThread().getPriority());
TestPriority testPriority = new TestPriority();
Thread t0 = new Thread(testPriority);
Thread t1 = new Thread(testPriority);
Thread t2 = new Thread(testPriority);
Thread t3 = new Thread(testPriority);
t0.setPriority(1);
t1.setPriority(4);
t2.setPriority(7);
t3.setPriority(10);
t0.start();
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getState()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待
package com.volcano.thread;
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Human human = new Human();
Thread tGod = new Thread(god);
//默认为false表示用户线程
tGod.setDaemon(true);
Thread tHuman = new Thread(human);
tGod.start();
tHuman.start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑你");
}
}
}
class Human implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("活到第"+i+"天");
}
}
}
上帝保佑你
..
活到第0天
..
上帝保佑你
..
活到第36499天
#即使用户线程结束了,上帝线程依然在执行,直到虚拟机结束
上帝保佑你
...
线程的同步
抢票模拟问题
package com.volcano.thread;
//线程的并发问题
public class TestThread2 implements Runnable{
int ticketCount=10;
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticketCount<=0){
break;
}
}
}
public static void main(String[] args) {
TestThread2 thread2 = new TestThread2();
new Thread(thread2,"小明").start();
new Thread(thread2,"小红").start();
new Thread(thread2,"黄牛党").start();
}
}
结果:
小红抢到了第10张票
黄牛党抢到了第9张票
小明抢到了第10张票
小红抢到了第7张票
小明抢到了第8张票
黄牛党抢到了第8张票
小红抢到了第6张票
黄牛党抢到了第6张票
小明抢到了第6张票
小明抢到了第3张票
黄牛党抢到了第5张票
小红抢到了第4张票
小红抢到了第2张票
小明抢到了第0张票
黄牛党抢到了第1张票
线程同步
解决上面的问题,需要队列和锁,队列保证顺序,锁保证安全。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延伸,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题
同步方法
显然有人抢到同一张票,或者直接抢到不存在的0票,面临这种问题,需要在处理问题的方法中添加synchronized关键字
//在普通方法上加同步锁 synchronized,锁的是个对象,不是某一个方法
//不同的对象就是不同的锁,普通方法加 synchronized,线程使用不同的此方法的对象,还有共享资源的问题
//在静态方法上添加 synchronized,则锁的是所有对象
package com.volcano.thread;
//线程的并发问题
public class TestThread2 implements Runnable{
int ticketCount=10;
boolean flag=true;
@Override
public void run() {
while (flag){
try {
buy();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized解决问题,锁的都是this这个对象
private synchronized void buy(){
if (ticketCount<=0){
stop();
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
}
private void stop(){
flag=false;
}
public static void main(String[] args) {
TestThread2 thread2 = new TestThread2();
new Thread(thread2,"小明").start();
new Thread(thread2,"小红").start();
new Thread(thread2,"黄牛党").start();
}
}
小明抢到了第10张票
小红抢到了第9张票
黄牛党抢到了第8张票
小明抢到了第7张票
黄牛党抢到了第6张票
小红抢到了第5张票
小红抢到了第4张票
小明抢到了第3张票
黄牛党抢到了第2张票
小明抢到了第1张票
同步块
- synchronized(obj){}
- obj称之为同步监视器,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程
- 第一个线程访问,锁定个同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个次访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
public void qukuan(int money){
synchronized(this){
//表示当前的对象的代码块被加了synchronized同步锁
//用this锁代码块是代表当前的对象,如果在其他方法中也有代码块使用的都是同一个同步锁
操作金额
}
}
public void qukuan2(int money,Account a){
synchronized(a){
//实现不同对象用不同的锁
操作金额
}
}
package com.volcano.thread;
public class TestSynBlock{
public static void main(String[] args) {
Station station = new Station();
new Thread(station,"小明").start();
new Thread(station,"小红").start();
new Thread(station,"黄牛党").start();
}
}
class Station implements Runnable{
int ticketCount=10;
boolean flag=true;
@Override
public void run() {
while (flag){
buy();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy(){
synchronized (this){
if(ticketCount<=0){
stop();
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
}
}
private void stop(){
flag=false;
}
}
Lock
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的ReentrantLock,可以显式加锁、释放锁
package com.volcano.thread;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread2 implements Runnable{
int ticketCount=500;
boolean flag=true;
//另锁私有并且为不可修改
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (flag){
try {
//建议放到try/catch中
lock.lock();
buy();
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}finally {
//解锁放到finally中
lock.unlock();
}
}
}
private void buy(){
if (ticketCount<=0){
stop();
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketCount--+"张票");
}
private void stop(){
flag=false;
}
public static void main(String[] args) {
TestThread2 thread2 = new TestThread2();
new Thread(thread2,"小明---").start();
new Thread(thread2,"小红--").start();
new Thread(thread2,"黄牛党").start();
}
}
synchronized和Lock的区别
- Lock是显式锁(手动开启和关闭,别忘记关闭),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩张性(提供更多的子类)
- 优先使用顺序:
- **Lock **> 同步代码块 >同步方法
线程的死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
问题的模拟
package com.volcano.thread;
//模拟小红和小兰二人撕逼,互相要对方的口红和镜子
public class TestDeadLock {
public static void main(String[] args) {
SiB xh = new SiB("小红",0);
SiB xl = new SiB("小兰",1);
new Thread(xh).start();
new Thread(xl).start();
}
}
class LipStick{
}
class Mirror{
}
class SiB implements Runnable{
//保证只有一个口红和镜子
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
String name;
int choice;
public SiB(String name, int choice) {
this.name = name;
this.choice = choice;
}
@Override
public void run() {
if(choice==0){
synchronized (lipStick){
System.out.println(name+"得到了口红的锁");
/*1.会死锁*/
synchronized (mirror){
System.out.println(name+"得到了镜子的锁");
}
System.out.println(name+"释放了口红的锁");
}
//2.不会死锁,开启后注释掉1的部分
// synchronized (mirror){
// System.out.println(name+"得到了镜子的锁");
// }
}else{
synchronized (mirror){
System.out.println(name+"得到了镜子的锁");
/*1.会死锁*/
synchronized (lipStick){
System.out.println(name+"得到了口红的锁");
}
System.out.println(name+"释放了镜子的锁");
}
/*2.不会死锁,开启后注释掉1的部分
synchronized (lipStick){
System.out.println(name+"得到了口红的锁");
}*/
}
}
}
小兰得到了镜子的锁
小红得到了口红的锁
#线程会死锁到这一步
#没有死锁的结果
小红得到了口红的锁
小兰得到了镜子的锁
小兰释放了镜子的锁
小红释放了口红的锁
小兰得到了口红的锁
小红得到了镜子的锁
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
只要想办法破坏一个或多个的条件就可以避免死锁的发生
线程通信
生产者和消费者
这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件。
解决方式
-
并发协作模型“生产者/消费者模式”-->管程法
-
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
-
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
-
缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
-
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
-
package com.volcano.thread;
//消费者和生产者和鸡的故事
public class TestPC {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
new Thread(producer).start();
new Thread(consumer).start();
}
}
//生产者
class Producer implements Runnable{
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
buffer.push(new Chicken(i));
System.out.println("生产了id为"+i+"的鸡");
}
}
}
//消费者
class Consumer implements Runnable {
Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了id为"+buffer.pop().id+"的鸡");;
}
}
}
//消费品:鸡
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class Buffer{
Chicken[] chickens = new Chicken[10];
int count=0;
synchronized void push(Chicken chicken){
//如果缓冲区的鸡满了就等
if(count==chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有空闲了,可以生产鸡了
chickens[count]=chicken;
count++;
//通知消费者可以消费了
this.notifyAll();
}
synchronized Chicken pop(){
//没有鸡了等待
if(count==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有鸡了可以消费
Chicken chicken=chickens[--count];
//通知生产者可以生产鸡了
this.notifyAll();
return chicken;
}
}
生产了id为0的鸡
消费了id为0的鸡
生产了id为1的鸡
消费了id为1的鸡
...
消费了id为96的鸡
消费了id为99的鸡
消费了id为98的鸡
消费了id为97的鸡
-
并发协作模型“生产者/消费者模式” —> 信号灯法
package com.volcano.thread; public class TestFlag { public static void main(String[] args) { Road road = new Road(100); Car car = new Car(road); Traffic traffic = new Traffic(road); new Thread(car).start(); new Thread(traffic).start(); } } //车子 class Car implements Runnable{ public Car(Road road) { this.road = road; } Road road; @Override public void run() { getStatus(); } synchronized void getStatus(){ //100辆车的状态 while (road.carNum>0){ if(!road.flag){ road.stop(); System.out.println("剩下"+road.carNum+"辆车正在等待中···"); } else{ road.go(); System.out.println("第"+road.carNum+"辆车正在通过~"); road.carNum--; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } //交通信号灯 class Traffic implements Runnable{ Road road; public Traffic(Road road) { this.road = road; } @Override public void run() { //每两秒切换一次信号灯 while(road.carNum>0){ try { changeFlag(); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } synchronized void changeFlag(){ road.flag=!road.flag; } } //路 class Road{ boolean flag=false; int carNum; public Road(int carNum) { this.carNum = carNum; } synchronized void stop(){ //绿灯则等待红灯 if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("红灯停止"); this.notifyAll(); } synchronized void go(){ //红灯则等待绿灯 if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("绿灯通行"); this.notifyAll(); } }
线程通信的方法
-
wait()- 放弃CPU、同步资源,使别的线程可访问并修改共享资源,直到其他线程通知,和sleep不同,会释放锁
-
wait(timeout)- 指定等待毫秒
-
notify()- 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
-
notifyAll()- 唤醒正在排队等待资源的所有线程结束等待。
Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完返回池中。
- 好处:
- 提高了响应速度(减少了创建新线程的时间)
- 降低了资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximunPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后悔终止
使用线程池
- JDK5起提供了线程池相关API:ExecutorService和Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnale
- Futrue submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package com.volcano.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
//创建线程池,指定大小
ExecutorService service = Executors.newFixedThreadPool(10);
//启动Runnable线程
service.execute(new OneThread());
service.execute(new OneThread());
service.execute(new OneThread());
//关闭连接
service.shutdown();
}
}
class OneThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2