JavaSE 多线程(2)

骑猪看日落 2022-09-26 11:49 105阅读 0赞

死锁

多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁.
为了避免死锁出现,最好不要同步代码块嵌套。
例子
饭桌上每个人都只有一边筷子,都在旁别的人把筷子借给自己吃饭
这里写图片描述

  1. public class Demo5_DeadLock {
  2. /** * @param args */
  3. private static String s1 = "筷子左";
  4. private static String s2 = "筷子右";
  5. public static void main(String[] args) {
  6. new Thread() {
  7. public void run() {
  8. while(true) {
  9. synchronized(s1) {
  10. System.out.println(getName() + "...获取" + s1 + "等待" + s2);
  11. synchronized(s2) {
  12. System.out.println(getName() + "...拿到" + s2 + "开吃");
  13. }
  14. }
  15. }
  16. }
  17. }.start();
  18. new Thread() {
  19. public void run() {
  20. while(true) {
  21. synchronized(s2) {
  22. System.out.println(getName() + "...获取" + s2 + "等待" + s1);
  23. synchronized(s1) {
  24. System.out.println(getName() + "...拿到" + s1 + "开吃");
  25. }
  26. }
  27. }
  28. }
  29. }.start();
  30. }
  31. }

单例设计模式

单例设计模式:保证类在内存中只有一个对象。

如何保证类在内存中只有一个对象呢?
(1)控制类的创建,不让其他类来创建本类的对象。private
(2)在本类中定义一个本类的对象。Singleton s;
(3)提供公共的访问方式。 public static Singleton getInstance(){return s}

单例的两种写法:
(1)饿汉式 开发用这种方式

  1. //饿汉式
  2. class Singleton {
  3. //1,私有构造函数,其他类就不能访问构造方法了
  4. private Singleton(){}
  5. //2,创建本类对象,private使外部不能随意更改
  6. private static final Singleton s = new Singleton();
  7. //3,对外提供公共的访问方法
  8. public static Singleton getInstance() {
  9. return s;
  10. }
  11. }

饿汉式 是不管你用不用,上来就创建对象
懒汉式 是事先做个判断 什么时候用什么时候创建

  1. //懒汉式, 单例的延迟加载模式 面试
  2. class Singleton {
  3. //1,私有构造函数
  4. private Singleton(){}
  5. //2,声明一个本类的引用
  6. private static final Singleton s;
  7. //3,对外提供公共的访问方法
  8. public static Singleton getInstance() {
  9. if(s == null)
  10. //线程1,线程2
  11. s = new Singleton();
  12. return s;
  13. }
  14. }

懒汉式在多线程使用时有安全隐患,有可能创建出多个对象出来。所以开发一般用饿汉式,面试一般是懒汉式。

饿汉式和懒汉式的区别
1,饿汉式是空间换时间,懒汉式是时间换空间
2,在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象

单例设计模式在多线程的应用场景

RunTime类
Runtime类就是一个单例类

  1. import java.io.IOException;
  2. public class Demo2_Runtime {
  3. /** * @param args * @throws IOException */
  4. public static void main(String[] args) throws IOException {
  5. Runtime r = Runtime.getRuntime(); //获取运行时对象
  6. //r.exec("shutdown -s -t 300"); //300秒后关机
  7. r.exec("shutdown -a"); //取消关机
  8. }
  9. }

Timer计时器

  1. import java.util.Date;
  2. import java.util.Timer;
  3. import java.util.TimerTask;
  4. public class Demo3_Timer {
  5. /** * @param args * @throws InterruptedException */
  6. public static void main(String[] args) throws InterruptedException {
  7. Timer t = new Timer();
  8. //在指定时间安排指定任务
  9. //第一个参数,是安排的任务,第二个参数是执行的时间,第三个参数是过多长时间再重复执行
  10. t.schedule(new MyTimerTask(), new Date(188, 6, 1, 14, 22, 50),3000);
  11. while(true) {
  12. Thread.sleep(1000);
  13. System.out.println(new Date());
  14. }
  15. }
  16. }
  17. class MyTimerTask extends TimerTask {
  18. @Override
  19. public void run() {
  20. System.out.println("起床背英语单词");
  21. }
  22. }

执行时间的参数设置
这里写图片描述


线程间的通信

两个线程间的通信
1.什么时候需要通信
-多个线程并发执行时, 在默认情况下CPU是随机切换线程的
-如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
2.怎么通信
- 如果希望线程等待, 就调用wait()
- 如果希望唤醒等待的线程, 就调用notify();
-这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

  1. public class Demo1_Notify {
  2. /** * @param args * 等待唤醒机制 */
  3. public static void main(String[] args) {
  4. final Printer p = new Printer();
  5. new Thread() {
  6. public void run() {
  7. while(true) {
  8. try {
  9. p.print1();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. }.start();
  16. new Thread() {
  17. public void run() {
  18. while(true) {
  19. try {
  20. p.print2();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  26. }.start();
  27. }
  28. }
  29. //等待唤醒机制
  30. class Printer {
  31. private int flag = 1;
  32. public void print1() throws InterruptedException {
  33. synchronized(this) {
  34. if(flag != 1) {
  35. this.wait(); //当前线程等待
  36. }
  37. System.out.print("华");
  38. System.out.print("中");
  39. System.out.print("科");
  40. System.out.print("技");
  41. System.out.print("大");
  42. System.out.print("学");
  43. System.out.print("\r\n");
  44. flag = 2;
  45. this.notify(); //随机唤醒单个等待的线程
  46. }
  47. }
  48. public void print2() throws InterruptedException {
  49. synchronized(this) {
  50. if(flag != 2) {
  51. this.wait();
  52. }
  53. System.out.print("软");
  54. System.out.print("件");
  55. System.out.print("工");
  56. System.out.print("程");
  57. System.out.print("\r\n");
  58. flag = 1;
  59. this.notify();
  60. }
  61. }
  62. }

1.在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
2.为什么wait方法和notify方法定义在Object这个类中?
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
3.sleep方法和wait方法的区别?
(1):sleep方法必须传入参数,时间到了就醒来
wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
(2):sleep方法在同步函数或同步代码块中,不释放锁
wait方法在同步函数或者代码块中释放锁,线程会放弃对象锁,进入等待此对象的等待锁定池


互斥锁


线程组

A:线程组概述
* Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
* 默认情况下,所有的线程都属于主线程组。
* public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
* public final String getName()//通过线程组对象获取他组的名字
* 我们也可以给线程设置分组
* 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
* 2,创建线程对象
* 3,Thread(ThreadGroup?group, Runnable?target, String?name)
* 4,设置整组的优先级或者守护线程

  1. public class Demo4_ThreadGroup {
  2. /** * @param args * ThreadGroup */
  3. public static void main(String[] args) {
  4. //demo1();
  5. ThreadGroup tg = new ThreadGroup("我是一个新的线程组"); //创建新的线程组
  6. MyRunnable mr = new MyRunnable(); //创建Runnable的子类对象
  7. Thread t1 = new Thread(tg, mr, "张三"); //将线程t1放在组中
  8. Thread t2 = new Thread(tg, mr, "李四"); //将线程t2放在组中
  9. System.out.println(t1.getThreadGroup().getName()); //获取组名
  10. System.out.println(t2.getThreadGroup().getName());
  11. tg.setDaemon(true);
  12. }
  13. //自己设定线程组
  14. public static void demo1() {
  15. MyRunnable mr = new MyRunnable();
  16. Thread t1 = new Thread(mr, "张三");
  17. Thread t2 = new Thread(mr, "李四");
  18. ThreadGroup tg1 = t1.getThreadGroup();
  19. ThreadGroup tg2 = t2.getThreadGroup();
  20. System.out.println(tg1.getName()); //默认的是主线程
  21. System.out.println(tg2.getName());
  22. }
  23. }
  24. class MyRunnable implements Runnable {
  25. @Override
  26. public void run() {
  27. for(int i = 0; i < 1000; i++) {
  28. System.out.println(Thread.currentThread().getName() + "...." + i);
  29. }
  30. }
  31. }

线程池

线程池概述
* 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

内置线程池的使用概述
* JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
* public static ExecutorService newFixedThreadPool(int nThreads)
* public static ExecutorService newSingleThreadExecutor()
* 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
* Future <?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)

  • 使用步骤:
    * 创建线程池对象
    * 创建Runnable实例
    * 提交Runnable实例
    * 关闭线程池

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    public class Demo5_Executors {

    1. /** * public static ExecutorService newFixedThreadPool(int nThreads) * public static ExecutorService newSingleThreadExecutor() */
    2. public static void main(String[] args) {
    3. ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
    4. pool.submit(new MyRunnable()); //将线程放进池子里并执行
    5. pool.submit(new MyRunnable());
    6. pool.shutdown(); //关闭线程池
    7. }

    }

    class MyRunnable implements Runnable {

    1. @Override
    2. public void run() {
    3. for(int i = 0; i < 1000; i++) {
    4. System.out.println(Thread.currentThread().getName() + "...." + i);
    5. }
    6. }

    }

输出结果:
这里写图片描述

发表评论

表情:
评论列表 (有 0 条评论,105人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JavaSE 线2

    死锁 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁. 为了避免死锁出现,最好不要同步代码块嵌套。 例子 饭桌上每个人都只有一边筷子,

    相关 JavaSE线

    写这篇文章的前提,楼主从学习java开始,自认为技术不错,反正肯费工夫学,为什么我现在又回来准备写一篇关于多线程文章,说真的,现在谁还用多线程?都是封装过的框架,根本不考虑什么

    相关 javaSE_线

    线程的创建和启动: A:继承Thread类创建线程 1.定义Thread类的子类,并重写run方法。 2.创建对象,调用start方法启动线程.   B:实现Runn

    相关 Javase线2

    多线程(2) 线程的生命周期 新建:创建线程对象 就绪:有执行资格,没有执行权 运行:有资格运行,有执行权 ​ 阻塞:由一些操作让线程处于改状态。没有执行资格