JavaSE核心API--线程类

java.lang.Thread

1)理论讲解:获取当前线程

代码演示:

public static void main(String[] args) {
    // 获取运行main方法的线程(主线程)
    Thread main = Thread.currentThread();
    System.out.println("运行main方法的线程是:" + main);
    dosome();
    // 自定义线程
    Thread t = new Thread() {
        public void run() {
            Thread t = Thread.currentThread();
            System.out.println("自定义线程:" + t);
            dosome();
        }
    };
    t.start();
}

public static void dosome() {
    Thread t = Thread.currentThread();
    System.out.println("运行dosome方法的线程是:" + t);
}

2)理论讲解:守护线程

守护线程使用上与普通线程没什么区别,但是在结束时机上有一点不同:进程的退出
当一个进程退出时,所有的守护线程会被强制终止
进程的退出:当一个进程中所有普通线程结束时,进程退出
守护线程需要单独进行设置,因为默认创建出来的线程都是普通线程

代码演示:

public static void main(String[] args) {
    Thread rose = new Thread() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("rose:Let me go!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("rose:啊啊啊啊啊AAAAAAAaaaaa...");
            System.out.println("噗通");
        }
    };
    Thread jack = new Thread() {
        public void run() {
            while (true) {
                System.out.println("jack:You jump,I jump!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    rose.start();
    jack.setDaemon(true);// 设置守护线程必须在线程启动前进行!----让jack一直跑,如果rose停了,那么jack也跟着停了
    jack.start();
    System.out.println("main方法执行完了");
}

3)理论讲解:获取线程信息的相关方法

代码演示:

    // 获取主线程
    Thread main = Thread.currentThread();
    // id唯一标识
    long id = main.getId();
    System.out.println("线程id:" + id);
    // 线程名字
    String name = main.getName();
    System.out.println("线程name:" + name);
    // 线程优先级
    int priority = main.getPriority();
    System.out.println("线程优先级:" + priority);
    // 是否处于活动状态
    boolean isAlive = main.isAlive();
    System.out.println("isAlive:" + isAlive);
    // 是否为守护线程
    boolean isDaemon = main.isDaemon();
    System.out.println("isDaemon:" + isDaemon);
    // 是否被中断了
    boolean isInterrupted = main.isInterrupted();
    System.out.println("isInterrupted:" + isInterrupted);

4)理论讲解:Join方法

线程提供了一个Join方法,可以协调线程之间的同步运行

同步运行:运行有先后顺序
异步运行:运行代码是各干各的(多线程就是异步运行的)

代码演示:

   public static boolean isFinish = false;// 表示图片是否下载完毕

   public static void main(String[] args) {
        Thread download = new Thread() {
           public void run() {
                System.out.println("down:开始下载图片...");
                for (int i = 0; i <= 100; i++) {
                   System.out.println("已下载" + i + "%");
                   try {
                        Thread.sleep(50);
                   } catch (InterruptedException e) {
                        e.printStackTrace();
                   }
                }
                System.out.println("down:图片下载完毕");
                isFinish = true;
           }
        };
        Thread show = new Thread() {
           public void run() {
                System.out.println("show:开始显示图片...");
                /** 先等待下载线程将图片下载完毕 */
                try {
                   /** 当show线程调用download线程的join方法后就进入了阻塞状态,直到download执行完毕才会解除阻塞 */
                   download.join();// 也是处于阻塞状态,排在线程download后面,等线程download把所有事干完则解除阻塞,继续执行下面的代码
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
                if (!isFinish) {
                   throw new RuntimeException("图片加载失败");
                }
                System.out.println("show:显示图片完毕!");
           }
        };
        download.start();
        show.start();
   }

5)理论讲解:线程的优先级

线程的优先级
线程启动后便纳入到了线程调度中统一管理
线程无法主动获取CPU时间片,何时获取完全听线程调度统一管理
调节线程的优先级可以最大程度改善获取CPU时间片的几率

代码演示:

    Thread max = new Thread() {
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("max");
            }
        }
    };
    Thread norm = new Thread() {
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("nor");
            }
        }
    };
    Thread min = new Thread() {
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("min");
            }
        }
    };
    max.setPriority(Thread.MAX_PRIORITY);
    min.setPriority(Thread.MIN_PRIORITY);

    min.start();
    norm.start();
    max.start();

6)理论讲解:Sleep阻塞

Sleep阻塞
static void sleep(long ms)
该方法可以让运行这个方法的线程进入阻塞状态指定毫秒
超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行

代码演示:

    /*
     * 倒计时程序 程序启动后,输入一个数字,然后从该数字开始每秒递减并输出,到0为止
     */
    System.out.println("程序开始了!");
    Scanner scan = new Scanner(System.in);
    System.out.println("请输入一个数字:");
    int number = scan.nextInt();
    System.out.println("现在开始倒计时:");
    for (int i = number; i > 0; i--) {
        System.out.println(number);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        number--;
    }
    System.out.println("倒计时结束!");
    scan.close();

7)理论讲解:中断异常InterruptedException

sleep方法要求处理中断异常InterruptedException
线程有一个方法:interrupt(),该方法是用来中断线程的
当一个线程调用sleep方法处于阻塞状态的过程中,其中断方法若被调用,则此时sleep方法会立即抛出中断异常,提示我们该线程的阻塞状态被中断

代码演示:

    /*
     * JDK1.8之前,有一个要求: 当一个方法中的局部内部类中若引用了这个方法的其他局部变量,那么该变量必须是final的
     */
    final Thread lin = new Thread() {
        public void run() {
            System.out.println("林:刚美容完,睡一会儿吧!");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
            }
            System.out.println("林:醒了!");
        }
    };
    Thread huang = new Thread() {
        public void run() {
            System.out.println("黄:开始砸墙!");
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(1000);
                    System.out.println("黄:80!");
                }
                System.out.println("咣当");
                System.out.println("黄:搞定!");
                lin.interrupt();// 中断lin的睡眠阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    lin.start();
    huang.start();

8)理论讲解:多线程并发安全问题

多线程并发安全问题
当多线程并发访问同一临界资源时,由于线程切换的不确定性导致操作顺序出现了混乱
未按照程序预想的流程执行而导致一系列问题,严重时可能导致系统瘫痪

代码演示:

public class SyncDemo1 {
   public static void main(String[] args) {

        Table table = new Table();
        Thread t1 = new Thread() {
           public void run() {
                while (true) {
                   int bean = table.getBean();
                   Thread.yield();// 模拟线程切换
                   System.out.println(getName() + ":" + bean);
                }
           }
        };

        Thread t2 = new Thread() {
           public void run() {
                while (true) {
                   int bean = table.getBean();
                   Thread.yield();// 模拟线程切换
                   System.out.println(getName() + ":" + bean);
                }
           }
        };

        t1.start();
        t2.start();

   }
}

class Table {
   // 桌子上有20个豆子
   private int beans = 20;

   /*
     * 当一个方法使用synchronized修饰后,该方法称为“同步方法” 即:多个线程不能同时进入方法内部执行
     * 这样保证了多个线程执行该方法时由异步执行强制变为了同步执行(各干各的变为排队执行),从而解决了多个线程“抢”的问题
     */
   public synchronized int getBean() {
        if (beans == 0) {
           throw new RuntimeException("没有豆子啦!");
        }
        Thread.yield();// 模拟线程切换,但不能确定切换到哪个线程
        return beans--;
   }
}

9)理论讲解:同步块

同步块
synchronized(同步监视器对象){
  需要同步运行的代码片段
}
同步块可以更精准的控制需要同步运行的代码片段,有效缩小同步范围可以在保证并发安全的前提下提高并发的效率

代码演示:

public class SyncDemo2 {
   public static void main(String[] args) {
        Shop shop = new Shop();
        Thread t1 = new Thread() {
           public void run() {
                shop.buy();
           }
        };
        Thread t2 = new Thread() {
           public void run() {
                shop.buy();
           }
        };
        t1.start();
        t2.start();
   }
}

class Shop {
   public void buy() {
        try {
           Thread t = Thread.currentThread();

           /** 挑衣服各干各的 */
           System.out.println(t.getName() + ":正在挑衣服...");
           Thread.sleep(5000);

           /** 试衣服排队干 */
           /*
             * 同步块需要指定同步监视器对象,即:"()"中的内容
             * 该对象可以是java中任何类型的实例,但是必须保证多个线程看到的该对象是“同一个”,否则该同步块达不到同步效果!!!
             * 
             */
           synchronized (this) {
    //     synchronized (new Object()) {//这样写就是错误的
                System.out.println(t.getName() + ":正在试衣服...");
                Thread.sleep(5000);
           }

           System.out.println(t.getName() + ":结账离开。");
        } catch (InterruptedException e) {
           e.printStackTrace();
        }
   }
}

10)理论讲解:静态方法使用synchronized修饰

静态方法上若使用synchronized修饰后,那么该方法一定具有同步效果
静态方法的同步监视器对象为当前类的类对象(Class类的实例)
java中每个被JVM加载的类都有且只有唯一的一个Class实例与之对应。

代码演示:

public class SyncDemo3 {
   public static void main(String[] args) {

        Thread t1 = new Thread() {
           public void run() {
                Boo.dosome();
           }
        };
        Thread t2 = new Thread() {
           public void run() {
                Boo.dosome();
           }
        };
        t1.start();
        t2.start();
   }
}

class Boo {
   public synchronized static void dosome() {
        try {
           Thread t = Thread.currentThread();
           System.out.println(t.getName() + ":正在执行dosme...");
           Thread.sleep(5000);
           System.out.println(t.getName() + ":执行dosme完毕!");
        } catch (Exception e) {
           e.printStackTrace();
        }
   }
}

11)理论讲解:互斥锁

互斥锁
当使用synchronized锁定多个代码片段,并且这些同步块使用的同步监视器对象(上锁的对象)为同一个(引用类型一样)时
那么这些代码片段就是互斥的,多个线程不能同时执行它们

代码演示:

public class SyncDemo4 {
   public static void main(String[] args) {

        Foo foo = new Foo();
        Thread t1 = new Thread() {
           public void run() {
                foo.methodA();
           }
        };
        Thread t2 = new Thread() {
           public void run() {
                foo.methodB();
           }
        };
        t1.start();
        t2.start();

   }

}

class Foo {
   public synchronized void methodA() {
        try {
           Thread t = Thread.currentThread();
           System.out.println(t.getName() + ":正在执行A方法...");
           Thread.sleep(5000);
           System.out.println(t.getName() + ":执行A方法完毕!");
        } catch (Exception e) {
           e.printStackTrace();
        }
   }

   public void methodB() {
        synchronized (this) {
           try {
                Thread t = Thread.currentThread();
                System.out.println(t.getName() + ":正在执行B方法...");
                Thread.sleep(5000);
                System.out.println(t.getName() + ":执行B方法完毕!");
           } catch (Exception e) {
                e.printStackTrace();
           }
        }

   }
}

12)理论讲解:多线程

多线程
多线程允许我们“同时”执行多段代码
实际上多线程是并发运行的,每段代码都是走走停停的,CPU会在这些线程间快速的切换,保证每段代码都有进度
从而感官上是同时运行的效果
线程的创建有两种模式
方式一:
  定义一个线程类并继承线程Thread,然后重写其run方法
  run方法用来定义线程要执行的任务代码

代码演示:

public class ThreadDemo1 {
   public static void main(String[] args) {
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        t1.start();
        t2.start();
   }
}

/** 创建两个线程让两个for并发运行 */
/*
* 第一种创建线程的方式有两个不足:
* 1.由于java是单继承的,这导致若继承了Thread则无法再继承其他类,便无法继承其他类来复用代码,实际开发不方便
* 2.由于我们在线程内部直接重写run方法定义了线程要执行的任务,这导致该线程只能执行该任务,使得线程与任务存在一个必然的耦合关系,复用性变得很差
*/
class MyThread1 extends Thread {
   @Override
   public void run() {
        for (int i = 0; i < 1000; i++) {
           System.out.println("你是谁啊?");
        }
   }
}

class MyThread2 extends Thread {
   @Override
   public void run() {
        for (int i = 0; i < 1000; i++) {
           System.out.println("我是查水表的!");
        }
   }
}

13)理论讲解:第二种创建线程的方式

第二种创建线程的方式:
实现Runnable接口单独定义线程任务

代码演示:

public class ThreadDemo2 {
   public static void main(String[] args) {
        // 单独实例化任务
        Runnable r1 = new MyRunnable1();
        Runnable r2 = new MyRunnable2();
        // 创建线程
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        // 让线程跑起来
        t1.start();
        t2.start();
   }
}

class MyRunnable1 implements Runnable {

   @Override
   public void run() {
        for (int i = 0; i < 1000; i++) {
           System.out.println("你是谁啊?");
        }
   }

}

class MyRunnable2 implements Runnable {

   @Override
   public void run() {
        for (int i = 0; i < 1000; i++) {
           System.out.println("我是查水表的!");
        }
   }

}

14)理论讲解:使用匿名内部类的方式完成两种线程的创建

代码演示:

    // 方式一
    Thread t1 = new Thread() {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 1000; i++) {
                System.out.println("你是谁啊?");
            }
        }
    };
    t1.start();
    // 方式二
    Runnable r = new Runnable() {

        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("我是查水表的!");
            }
        }
    };
    Thread t2 = new Thread(r);
    t2.start();

15)理论讲解:线程池

线程池 线程池主要解决两个问题:
1.重用线程 2.控制线程数量

频繁创建销毁线程会给系统带来不必要的开销,所以线程尽量去重复使用

当系统中并发运行的线程数量过多时,会导致CPU过度切换,导致每个线程运行效率下降
导致整体并发性能下降,并且线程数量过多占用的资源也会更多,因此我们要控制线程的数量

代码演示:

    ExecutorService threadPool = Executors.newFixedThreadPool(2);// 创建线程池
    for (int i = 0; i < 5; i++) {// 创建5个任务
        Runnable runn = new Runnable() {// 单独创建线程任务
            public void run() {
                Thread t = Thread.currentThread();
                try {
                    System.out.println(t + "正在执行任务...");
                    Thread.sleep(5000);
                    System.out.println(t + "执行任务完毕!!!");
                } catch (Exception e) {
                }
            }
        };
        System.out.println("创建了一个任务!");
        threadPool.execute(runn);// 指派一个任务给线程池
    }
    threadPool.shutdown();// 线程池关闭,不再接收新任务
    System.out.println("线程池关闭了!");