目录
⑤静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象
1、线程API
(1)sleep阻塞
线程提供了一个静态方法:
static void sleep(long ms)
使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行.
package thread;
import java.util.Scanner;
public class SleepDemo {
public static void main(String[] args) {
System.out.println("start");
Scanner scanner = new Scanner(System.in);
for (int i = scanner.nextInt();i>0;i--){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("时间到");
System.out.println("end");
}
}
(2)sleep方法处理异常
- sleep方法处理异常:
- InterruptedException.
- 当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞。
package thread;
/** interrupt()方法使用
* 当一个线程调用sleep方法出于睡眠阻塞的过程中,
* 如果该线程的interrupt()方法被调用,
* 那么该线程会立即中断睡眠阻塞,
* 并在sleep方法这里抛出中断异常
*/
public class SleepInterruptDemo2 {
public static void main(String[] args) {
Thread lin = new Thread(){
public void run(){
System.out.println("林:刚美容完,休息一下吧~");
try {
/*
lin线程调用sleep方法后会进入睡眠阻塞,
期间如果lin线程的interrupt方法被调用的话,
就会在sleep方法这里立即抛出中断异常,
并结束sleep阻塞,并跳到catch块
*/
Thread.sleep(30000000);
} catch (InterruptedException e) {
System.out.println("此处因其他因数导致sleep中断,就会报异常");
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
}
System.out.println("正常情况下,会在sleep结束后执行这里");
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) {
//这里正常sleep,不会出现异常,所以此处可以不写
//不调用interrupt,就不会出现异常,
// 这里是java中的语法,人家不知道你是否会不会调用interrupt,所以这里强行捕获异常
}
}
System.out.println("咣当!");
System.out.println("黄:大哥!搞定");
//让huang线程中断lin线程的睡眠阻塞
lin.interrupt();
}
};
lin.start();
huang.start();
}
}
(3)守护线程
- 守护线程也称为:后台线程
守护线程是,
通过普通线程调用 setDaemon ( boolean on ) 方法设置而来的,
因此创建上与普通线程无异。例: jack .setDaemon ( true ) ;
线程分为 用户线程 和 守护线程 * 用户线程:正常创建的线程都是用户线程。 * 守护线程:只有调用该线程的setDaemon(true)时,才会将该线程改为守护线程守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
进程结束:
当一个java进程中的 所有普通线程 都结束时,该进程就会结束,
此时会杀掉所有正在运行的 守护线程. * 守护线程 与 用户线程 最主要的区别在于进程结束: * 进程的结束: * 当一个java进程中的 所有用户线程 都结束时,该进程就会结束, * 此时进程会强制杀死所有还在运行的 守护线程。PS:
通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行。
比如GC就是在守护线程上运行的.
package thread;
/**
* 守护线程
* 线程分为 用户线程 和 守护线程
* 用户线程:正常创建的线程都是用户线程。
* 守护线程:只有调用该线程的setDaemon(true)时,才会将该线程改为守护线程
*
* 守护线程 与 用户线程 最主要的区别在于进程结束:
* 进程的结束:
* 当一个java进程中的 所有用户线程 都结束时,该进程就会结束,
* 此时进程会强制杀死所有还在运行的 守护线程。
*
* GC就是运行在守护线程上的。(垃圾收集 Garbage Collection)
*/
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:啊啊啊啊啊~~~~~");
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();
/*
main主线程也是用户线程,如果主线程不结束,进程就不会结束,
此时就算rose线程结束了,进程也不会杀死还在运行的jack守护线程
*/
//知识点:main方法是最开始就结束的一个用户线程
System.out.println("main方法执行完毕,主线程结束");
//将main方法用户线程卡死,让其不结束,守护线程因为规则,也不会结束
while (true);
}
}
2、多线程并发安全问题
多线程并发安全问题:
当多个线程并发操作同一临界资源,由于线程切换时机不确定,
导致操作临界资源的顺序出现混乱,严重时可能导致系统瘫痪。- 解决办法:
- 将多个线程并发执行改为同步执行,可有效的解决并发安全问题
同步与异步的概念:
同步和异步都是说的多线程的执行方式。
异步执行:多线程各自执行各自的就是 异步执行
同步执行:而多线程执行出现了先后顺序进行就是 同步执行
同步执行: 多个线程有先后顺序的执行
临界资源: 操作该资源的全过程同时只能被单个线程完成。
java 在语法层面上加了一个关键字 synchronized
被该关键字修饰的方法称为 同步方法
即: 多个线程不能同时在方法内部执行
线程提供的这个静态方法作用是:
让执行该方法的线程主动放弃本次时间片。
static void yield ( )
例:Thread . yield ( ) ;
这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。
package thread;
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换实际不确定,
* 导致执行顺序出现混乱从而导致程序出现不良后果。
* 临界资源:
* 操作该资源的完成过程同一时刻只能被单一线程进行。
*
*
* * java在语法层面上加了一个关键字synchronized.被该关键字修饰的方法称为同步方法
* * 即: 多个线程不能同时在方法内部执行
* * 将多个线程并发执行改为同步执行可有效的解决并发安全问题
* * 同步执行: 多个线程有先后顺序的执行
*
*/
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table(); //创建一个新桌子
Thread t1 = new Thread(){
//如果run方法内部对外抛出了一个非检查异常,就意味着这个线程就结束了
public void run() {
while (true){
//如果桌子没有豆子了,getBean方法会抛出一个非检查异常
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":" + bean);
//getName()获取当前线程的名字
//在外使用t1.getName(); 在内可以省略t1.
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.strat();
}
}
class Table{ //桌子类
private int beans = 20; //桌子上有20个豆子
//此线程添加 synchronized 意义:将此线程上锁,此线程没有走完,其他线程不允许获取该线程中的数据
public synchronized int getBean(){ //从桌子上获取一个豆子并返回该豆子的编号
if (beans==0){ //首先判断桌子上是否还有剩余的豆子
throw new RuntimeException("桌子上没有豆子了"); //模拟报错(桌子上没有豆子了)
}
//该方法是让执行这个方法的线程主动放弃本次剩余时间片
Thread.yield(); //这里放弃时间片目的是模拟执行完上面的if后时间片用尽发生线程切换
return beans--; //从桌子上取出一个豆子并返回
}
}
/**
* java在语法层面上加了一个关键字synchronized.被该关键字修饰的方法称为同步方法
* 即: 多个线程不能同时在方法内部不执行
* 将多个线程并发执行改为同步执行可有效的解决并发安全问题
* 同步执行: 多个线程有先后顺序的执行
*/
2.1、synchronized关键字
① synchronized有两种使用方式
同步方法:在方法上修饰,此时该方法变为一个 同步方法
同步块:可以更准确的锁定需要排队的代码片段
有效的缩小同步范围 可以在保证并发安全的前提下 提高并发效率
②同步方法
- 当一个方法使用synchronized修饰后,这个方法称为"同步方法"
- 即:
多个线程不能同时在方法内部执行,
只能有先后顺序的一个一个进行,
将并发操作同一临界资源的过程改为同步执行,
就可以有效的解决并发安全问题.
package thread;
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
* 混乱,严重时可能导致系统瘫痪。
* 临界资源:同时只能被单一线程访问操作过程的资源。
*/
public class SyncDemo {
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();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
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();
return beans--;
}
}
③同步块
有效的缩小同步范围,可以在保证并发安全的前提下,尽可能的提高并发效率。
同步块可以更准确的控制,需要多个线程排队执行的 代码片段。
语法:
synchronized ( 同步监视器对象 ) {
需要多线程同步执行的代码片段
}
同步监视器对象即上锁的对象,
要想保证同步块中的代码被多个线程同步运行,
则要求多个线程看到的同步监视器对象是同一个.
在线程添加 synchronized 意义: 将此线程上锁,此线程没有走完,其他线程不允许获取该线程中的数据 在成员方法上直接使用synchronized时,同步监视器对象不可选,只能是this
使用同步块可以更灵活更精准的控制需要多个线程同步执行的代码片段。 同步块使用时需要在"()"中指定一个称为"同步监视器对象",它在语法 层面上而言必须是一个引用类型元素,否则编译不通过。 当然,如果希望该同步块发挥作用,实际上该同步监视器对象还必须要求 多个需要同步执行(排队执行)的多个线程看到的该对象必须是【同一个】 实际开发中适合作为同步监视器对象的通常就是临界资源:抢谁就锁谁
- PS:
synchronized (new Object()) {//凡是new一定是无效锁,无效的锁对象
使用字面量在这里作为同步监视器对象虽然有效,但是并非合适的锁对象。 所谓合适的锁对象应当是当多个线程出现"抢"的时候可以使他们分开执行, 而不存在"抢"的时候可以同时执行。 而字面量是全局唯一的对象,无论何时多个线程看到的都是同一个对象,因此 任何情况下执行下面的同步块都需要分开执行。 例如:两个线程一个调用shop1对象的buy,一个调用shop2对象的buy方法 他们没有抢的情况,但是执行到下面代码时依然排队就会降低效率,这就不是 合适的锁对象。
package thread;
/**
* 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率
* 同步块可以更准确的锁定需要同步代码片段
* 语法:
* synchronized(同步监视器对象){
* 需要同步执行的代码片段
* }
*/
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop();
Shop shop1 = new Shop();//创建shop1 店
Shop shop2 = new Shop();//创建shop2 店
Thread t1 = new Thread("此处可添加线程名字"){
public void run() {
// shop.buy();
shop1.buy();//这相当于进了shop1店
}
};
Thread t2 = new Thread("我叫赵庭恺"){
public void run() {
// shop.buy();
shop2.buy();//这相当于进了shop2店
}
};
t1.start();
t2.start();
}
}
class Shop{
//此线程添加 synchronized 意义:将此线程上锁,此线程没有走完,其他线程不允许获取该线程中的数据
//在成员方法上直接使用synchronized时,同步监视器对象不可选,只能是this
// public synchronized void buy(){//不能选,只能是this
public void buy(){
try {
Thread t = Thread.currentThread();//获取运行buy方法的线程
System.out.println(t.getName()+"正在挑选衣服。。。");
Thread.sleep(5000);
//此环节不允许多个线程一起执行
/*
使用同步块可以更灵活更精准的控制需要多个线程同步执行的代码片段。
同步块使用时需要在"()"中指定一个称为"同步监视器对象",它在语法
层面上而言必须是一个引用类型元素,否则编译不通过。
当然,如果希望该同步块发挥作用,实际上该同步监视器对象还必须要求
多个需要同步执行(排队执行)的多个线程看到的该对象必须是【同一个】
实际开发中适合作为同步监视器对象的通常就是临界资源:抢谁就锁谁
*/
// synchronized (new Object()) {//凡是new一定是无效锁,无效的锁对象
/*
使用字面量在这里作为同步监视器对象虽然有效,但是并非合适的锁对象。
所谓合适的锁对象应当是当多个线程出现"抢"的时候可以使他们分开执行,
而不存在"抢"的时候可以同时执行。
而字面量是全局唯一的对象,无论何时多个线程看到的都是同一个对象,因此
任何情况下执行下面的同步块都需要分开执行。
例如:两个线程一个调用shop1对象的buy,一个调用shop2对象的buy方法
他们没有抢的情况,但是执行到下面代码时依然排队就会降低效率,这就不是
合适的锁对象。
*/
// synchronized (“abc”) { //字符串字面量在常量池是全局唯一参数,相同的字面量是同一个,所以这个是可以的,但他不是一个合适的锁对象
//this 就是一个合适的锁对象,在该上锁的时候上锁,不该上锁的时候不上锁
synchronized (this) { //this 可以分情况看,比如现在分别进了shop1和shop2俩家店,他们互不影响,就可以同时试衣服,
// 换做字符串,因为其全局唯一的属性,还会让俩人分开试衣,这不符合实际情况,反而大大降低了效率
System.out.println(t.getName() + "正在试穿衣服。。。");
Thread.sleep(5000);
}
System.out.println("结账走人");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
④在静态方法上使用synchronized
- 当在静态方法上使用 synchronized 后,该方法是一个同步方法。
- 由于静态方法所属类,所以一定具有同步效果.
- 静态方法使用的同步监视器对象为,当前类的类对象 ( Class的实例 )
类对象:
Class实例,在JVM内部每个被加载的类都有且只有一个Class实例与之绑定- 注:类对象会在后期反射知识点介绍
package thread;
/**
* 当一个静态方法上使用synchronized修饰后,该方法一定是同步的。
*/
public class SyncDemo3 {
public static void main(String[] args) {
Boo b1 = new Boo();
Boo b2 = new Boo();
Thread t1 = new Thread(){
@Override
public void run() {
b1.dosome();
//静态方法通常使用类名点,这里用实例点,是为了测试,
// 在实际当中,不应当这么做,会引起他人读代码的误会
}
};
Thread t2 = new Thread(){
public void run(){
b2.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
/*
使用上面main方法中的方式测试,两个线程一个调用b1实例的dosome方法,另一个线程
调用b2实例的dosome方法:
如果dosome方法是一个成员方法,那么synchronized指定的同步监视器对象为this,
由于两个线程调用的并非同一个对象的dosome方法,因此看到的this也不是同一个对象
所以他们是可以同时执行的。
如果dosome方法是一个静态方法,那么synchronized指定的同步监视器为当前类的
类对象(Class实例),由于每个类都有且只有一个类对象与之对应,因此多个线程看到的
同一个类一定也是同一个类对象,因此执行dosome方法时是分开的。
类对象:Class实例,在JVM内部每个被加载的类都有且只有一个Class实例与之绑定,
后面学习反射知识时会详细介绍这个对象。
*/
//在静态方法上直接使用synchronized时,锁对象不可选,只能是当前类的类对象
// public synchronized static void dosome(){ //同步执行(排队执行),静态方法锁一定相同,一定会分开执行
// public synchronized void dosome(){ //一起执行,没加静态方法修饰词static,不加静态方法static,那就得看情况而定
// public static void dosome(){ //一起执行,没加同步方法修饰词synchronized
/*
public static void dosome(){
synchronized (Boo.class) {..}
...
}
如果不想在外加synchronized,可以在里面加synchronized (类名.class) {..}
和public synchronized static void dosome(){...}效果一致,
都是指定的同步监视器对象是当前类的类对象
注意:
但是成员方法,不能用该方法(在里面加synchronized (类名.class) {..})
因为里面的(类名.class)就和字符串一样了,
该起作用的时候起作用,不该起作用的时候还起作用
因为类对象也是全局唯一的,所以成员方法这里不是合适的锁对象,
但是在静态方法上,我们就可以用,因为静态方法本身方法就是所属类的,因此我们用这个就非常合适
*/
public static void dosome(){
//在静态方法中如果使用同步块,同步监视器对象通常也使用当前类的类对象。
//获取类对象的方式:类名.class
synchronized (Boo.class) { //他和public synchronized static void dosome(){...}效果相同,都是指定的同步监视器对象是当前类的类对象
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":正在执行dosome方法。。。");
Thread.sleep(5000);
System.out.println(t.getName() + "执行dosome方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
⑤静态方法中使用同步块时,
指定的锁对象通常也是当前类的类对象
- 静态方法 中使用 同步块 时,指定同步监视器对象 通常还是用 当前类的类对象
- 获取方式为:类名.class
- 例: public static void dosome(){
synchronized (Boo.class) {..}
...
}- 如果不想在方法外加synchronized,
可以在方法里面加synchronized (类名.class) {..}
和
public synchronized static void dosome(){...}
效果一致!- 指定的同步监视器对象 都是 当前类的类对象
- 注意:
- 但是成员方法,不能用该方法(在里面加synchronized (类名.class) {..})
因为里面的(类名.class)就和字符串一样了,
该起作用的时候起作用,不该起作用的时候还起作用- 因为类对象也是全局唯一的,所以成员方法这里不是合适的锁对象,
- 但是在静态方法上,我们就可以用,
因为静态方法本身方法就是所属类的,因此我们用这个就非常合适
class Boo{
public static void dosome(){
/*
静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象
获取方式为:类名.class
*/
synchronized (Boo.class) {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
⑥同步监视器对象的选取:
- 对于 同步的 成员方法 而言,同步监视器对象不可指定,只能是this
- 对于 同步的 静态方法 而言,同步监视器对象也不可指定,只能是类对象
- 对于 同步块 而言,需要自行指定同步监视器对象,选取原则:
- 1、必须是引用类型
- 2、多个需要同步执行该同步块的线程看到的该对象必须是同一个
⑦互斥锁
- 当多个线程执行不同的代码片段,
但是这些代码片段之间不能同时运行时,就要设置为互斥的。
- 使用 synchronized 锁定多个代码片段,
并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的。
package thread;
/**
* 互斥锁
* 当使用多个synchronize锁定多个代码片段,
* 并且指定的都是同一个同步监视器对象时,
* 这些代码片段之间就是互斥的,多个线程不能同时执行它们。
*/
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo(); //实例化一个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 void methodA(){ //成员方法,一起执行
public synchronized void methodA(){ //成员方法中调用synchronize,锁的是this,而t1、t2调用的是同一个对象的A方法,this是相同的,所以是同步执行(排序执行)
try {
Thread t = Thread.currentThread();//获取运行该方法的线程
System.out.println(t.getName()+":正在执行A方法。。。");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// public synchronized void methodB(){ // 在方法上加synchronize
public void methodB(){ // 和 是相同的效果,同样都是指定的同步监视器对象是当前类的类对象Foo
synchronized (this) { // 在方法块上加synchronize
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":正在执行B方法。。。");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行B方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/** 同样都是同步执行(排序执行) */
/*
前提:synchronize指定的同步监视器对象是当前类的类对象
如果A有synchronize:
在Foo旁分出一块内存记录有A再用,上锁
B也有synchronize:
B发现这个内存里有A,已经上锁,那就得排序执行,在后面等着时间片给分配。
但是如果B没有synchronize:
那B线程在运行时,就不会管Foo旁边那个内存里是否有其他线程,从而直接执行,达到一起执行的效果
*/
}