线程(Thread)
进程:一个独立的正在执行的程序
线程:一条独立执行路径
进程和线程关系:一条进程可以包含一条或多条线程。
多线程的理解:
1.虚拟机至少开辟了两条线程:a.主线程 b.垃圾回收线程
2.多线程不能提高效率,反而会降低效率,但是可以提高CPU的使用率
3.CPU本质在某个时间刻度上只能够执行一个进程的一条线程的一个原子性语句(不可再分割的语句)
4.一个进程如果有多条执行路径,则成为多线程程序。
5.一个线程可以理解为进程的子任务。
线程生命周期
启动线程的五种方式
1.继承Thread的方式开启多线程
2.实现Runnable方式开启线程
3.实现 Callable 的方式开启线程(需要借助一个中间类: FutureTask)
4.匿名内部类方式开启线程
5.Lambda表达式方式开启线程
方法一:继承Thread的方式开启多线程
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
//继承Thread 拷贝文件
public class Demo01 {
public static void main(String[] args) {
copyFileThread cf = new copyFileThread(new File("Demo002.java"), new File("Demo002.txt"));
cf.start();
}
}
class copyFileThread extends Thread {
private File srcFile;
private File desFile;
public copyFileThread() {
super();
}
public copyFileThread(File srcFile, File desFile) {
super();
this.srcFile = srcFile;
this.desFile = desFile;
}
@Override
public void run() {
copy();
}
private void copy() {
try (BufferedReader br = new BufferedReader(new FileReader(srcFile));
BufferedWriter bw = new BufferedWriter(new FileWriter(desFile))) {
char[] chs = new char[1024];
int len = 0;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
bw.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public void closeResources(AutoCloseable... resources) {
for (AutoCloseable resource : resources) {
if (resource != null) {
try {
resource.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
方法二:实现Runnable接口方式开启线程
public class Demo02 {
public static void main(String[] args) {
CalculateThread ct = new CalculateThread();
Thread t = new Thread(ct);
t.start();
}
}
class CalculateThread implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(i);
}
}
}
方式三:实现Callable接口开启线程(需借助中间类: FutureTask)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo03 {
public static void main(String[] args) {
FutureTask<Integer> ft = new FutureTask<Integer>(new GetSumThread());
Thread t = new Thread(ft);
t.start();
try {
System.out.println(ft.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class GetSumThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
方式四:利用匿名内部类开启线程
//匿名内部类输出a~z
public class Demo05 {
//方式一:
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
for (char i = 'a'; i <= 'z'; i++) {
System.out.println(i);
}
}
}.start();
//方式二:
new Thread(new Runnable() {
@Override
public void run() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
System.out.println(sum);
}
}).start();
}
}
方法五:利用Lambda表达式开启线程
//Lambda表达式
/*
* Lambda表达式的组成语法
* 主要由三部分组成:
* 1.形参列表: 形式参数允许省略参数类型
* 2.箭头 ->
* 3.方法体: 由大括号包裹,当方法体中只有一条语句,{}可以省略
* 当一个方法有返回值的时候,如果只是返回一条语句,
* 那么return和{}都可以省略,这个表达式结果自动作为返回值的结果返回
*/
public class Demo06 {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 1; i <= 100; i++) {
System.out.println(i);
}
}).start();
new Thread(() -> {
for (char i = 'a'; i <= 'z'; i++) {
System.out.println(i);
}
}).start();
}
}
方法六:线程池开启线程(Executors工厂类来产生线程池)
线程池:池本质就是容器
池: 初始容量,最大容量, 最小容量, 最大空闲数,最小空闲数,增量,等待时间
当程序中要创建大量生存期很短的线程时,应该考虑使用线程池,程序启动一个新线程占用资源大,使用线程池可以很好的提高性能,线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
Executors工厂类来产生线程池。
public static ExecutorService newCachedThreadPool() //系统指定的线程个数
public static ExecutorService newFixedThreadPool(int nThreads) //自定义线程个数
public static ExecutorService newSingleThreadExecutor() //单线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
//线程池开启线程
/*Executors工厂类来产生线程池。
public static ExecutorService newCachedThreadPool() //系统指定的线程个数
public static ExecutorService newFixedThreadPool(int nThreads) //自定义线程个数
public static ExecutorService newSingleThreadExecutor() //单线程
*/
public class Demo07 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.submit(new MyRunnable());
Future<Integer> f = threadPool.submit(new MyCallable());
System.out.println("计算结果:" + f.get());
threadPool.submit(new Runnable() {
@Override
public void run() {
for (char i = 'a'; i <= 'z'; i++) {
System.out.println("runnable匿名内部类:" + i);
}
}
});
threadPool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
线程组(ThreadGroup)
线程组是把各个线程分组,然后可以通过组名来操作组中的线程成员
public class Demo08 {
public static void main(String[] args) throws InterruptedException {
ThreadGroup group = new ThreadGroup("三国演义组");
MyRunnableImpl mr = new MyRunnableImpl();
Thread t1 = new Thread(group, mr, "刘备");
Thread t2 = new Thread(group, mr, "关羽");
System.out.println(group.getName()); //三国演义组
group.setDaemon(true);
t1.start();
t2.start();
System.out.println(group.getMaxPriority()); //10
System.out.println(group.activeCount()); //2
}
}
class MyRunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
线程方法
一:设置和获取线程名称
通过构造方法
Thread(String name) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
通过线程的成员方法
public final String getName()
public final void setName(String name)
通过静态方法
public static Thread currentThread()
可以获取任意方法所在的线程名称
可以获取主线程的线程名称:Thread.currentThread().getName();
//设置和获取线程名称
public class Demo04 {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread("小明");
Thread t = new Thread(new MyRunnable());
mt1.setName("老王");
t.setName("myRunnable");
//获取当前线程的名称
Thread currentThread = Thread.currentThread();
currentThread.setName("主线程:开始");
mt1.start();
mt2.start();
t.start();
System.out.println(currentThread.getName());
}
}
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
二:设置和获取线程的优先级
public final int getPriority()
public final void setPriority(int newPriority)
提高某个任务的执行时长,以及任务被执行的概率
/*设置和获取线程的优先级
public final int getPriority()
public final void setPriority(int newPriority)
提高某个任务的执行时长,以及任务被执行的概率
*/
public class Demo05 {
public static void main(String[] args) {
Priority p1 = new Priority();
Priority p2 = new Priority();
Priority p3 = new Priority();
p1.setName("小王");
p2.setName("小李");
p3.setName("小航");
p1.setPriority(Thread.MAX_PRIORITY);//10
p2.setPriority(Thread.MIN_PRIORITY);//1
p3.setPriority(Thread.NORM_PRIORITY);//5
System.out.println(p1.getPriority());
System.out.println(p2.getPriority());
System.out.println(p3.getPriority());
p1.start();
p2.start();
p3.start();
}
}
class Priority extends Thread {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
三:线程休眠
-
线程休眠
-
public static void sleep(long millis)
-
import java.text.SimpleDateFormat; import java.util.Date; /* * 线程休眠 * public static void sleep(long millis) * * 小练习: 利用线程休眠模拟时钟 * */ public class Demo06 { public static void main(String[] args) { System.out.println("时钟开启:"); SleepThread st = new SleepThread(); st.start(); } } class SleepThread extends Thread { @Override public void run() { while (true) { System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); try { sleep(1000L); } catch (Exception e) { e.printStackTrace(); } } } }
四:中断线程
public final void stop() 结束子线程的生命周期
public void interrupt() 给子线程抛出一个异常,至于要不要结束子线程取决于子线程自己
面试题:stop和interrupt的区别
stop会直接结束子线程的生命周期,没有任何提醒;而interrupt会抛出异常,子线程处理异常后能够继续执行至结束,但如果子线程catch代码块中有System.exit(0);则不会执行之后的代码。
import java.text.SimpleDateFormat;
import java.util.Date;
/*
中断线程
* public final void stop() 结束子线程的生命周期
* public void interrupt() 给子线程抛出一个异常,至于要不要结束子线程取决于子线程自己
*/
public class Demo07 {
public static void main(String[] args) {
InterruptThread it = new InterruptThread();
it.setName("小明");
it.start();
try {
Thread.sleep(3000L);
//it.stop();
it.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class InterruptThread extends Thread {
@Override
public void run() {
System.out.println(getName() + "入睡时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
try {
sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
//System.exit(0);
}
System.out.println(getName() + "觉醒时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
it.stop运行结果如下:
it.interrupt运行结果如下:
五:后台线程又被称为: 服务线程,守护线程【守护非后台线程】
一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,分别是用户线程和后台线程。
所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。而且当用户线程结束的时候,同时会杀死后台进程。
后台线程:
public final void setDaemon(boolean on)
//后台线程
//public final void setDaemon(boolean on)
public class Demo08 {
public static void main(String[] args) {
DeamonThread dt1 = new DeamonThread();
DeamonThread dt2 = new DeamonThread();
DeamonThread dt3 = new DeamonThread();
dt1.setName("刘备");
dt2.setName("关羽");
dt3.setName("张飞");
dt1.setDaemon(true);//表示刘备是后台线程(服务线程)
dt2.setDaemon(true);//表示关羽是后台线程(服务线程)
dt1.start();
dt2.start();
dt3.start();
//主线程执行三次,三个子线程执行100次
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class DeamonThread extends Thread {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
//运行结果:服务线程刘备和关羽在主线程结束的时候也会被终止,执行次数不一定;而张飞属于非后台线程,则不受影响,会继续执行完100次。
六:线程加入
线程加入:join后是否会使得前面的线程先走完,之后再走join后面的线程???
public final void join()
//线程加入
public class Demo09 {
public static void main(String[] args) {
JoinThread jt1 = new JoinThread();
JoinThread jt2 = new JoinThread();
JoinThread jt3 = new JoinThread();
jt1.setName("小红");
jt2.setName("小蓝");
jt3.setName("小白");
jt1.start();
jt2.start();
try {
jt2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
jt3.start();
}
}
class JoinThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
//运行结果:小蓝先走完,之后小红和小白互抢
七:线程礼让
线程礼让:该方法是唯一一个能够让线程从运行状态转为就绪状态的一个线程控制方法,表示放弃执行权,重新加入抢夺执行权队伍中去。
public static void yield()
//线程礼让
public class Demo10 {
public static void main(String[] args) {
YieldThread yt1 = new YieldThread();
YieldThread yt2 = new YieldThread();
YieldThread yt3 = new YieldThread();
yt1.setName("刘备");
yt2.setName("关羽");
yt3.setName("张飞");
yt1.start();
yt2.start();
yt3.start();
}
}
class YieldThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
yield();
}
}
}
线程同步
分析线程安全问题:
1.存在多线程程序
2.存在共享数据
3.存在多个线程对共享数据做了操作 【写操作】
解决办法:
1.同步代码块
2.同步方法
3.Lock锁
方式一:同步代码块
格式:synchronized(对象){需要同步的代码;}
解决办法方式二:同步方法
格式:public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
}
如果锁对象是this,就可以考虑使用同步方法。
如果是静态方法? 锁对象就是字节码文件对象做锁 ThreadDemo.class
方式三: JDK中提供了Lock锁
清楚地告诉使用者锁对象是谁,同时告诉使用者在哪里上锁,在哪里释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//需求:深圳罗湖火车站目前正在出售车票,共有100张票,而它有3个售票窗口售票,
public class Demo03 {
public static void main(String[] args) {
/*SellTicketThread s1 = new SellTicketThread();
SellTicketThread s2 = new SellTicketThread();
SellTicketThread s3 = new SellTicketThread();
s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");
s1.start();
s2.start();
s3.start();*/
SellTicket st = new SellTicket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SellTicketThread extends Thread {
private static final String LOCK = "LOCK";
private static int tickets = 100;
//方法一:同步代码块
@Override
public void run() {
while (true) {
synchronized (LOCK) {
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName() + "正在出售" + tickets-- + "张票");
}
}
sellTicket();
}
}
//方式二:静态同步方法 (该方法的锁对象是字节码文件对象)
public static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + tickets-- + "张票");
}
}
}
class SellTicket implements Runnable {
private int tickets = 100;
Lock lock = new ReentrantLock();
//方式三:利用LOCK锁
@Override
public void run() {
while (true) {
/*lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + tickets-- + "张票");
}
lock.unlock();*/
sellTicket();
}
}
//方式二:非静态同步方法 (该方法的锁对象是this对象)
public synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + tickets-- + "张票");
}
}
}
死锁
死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
//死锁
public class Demo04 {
public static void main(String[] args) {
DieLockThread dlt1 = new DieLockThread(false);
DieLockThread dlt2 = new DieLockThread(true);
dlt1.start();
dlt2.start();
}
}
enum MyLocks {
LOCKA, LOCKB
}
class DieLockThread extends Thread {
private Boolean flage;
public DieLockThread() {
super();
}
public DieLockThread(Boolean flage) {
super();
this.flage = flage;
}
@Override
public void run() {
if (flage) {
synchronized (MyLocks.LOCKA) {
System.out.println("if语句中的A锁");
synchronized (MyLocks.LOCKB) {
System.out.println("if语句中的B锁");
}
}
} else {
synchronized (MyLocks.LOCKB) {
System.out.println("else语句中的B锁");
synchronized (MyLocks.LOCKA) {
System.out.println("else语句中的A锁");
}
}
}
}
public Boolean getFlage() {
return flage;
}
public void setFlage(Boolean flage) {
this.flage = flage;
}
}
执行结果如下:
情况一:
情况三:
- else语句中的 LockB锁
- else语句中的LockA锁
- if语句中的 LockA锁
- if语句中的LockB锁
情况四:
- if语句中的 LockA锁
- if语句中的LockB锁
- else语句中的 LockB锁
- else语句中的LockA锁
volatile关键字——JMM内存模型
volatile关键字的作用是:它强制线程从主内存中取 volatile修饰的变量,使变量在多个线程间可见(可见性)。
如下:当主线程修改volatile修饰的isRunning变量的时候,就能使得修改后的变量立刻对所有线程有效(可见)。
class RunThread extends Thread {
private volatile boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
等待唤醒机制【线程执行期间通信】(实现生产者消费者模式)
package com.sxt.thread2;
public class Demo03 {
public static void main(String[] args) {
Toys t = new Toys();
Produces pro = new Produces(t);
Consumers con = new Consumers(t);
pro.start();
con.start();
}
}
class Toys {
private String toyName;
private int toyNum;
private boolean flage;
public Toys() {
super();
}
public Toys(String toyName, int toyNum, boolean flage) {
super();
this.toyName = toyName;
this.toyNum = toyNum;
this.flage = flage;
}
public synchronized void produce(String toyName, int toyNum) {
//先判断是否有玩具,有就等待消费者消费
if (this.isFlage()) {
try {
this.wait(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果没有玩具,就生产
this.setToyName(toyName);
this.setToyNum(toyNum);
//修改标志并通知消费者消费
this.setFlage(true);
this.notify();
}
public synchronized void consumer() {
//先判断是否有玩具,有就消费
if (this.isFlage()) {
String toyName = this.getToyName();
int toyNum = this.getToyNum();
System.out.println("正在消费:" + toyName + ",玩具的数量:" + toyNum);
this.setToyNum(--toyNum);
// 如果消费完毕后,通知生产者生产
if (toyNum <= 0) {
this.setFlage(false);
// 通知生产者生产
// 该方法表示会随机唤醒在同一个把锁上任意使用了wait方法的线程
this.notify();
}
} else {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public String getToyName() {
return toyName;
}
public void setToyName(String toyName) {
this.toyName = toyName;
}
public int getToyNum() {
return toyNum;
}
public void setToyNum(int toyNum) {
this.toyNum = toyNum;
}
public boolean isFlage() {
return flage;
}
public void setFlage(boolean flage) {
this.flage = flage;
}
}
//生产者模式
class Produces extends Thread {
private Toys t;
private int i;
public Produces() {
super();
}
public Produces(Toys t) {
super();
this.t = t;
}
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
t.produce("叮当猫", 10);
} else {
t.produce("玩具狗", 10);
}
i++;
}
}
public Toys getT() {
return t;
}
public void setT(Toys t) {
this.t = t;
}
}
//消费者模式
class Consumers extends Thread {
private Toys t;
public Consumers() {
super();
}
public Consumers(Toys t) {
super();
this.t = t;
}
@Override
public void run() {
while (true) {
t.consumer();
}
}
public Toys getT() {
return t;
}
public void setT(Toys t) {
this.t = t;
}
}
定时器开启定时任务(Timer)
线程的第七种开启方式: 定时器开启线程
依赖于两个类:
定时器类 Timer
1.定时执行某个线程任务
void schedule(TimerTask task, Date time)
2.延时执行某个线程任务
void schedule(TimerTask task, long delay)
3.重复执行某个线程任务
void schedule(TimerTask task, long delay, long period)
定时任务类 TimerTask
import java.util.Timer;
import java.util.TimerTask;
public class Demo01 {
public static void main(String[] args) {
Timer t = new Timer("定时输出语句", false);
//延时5秒后输出HelloWorld!!,随后每间隔1秒重复输出一次
t.schedule(new Mytask(), 5000, 1000);
//延时2秒后输出“你好”,但只输出一次。
//匿名内部类
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("你好");
}
}, 2000);
}
}
class Mytask extends TimerTask {
@Override
public void run() {
System.out.println("HelloWorld!!");
}
}
多线程捕获未捕获异常
import java.util.concurrent.ThreadFactory;
/*
* 每个线程都有自己独立的栈空间
* 每个线程都已自己独立的执行过程
* 线程与线程之间也可以进行通信
* 每个线程也可能出现异常
* 捕获未捕获的异常
*
* 希望在线程开启的地方去捕获到子线程的没捕获的异常
* static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
* 设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
* void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
* 设置该线程由于未捕获到异常而突然终止时调用的处理程序。
*
* 面试题:
* 线程未捕获异常处理器和try-catch处理方式区别
* 1.try-catch能够让程序继续执行
* 2.只能够捕获子线程中未处理的异常信息,自己显示,子线程依然会中断
*/
public class ThreadDemo01 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.setName("子线程");
t.setUncaughtExceptionHandler(new MyThreadUncaughtExceptionHandler());
t.start();
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
if (i == 50) {
// throw new RuntimeException("子线程出现未知异常");
System.out.println(10/0);
}
}
}
}
class MyThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
//当子线程遇到异常时,会通过自定义捕获异常的方法去处理,但子线程由于没有tyr-catch,依旧会中断执行。
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + "出现了异常:" + e);
}
}
本地线程 ThreadLocal
public class Demo05 {
public static void main(String[] args) {
User user = new User("admin", "123456");
ThreadLocal<User> tl = new ThreadLocal<>();
tl.set(user);
// 匿名内部类方式开启线程
new Thread(new Runnable() {
@Override
public void run() {
User user = new User("Jim", "789456");
tl.set(user);
System.out.println(tl.get());//User [userName=admin, password=123456]
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User u = tl.get();
System.out.println(u);//User [userName=Jim, password=789456]
}
}
class User {
private String userName;
private String password;
public User() {
super();
}
public User(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [userName=" + userName + ", password=" + password + "]";
}
}