java入门篇(29)多线程(补充二)

一、死锁现象

1.1 死锁的概念

多个线程抢占共享资源而出现的相互等待的现象

1.2 举例

问题描述:
两个线程都抢占共享资源a与b,除非都拿到a资源与b资源,否则不会释放已有的资源。

共享资源a与b

public class Thread_lock {
    public static Thread_lock a = new Thread_lock();
    public static Thread_lock b = new Thread_lock();
}

线程:

public class Thread_sub extends Thread {
    boolean flag;
    public Thread_sub(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        if(flag) {
            synchronized (Thread_lock.a) {
                System.out.println("线程一拿到了a资源");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(Thread_lock.b) {
                    System.out.println("线程二拿到了b资源");
                }
            }
        }
        else
            synchronized(Thread_lock.b) {
                System.out.println("线程二拿到了b资源");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(Thread_lock.a) {
                    System.out.println("线程二拿到了a资源");
                }
            }
    }
}

主函数:

public class Thread_main {
    public static void main(String[] args) {
        Thread_sub a = new Thread_sub(true);
        Thread_sub b = new Thread_sub(false);
        a.start();
        b.start();
    }
}

结果:

线程一拿到了a资源
线程二拿到了b资源

结果说明两个线程都只拿到了第一份资源,并且都等待对方释放已有的资源从而产生死锁现象。

结论:

  • 在拿到第一个资源之后,要sleep一段时间,作用是给第二个线程时间去抢占另一个资源,从而产生死锁现象。
  • 两个线程执行步骤(flag为true还是false)的判断条件可以封装进有参构造方法里。

二、生产者与消费者问题

2.1 问题描述

生产者生产资源,消费者消费资源。有资源时,生产者等待,通知消费者消费;
没资源时,消费者等待,通知生产者生产。

2.2 示例

生产者线程

public class produce_thread implements Runnable {
    resource res = new resource();

    public produce_thread(resource res) {
        this.res = res;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res) {
                //有资源等待
                if (res.flag) {
                    try {
                        res.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没资源生产(更新产品编号,使用年限)
                res.ID = UUID.randomUUID().toString().substring(0, 3);
                res.yearsOfUse = new Random().nextInt();
                //改标志并通知消费者消费
                res.flag = true;
                res.notify();
            }
        }
    }
}

消费者线程:

public class user_thread implements Runnable {
    resource res = new resource();

    public user_thread(resource res) {
        this.res = res;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res) {
                //有资源消费
                if (res.flag) {
                    System.out.println("资源的编号:" + res.ID + " " + "资源的使用年限" + res.yearsOfUse);
                    res.flag = false;
                    res.notify();
                }
                //没资源等待
                try {
                    res.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

主函数:

public class thread_main {
    public static void main(String[] args) {
        resource res = new resource();
        user_thread a = new user_thread(res);
        produce_thread b = new produce_thread(res);
        Thread user = new Thread(a);
        Thread producer = new Thread(b);
        user.start();
        producer.start();
    }
}

资源:

public class resource {
    String ID;
    int  yearsOfUse;
    boolean flag;
}

总结:

  • 用继承Callable做本例的时候只能生产和消费一次,具体原因还未搞清楚。
  • wait()方法的作用是:释放当前锁,线程重新进入抢占资源状态。
  • notify()方法的作用时通知另一个线程去执行自己的任务。

2.3 sleep()方法和wait()方法的区别

相同点:

  • 都可以将时间量作为参数

  • 都可以使当前线程阻塞
    不同点:

  • sleep()方法不释放当前锁

  • wait()方法释放当前锁

三、线程池

3.1 线程池概述

线程池是一个容器,里面预先装有线程对象,并可以帮我们高效的管理线程对象我们自己手动创建线程,是比较耗费底层资源的,而且这个线程使用完之后,就死亡了,不能重复利用,JDK1.5之后,已经给我们提供好了线程池,我们只需要,往线程池里面提交任务即可。

3.2 获取线程池对象

Java给我们提供了一个工厂,用这个工厂类,来来获取线程池对象

  • public static ExecutorService newCachedThreadPool ()
    根据任务的数量来创建线程对应的线程个数

  • public static ExecutorService newFixedThreadPool ( int nThreads)
    固定初始化几个线程

  • public static ExecutorService newSingleThreadExecutor ()
    初始化一个线程的线程池

3.3线程池常用方法

  • submit(线程对象)
    往线程池中提交任务
  • shutdown()
    关闭线程池
public class Thraedpool {
    public static void main(String[] args) {
        mythread a = new mythread();
        mythread a1 = new mythread();
        muRunnable b = new muRunnable();
        muRunnable b1 = new muRunnable();
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.submit(a);
        pool.submit(a1);
        pool.submit(b);
        pool.submit(b1);
    }
}

线程:

public class muRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("qwq");
    }
}
public class mythread  extends Thread{
    @Override
    public void run() {
        System.out.println("qwq");
    }
}

四、定时器类 -Timer

4.1 作用

使线程执行的任务执行一次,或者定期重复执行。

4.2 Timer常用方法

  • public void schedule (TimerTask task,long delay)
    延迟给定时间后,执行任务
  • public void schedule (TimerTask task,long delay, long period);
    delay:执行第一个任务的延迟时间
    period:后续任务之间的时间间隔
  • public void schedule (TimerTask task, Date time):
    在给定时间点开始执行任务
  • public void schedule (TimerTask task, Date firstTime,long period):
  • void cancel()
    终止此计时器,丢弃所有当前已安排的任务。
  • boolean cancel()
    取消此计时器任务。

4.3 举例

public class Timer_main {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        mytimertask task = new mytimertask(timer);
        String str = "2019-01-30 17:06";
        Date date = new SimpleDateFormat("YYYY-MM-dd HH:MM").parse(str);
        timer.schedule(task,date,1000);
    }
}
public class mytimertask extends TimerTask {
    Timer timer = new Timer();
    public mytimertask(Timer timer) {
        this.timer = new Timer();
    }
    @Override
    public void run() {
        System.out.println("爆炸");
        this.timer.cancel();
    }
}

4.4 注意事项

  • 如果要停止定时器,在子程序里使用cancel()方法,即在重写的run方法中,在主程序中使用cancle方法会是计时器任务停止执行。
  • 注意period和delay的含义,不要搞混了。

五、volatile关键字

5.1 java内存模型

java内存模型规定所有的变量都放在主内存中,每个线程都有自己的工作内存,
线程的工作内存存放的的线程所要使用的变量,这些变量是从主内存中拷贝过来的, 线程对变量的所有操作都是在工作内存中进行的。不同线程间也不能直接读取对方工作内存的变量,需要通过主内存来间接完成。

5.2 可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即知道修改的值。

5.3 volatile关键字的作用

可以保证可见性,当一个共享变量(被多个线程操作的变量)被volatile关键字修饰时,这个关键字会保证这个共享变量被修改后的值立即更新到主内存中,这样其他线程使用该共享变量时拿到的就是该共享变量的最新值。而普通的共享变量不能保证内存可见性,因为普通变量一旦被修改,什么时候更新到主内存是不确定的,此时,其他线程读取该变量,在主内存中拿到的可能是该变量未更新的旧值。
通过synchronized和Lock也能够保证可见性,因synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码。

5.4 举例

public class mythread extends Thread {
    public static boolean flag = false;
    @Override
    public void run() {
        while (!flag) {
        }
    }
}

public class Tread_main {
    public static void main(String[] args) throws InterruptedException {
        mythread thread = new mythread();
        thread.start();
        Thread.sleep(100);
        mythread.flag = true;
    }
}

参考:

Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
http://www.cnblogs.com/mengheng/p/3495379.html

Java之 Timer 类的使用以及深入理解
https://www.cnblogs.com/xiaotaoqi/p/6874713.html#a

Java并发编程:volatile关键字解析
http://www.importnew.com/18126.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值