目录
死锁
死锁:过多的同步容易造成死锁
eg:
/**
* 过多的同步方法可能造成死锁
* @author qiao39gs
*
*/
public class SynDemo03 {
public static void main(String[] args) {
Object g = new Object();
Object m = new Object();
Test1 t1 = new Test1(g,m);
Test2 t2 = new Test2(g,m);
Thread proxy1 = new Thread(t1);
Thread proxy2 = new Thread(t2);
proxy1.start();
proxy2.start();
}
}
class Test1 implements Runnable{
Object goods;
Object money;
public Test1(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while(true){
test();
}
}
public void test(){
synchronized (goods) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money) {
}
}
System.out.println("一手给钱");
}
}
class Test2 implements Runnable{
Object goods;
Object money;
public Test2(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while(true){
test();
}
}
public void test(){
synchronized (money) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods) {
}
}
System.out.println("一手给货");
}
}
解决方法:生产者消费者模式
生产者消费者模式
- 生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者"在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
- 要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常常用的方法有信号灯法、管程等。如果解决方法不够完善,则容易出现死锁的情况。岀现死锁时,两个线程都会陷入休眠,等待对方唤醒自己
eg:信号灯法
/**
* 一个场景,共同的资源
* 生产者消费者模式 信号灯法
* wait():等待,释放锁 sleep 不释放锁
* notify()/notifyAll():唤醒
*
* @author qiao39gs
*
*/
public class Movie {
private String pic;
//flag ——> T 生产者生产,消费者等待,生产完后通知消费
//flag ——> F 消费者消费,生产者等待,消费完后通知生产
private boolean flag = true;
/**
* 播放
*/
public synchronized void play(String pic){
if(!flag){ //生产者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//开始生产
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("生产了:"+pic);
//生产完毕
this.pic = pic;
//通知消费
this.notify();
//生产者停下
this.flag = false;
}
public synchronized void watch(){
if(flag){ //消费者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//开始消费
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("消费了:"+pic);
//消费完毕
//通知生产
this.notifyAll();
//消费停止
this.flag = true;
}
}
/**
* 生产者
* @author qiao39gs
*
*/
public class Player implements Runnable{
private Movie m;
public Player(Movie m) {
super();
this.m = m;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
m.play("左青龙");
}else{
m.play("右白虎");
}
}
}
}
/**
* 消费者
* @author qiao39gs
*
*/
public class Watcher implements Runnable{
private Movie m;
public Watcher(Movie m) {
super();
this.m = m;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
m.watch();
}
}
}
public class App {
public static void main(String[] args) {
//共同的资源
Movie m = new Movie();
//多线程
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
任务调度
- Timer定时器类
- TimerTask任务类
- 通过 Java Timer、TimeTask:(spring的任务调度就是通过他们来实现的)
- 在这种实现方式中, Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实, Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的,而TimerTask类是一个抽象类,该类实现了 Runnable接口,所以该类具备多线程的能力
- 在这种实现方式中,通过继承 Timer Task使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行
- 在实际使用时,一个Timer可以启动任意多个 Timer Task实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动TimerTask实现
eg:
/**
* 了解
* schedule(任务,开始时间) //运行一次
* schedule(任务,开始时间,间隔时间) //重复运行
* @author qiao39gs
*
*/
public class TimeDemo01 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.println("so easy```");
}
},new Date(System.currentTimeMillis()+3000),200);
}
}
总结
一、创建线程
- 继承 Thread
- 实现 Runnable
- 实现 Callable(了解)
二、线程的状态
- 新生——>start——>就绪——>运行——>阻塞——>终止
- 终止线程
- 阻塞:join yield sleep
三、线程的信息
- Thread.currentThread
- 获取名称 设置名称 设置优先级 判断状态
四、同步:对同一份资源
synchronized(引用类型变量|this|类.class){
}
修饰符 synchronized 方法的签名{
方法体
}
过多的同步可能造成死锁
五、生产者消费者模式
六、任务调度
后期:juc quartz