-
interrupt();满足条件用来中断sleep阻塞,sleep阻塞抛出异常
- 就是如果线程阻塞为100秒,到10秒的时候被踹醒了,那肯定有脾气,就会抛出异常
- 注意:抛出中断异常后,并不是线程结束,会继续往下执行,只不过会抛出sleep的异常说明他不是自己醒的,而是被叫醒的,抛出的异常里边可以添加逻辑
/**
* sleep方法要求捕获中断异常:InterruptedException
* 当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用,那么该
* 线程会立即中断sleep阻塞,并抛出上述异常.
*/
public class SleepDemo2 {
public static void main(String[] args) {
Thread lin = new Thread(){
public void run(){
System.out.println("林:刚美完容,睡一会吧...");
try {
Thread.sleep(50000000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
}
System.out.println("林:醒了!");
}
};
Thread huang = new Thread(){
public void run(){
System.out.println("黄:大锤80,小锤40.开始砸墙!");
for(int i=0;i<5;i++){
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣当!");
System.out.println("黄:搞定!");
// 黄干完活了,要中断lin线程的睡眠阻塞
// 此时是把睡眠阻塞中断,而不是中断它的线程
// 抛出异常后,它会继续往下执行
lin.interrupt();
}
};
lin.start();
huang.start();
}
}
林:刚美完容,睡一会吧...
黄:大锤80,小锤40.开始砸墙!
黄:80!
黄:80!
黄:80!
黄:80!
黄:80!
咣当!
黄:搞定!
林:干嘛呢!干嘛呢!干嘛呢!都破了相了!
林:醒了!
-
守护线程:当一个java进程中所有普通线程都结束时,进程就会结束,此时就会杀掉所有正在运行的守护线程
- 设置守护线程,一定要在该线程启动之前调用,否则会抛出异常
- 守护线程在跑的过程中,是当所有的线程都死了,进程就会结束,守护线程就会结束
- 而进程或者的前提就是有线程活着
- 主线程也是普通线程,所以当main方法执行完毕后,主线程就结束了,进程发现没有线程了,程序就结束了
- 所以我们所谓的程序结束其实是线程结束了,进程就结束了,程序就停下了(主线程是最先结束的)
/**
* 守护线程
* 守护线程也称为:后台线程
* 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
* 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
* 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护
* 线程.
*
* 通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束
* 时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
*/
public class DaemonThreadDemo {
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) {
}
}
System.out.println("rose:啊啊啊啊啊啊啊啊AAAAAAAaaaaaaa.....");
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) {
}
}
}
};
rose.start();
jack.setDaemon(true);//将jack设置为守护线程
jack.start();
//主线程在这里死循环,只要有普通线程活着,进程就不会结束.jack就不会被杀掉
// while(true);
}
}
rose:let me go!
jack:you jump!i jump!
rose:let me go!
jack:you jump!i jump!
jack:you jump!i jump!
rose:let me go!
jack:you jump!i jump!
rose:let me go!
jack:you jump!i jump!
rose:let me go!
jack:you jump!i jump!
rose:啊啊啊啊啊啊啊啊AAAAAAAaaaaaaa.....
噗通!
-
join方法:阻塞,直到该线程执行完,
- 可以用来协调线程同步运行
- 让一个线程的执行在另一个线程运行过程中等待
- 多线程并发 本来就是异步的,
- 而同步运行(到了某个特定的场景,你先来,你执行完我再执行)
- 比如扫地和拖地,必须先扫地(此时拖地在阻塞中),扫完地然后我再拖地
- 显示和下载:当我们浏览淘宝,往下拉,拉的快的话,它的文字出来了,但是图片可能加载不出来,显示文字和下载就是同步执行的
- download.join();//show开始阻塞,直到download执行完毕!
- 谁阻塞就在谁的线程里调用另一个线程的join方法
- show线程里,调用download.join方法,那么show线程 就 排到了download线程后面,直到download线程执行完,才继续执行show线程
- 当一个方法的局部内部类中引用了方法的一个局部变量,那么这个变量必须是final的,只可以引用不可以赋值
- 要想改变 该变量的值 那么就把局部变量 变成类的属性,就可以了
- 方法里的变量 是 局部变量,方法里的内部类,是局部内部类
- 在run方法抛出异常没有捕获,那么该线程也会结束,不会在执行了
/**
* 线程提供了一个方法:
* void join()
* 该方法允许调用这个方法的线程在该方法所属线程上等待(阻塞),直到该方法所属线程结束后才会解
* 除等待继续后续的工作.所以join方法可以用来协调线程的同步运行.
*
* 同步运行:多个线程执行过程存在先后顺序进行.
* 异步运行:多个线程各干各的.线程本来就是异步运行的.
*
*/
public class JoinDemo {
private static boolean isFinish = false;//标示图片是否下载完毕
public static void main(String[] args) {
/*
当一个方法的局部内部类中引用了这个方法的其他局部变量时,这个变量必须是final的
这是由于JVM内存分配问题导致的,当成语法要求去遵守即可.
因此被局部内部类引用的局部变量是不能被2次赋值的.
*/
// boolean isFinish = false;
Thread download = new Thread(){
public void run(){
System.out.println("down:开始下载图片...");
for(int i=1;i<=100;i++){
System.out.println("down:"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
System.out.println("down:图片下载完毕!");
isFinish = true;
}
};
Thread show = new Thread(){
public void run(){
try {
System.out.println("show:开始显示文字...");
Thread.sleep(3000);
System.out.println("show:显示文字完毕!");
//将show线程阻塞,等待download线程执行完毕再继续后续操作
System.out.println("show:等待download下载图片...");
download.join();//show开始阻塞,直到download执行完毕!
System.out.println("show:等待完毕!");
System.out.println("show:开始显示图片...");
if(!isFinish){
//当一个线程的run方法抛出一个异常,则线程会结束
throw new RuntimeException("图片加载失败!");
}
System.out.println("show:图片显示完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
download.start();
show.start();
}
}
-
多线程并发安全问题
- Thread.yield();方法,让当前线程主动放弃时间片段,让CPU从新分配时间片,引起线程切换这样的一个过程
- 多线程并发导致安全问题
- 比如去银行取钱,一个线程在柜台取钱,一个线程在ATM取钱,两个同时取钱,其中一个线程执行到保存库前没时间了,另一个线程开始了,这样就会形成抢占资源的问题,就是并发安全问题了
- 本质上解决并发安全问题就是排队干
- 也就是说:操作某一个敏感地方排队干 就可以了
- synchronized修饰方法:同步方法(多个线程只能先后顺序执行)(就是一个锁,阻塞,锁上了)一个人进来了,就锁上了,别人进不来,只有他出去开锁了,另一个人才能进来
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱
* 严重时可能导致系统瘫痪.
* 临界资源:操作该资源的全过程同时只能被单个线程完成.
*
* 相当于现实生活中多个人抢同一个东西导致的混乱.
*/
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{
private int beans = 20;//桌子上有20个豆子
/*
当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时
在方法内部执行.只能有先后顺序的一个一个进行.
将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.
*/
public synchronized int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();//让线程主动放弃CPU时间,模拟执行到这里没有时间发生线程切换
return beans--;
}
- synchronized:
- 修饰普通方法:在方法上修饰,此时该方法变为一个同步方法(整个方法,效率低),synchronized修饰方法默认就是this(就是当前方法的所属对象,而且不能换)
- 同步块:同步块,可以更 准确 的锁定需要排队的代码片段
- 可以给任意需要加锁的 对象加锁
- synchronized(){};
- 锁的是方法所属对象(锁的是对象,地址指向对象,假如:锁的是数组对象,当对象扩容缩容,就会创建新的对象,从而分配一个新的地址,那么锁数组就没用了)
- 多个线程进锁方法 看的是谁先竞争到了CPU时间片
- 任何引用类型都可以作为锁对象
- 注意:同步监视器对象,必须是要多个线程看到的是同一个对象
- 成员方法同步监视器对象默认为this,静态方法同步监视器对象为当前类的类对象
- 修饰静态方法:一定具有同步效果
- 修饰静态块:也指定当前类对象,与修饰静态方法一个效果(类.class)
两个对象自己执行自己的不会出现抢的情况
- 使用synchronized的前提是出现并发安全问题,但是如果如下这种情况,他就相当于进了两个商店了,就不出现抢一个资源的情况了,这种情况是一起执行的,是正确的
- 如果还想要同步执行,可以让方法变成静态方法,如下下下的案例静态锁
public class Demo3 {
public static void main(String[] args) {
Shop shop1 = new Shop();
Shop shop2 = new Shop();
Thread t1 = new Thread(){
@Override
public void run() {
shop1.buy();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
shop2.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
public void buy(){
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+"正在挑选衣服");
Thread.sleep(5000);
synchronized (this){
System.out.println(t.getName()+"正在试衣服");
Thread.sleep(5000);
}
System.out.println(t.getName()+"结账");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Thread-0正在挑选衣服
Thread-1正在挑选衣服
Thread-0正在试衣服
Thread-1正在试衣服
Thread-1结账
Thread-0结账
- 同一个对象出现抢的情况
/**
* 同步块
* 有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.
* 同步块可以更准确的控制需要排队的代码片段.
* 语法:
* 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{
/*
在方法上使用synchronized时,锁对象就是当前方法所属对象this.
*/
// public synchronized void buy(){
public void buy(){
try {
Thread t = Thread.currentThread();//获取运行buy方法的线程
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();
}
}
}
Thread-0:正在挑衣服...
Thread-1:正在挑衣服...
Thread-1:正在试衣服...
Thread-1:结账离开
Thread-0:正在试衣服...
Thread-0:结账离开
- 静态方法上使用synchronized
- 静态方是和类相关的,全局就一份,和new多少次对象无关,即:静态方法没有this
- 该方法是一个同步方法,由于静态方法所属类,所以一定具有同步效果
- 即:new 了两个Foo对象,然后分别用Foo的引用取调用dosome()方法,依然是分开执行的,因为静态方法跟类相关,全局就一份,跟对象无关
- 如果是锁修饰的静态方法则一定是同步的,如果去掉锁,则会并发
- 静态方法上锁的对象,通常全都是类对象,即:锁修饰代码块,锁的也是类对象:类.class,不能再锁this了,静态方法都没有this
public static void main(String[] args) {
Foo f1 = new Foo();
Foo f2 = new Foo();
Thread t1 = new Thread(){
public void run(){
Foo.dosome();
//f1.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Foo.dosome();
//f2.dosome();
}
};
t1.start();
t2.start();
}
}
class Foo{
/*
静态方法上使用synchronized后,同步监视器对象为当前类的类对象(Class的实例)
在JVM中,每个被加载的类都有且只有一个类对象(Class的实例)与之对应.每个Class
的实例都可以描述其表示的类的信息.注:后期反射知识点会介绍Class.
下面dosome方法的同步监视器对象就是Foo的类对象.
*/
// public synchronized static void dosome(){
public static void dosome(){
synchronized (Foo.class) {//静态块也指定当前类的类对象,获取方式:类名.class
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":正在执行dosome方法...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println(t.getName() + ":执行dosome方法完毕!");
}
}
Thread-0:正在执行dosome方法...
Thread-0:执行dosome方法完毕!
Thread-1:正在执行dosome方法...
Thread-1:执行dosome方法完毕!
-
互斥锁与同步锁
- synchronized 锁 同一步代码段 的时候就是同步锁 并且锁对象相同
- synchronized 锁 不同的代码段 的时候就是互斥锁 并且锁对象相同(hashTable的写和读)
- 互斥:有你没我,有我没你,即:两件事不能同时干(咽东西,和喘气就不能同时干)
- 互斥 满足 的 两个 条件
- 方法代码都被synchronized锁定,
- 并且锁对象相同
- 当多个线程调用同一个对象的线程方法
- 互斥锁:当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的
- 使用线程锁,锁定多个代码片段,并且制定的同步监视器是同一个时,这些代码片段之间就是互斥的
- 互斥 满足 的 两个 条件
- 即:当有两个方法,A和B,并且都加上锁(对象是this),定义两个线程C和D ,对象是相同的都是Foo
- 让线程C开始执行A 方法,线程D执行B方法,
- 当线程C执行A方法的时候,A方法要求锁Foo
- 当线程D执行B方法的时候,发现有锁,并且锁对象就是它的,那么就不会去执行了
- 如果A方法加上了synchronized,而B方法没有加synchronized那么就不是互斥了直接就可以去执行B方法了
- 所以多个方法必须都加上synchronized,并且锁对象相同才会互斥
public static void main(String[] args) {
Boo boo = new Boo();
Thread t1 = new Thread(){
public void run(){
boo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
// boo.methodA();
boo.methodB();
}
};
t1.start();
t2.start();
}
}
class Boo{
public synchronized void methodA(){
Thread t = Thread.currentThread();
System.out.println(t+":正在执行A方法...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println(t+":执行A方法完毕!");
}
public void methodB(){
synchronized(this) {
Thread t = Thread.currentThread();
System.out.println(t + ":正在执行B方法...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println(t + ":执行B方法完毕!");
}
}
Thread[Thread-0,5,main]:正在执行A方法...
Thread[Thread-0,5,main]:执行A方法完毕!
Thread[Thread-1,5,main]:正在执行B方法...
Thread[Thread-1,5,main]:执行B方法完毕!
- 死锁
- 一个同步块同时拥有“两个以上的对象的锁”的时候,就可能会发生死锁问题
- 线程持有一个锁并要求等待另一个线程释放它的锁,而如果两个线程同时等待对方释放锁时就会形成死锁
//1、北方人拿起筷子锁上,开始吃饭
//2、南方人拿起勺子锁上,开始喝汤
//3、北方人吃完饭,准备喝汤,勺子锁着呢,等待
//4、南方人喝完汤,准备吃饭,筷子锁着呢,等待
public class Demo8 {
public static Object spoon = new Object();//勺
public static Object chopsticks = new Object();//筷子
public static void main(String[] args) {
//北方人
Thread NP = new Thread(){
public void run(){
try{
//北方人吃饭习惯,先吃饭后喝汤。
System.out.println("北方人去拿筷子");
synchronized (chopsticks){//指定锁对象为"筷子"
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);//吃饭过程...
System.out.println("北方人吃完了饭,去拿勺");
synchronized (spoon){//指定锁对象为"勺"
System.out.println("北方人拿起了勺,开始喝汤...");
Thread.sleep(5000);//喝汤过程...
}
System.out.println("北方人将勺放回去");
}
System.out.println("北方人将筷子放回去");
}catch(Exception e){
}
}
};
Thread SP = new Thread(){
public void run(){
try{
//南方人吃饭习惯,先喝汤后吃饭。
System.out.println("南方人去拿勺");
synchronized (spoon){//指定锁对象为"勺"
System.out.println("南方人拿起了勺,开始喝汤...");
Thread.sleep(5000);//喝汤过程...
System.out.println("南方人喝完了汤,去拿筷子");
synchronized (chopsticks){//指定锁对象为"筷子"
System.out.println("南方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);//吃饭过程...
}
System.out.println("南方人将筷子放回去");
}
System.out.println("南方人将勺放回去");
}catch(Exception e){
}
}
};
NP.start();
SP.start();
}
}
北方人去拿筷子
南方人去拿勺
北方人拿起了筷子,开始吃饭...
南方人拿起了勺,开始喝汤...
南方人喝完了汤,去拿筷子
北方人吃完了饭,去拿勺
- 更改为
Thread NP = new Thread(){
public void run(){
try{
//北方人吃饭习惯,先吃饭后喝汤。
System.out.println("北方人去拿筷子");
synchronized (chopsticks) {//指定锁对象为"筷子"
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);//吃饭过程...
}
System.out.println("北方人吃完了饭,将筷子放回去,饭后再拿勺");
synchronized (spoon){//指定锁对象为"勺"
System.out.println("北方人拿起了勺,开始喝汤...");
Thread.sleep(5000);//喝汤过程...
}
System.out.println("北方人将勺放回去");
}catch(Exception e){
}
}
};
Thread SP = new Thread(){
public void run(){
try{
//南方人吃饭习惯,先喝汤后吃饭。
System.out.println("南方人去拿勺");
synchronized (spoon) {//指定锁对象为"勺"
System.out.println("南方人拿起了勺,开始喝汤...");
Thread.sleep(5000);//喝汤过程...
}
System.out.println("南方人喝完了汤,将勺放回去,再去拿筷子");
synchronized (chopsticks){//指定锁对象为"筷子"
System.out.println("南方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);//吃饭过程...
}
System.out.println("南方人将筷子放回去");
}catch(Exception e){
}
}
};
NP.start();
SP.start();
北方人去拿筷子
北方人拿起了筷子,开始吃饭...
南方人去拿勺
南方人拿起了勺,开始喝汤...
北方人吃完了饭,将筷子放回去,饭后再拿勺
北方人拿起了勺,开始喝汤...
南方人喝完了汤,将勺放回去,再去拿筷子
南方人拿起了筷子,开始吃饭...
北方人将勺放回去
南方人将筷子放回去