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("线程池关闭了!");