(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
Java SE 101 synchronized关键字深入详解
文章目录
1.停止一个线程的方式
(1)不能使用Thread类的stop方法来终止线程的执行。
(2)一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
2.不能依靠线程的优先级来决定线程的执行顺序
3.多线程同步
3.1为什么要引入同步机制?
(1)在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
(2)解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。
4.多个人(线程)在同一银行账户上取钱的问题
4.1 不同的账户从各自的账户上取钱,即多个线程操作不同对象的成员变量。
4.1.1定义银行账户信息类
package com.javareview.thread.synchronizedkeyword;
/**
* 定义银行账户信息类
*/
public class Bank {
//多个线程需要操作的成员变量
private int money = 1000;
/**
* 返回实际取到钱的数目
* (1)number表示取的钱
* (2)实际取多少钱,取多少钱我就是多少钱,为什么这里还要返回一个int呢?这个值不是和传进来的值一样吗?
* (3)这是不一定的,如果有1000块钱,我给柜台小姐说,给我取2000,那这种情况我们就认为是个错误。这时候就不能返回2000了,
* 返回一个-1,来表示这个错误,因为不知道账户里面有多少钱。
*/
public int getMoney(int number) {
if (number < 0) {
// s1.取钱金额小于零的情况处理
return -1;
} else if (number > money) {
// s2.取钱金额大于银行账户余额的情况处理
return -2;
} else if (money < 0) {
// s3.银行账户余额小于0的情况
return -3;
} else {
try {
// s1.完成取钱之前的准备工作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.out.println("left money: " + money);
return number;
}
}
}
4.1.2定义取钱的线程类
package com.javareview.thread.synchronizedkeyword;
/**
* 线程类
*/
public class MoneyThread extends Thread {
/**
* 银行账户对象引用,
*/
private Bank bank;
/**
* 通过构造方法传递银行账户信息
*/
public MoneyThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
/**
* 从银行账户里面取钱,每次取800
*/
System.out.println(bank.getMoney(800));
}
}
4.1.3开启多个线程执行取钱操作
package com.javareview.thread.synchronizedkeyword;
/**
* 开启多个线程执行取钱操作
*/
public class TetchMoney1 {
public static void main(String[] args) {
//1.情境:不同的账户从各自的账户上取钱,即多个线程操作不同对象的成员变量。
//s1.构建银行账户信息
Bank bank = new Bank();
//s2.线程1,模拟一个人从柜台取钱
Thread t1 = new MoneyThread(bank);
//s3.再构建一个银行账户信息
bank = new Bank();
//s4.线程2,模拟一个人从取款机上取钱
Thread t2 = new MoneyThread(bank);
//s5.开启两个线程,执行取钱的操作
t1.start();
t2.start();
//s6.结果是两个线程start之后,分别取出了800元钱
}
}
4.1.4执行结果
(1)两个线程start之后,分别取出了800元钱
left money: -600
800
left money: -600
800
4.2多个线程取出多个账户共有的钱
4.2.1 定义银行账户信息类
package com.javareview.thread.synchronizedkeyword;
/**
* 定义银行账户信息类
*/
public class Bank {
/**
* 多个线程对多个对象共有成员变量进行操作
*/
private static int money = 1000;
public int getMoney(int number) {
if (number < 0) {
return -1;
} else if (number > money) {
return -2;
} else if (money < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.out.println("left money: " + money);
return number;
}
}
}
4.2.2定义取钱线程类
package com.javareview.thread.synchronizedkeyword;
/**
* 定义取钱线程类
*/
public class MoneyThread extends Thread {
/**
* 银行账户对象引用,
*/
private Bank bank;
public MoneyThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
/**
* 模拟从银行账户里面取钱,每次取800
*/
System.out.println(bank.getMoney(800));
}
}
4.2.3开启多个线程,构建多个对象,执行取钱操作
package com.javareview.thread.synchronizedkeyword;
/**
* 开启多个线程执行取钱操作
*/
public class TetchMoney1 {
public static void main(String[] args) {
Bank bank = new Bank();
Thread t1 = new MoneyThread(bank);
bank = new Bank();
Thread t2 = new MoneyThread(bank);
t1.start();
t2.start();
}
}
(1)注意:两个线程对两个对象的共有成员静态变量进行操作
(2)static 的成员变量是被所有对象所共享的。既然共享了,无论生成多少个对象,它也只会生成一份儿。操纵它的时候,也只会操纵唯一的一份儿。
(3)静态成员变量是属于类级别的,不属于对象,所以无论生成多少个对象,对于具体的对象来说,这个对象也只会生成一分拷贝。所以多个线程,再操作共享的静态成员变量时,互相也不会受到干扰。
(4)这个案例的实质还是多个线程对多个对象进行操作,但是是对多个对象共享的静态成员变量的操作。
4.2.4执行结果
(1)两个线程start之后,分别取出了800元钱
left money: -600
800
left money: -600
800
5.如何处理线程同步的问题?
5.1多个用户从同一个账户取钱(即多个线程操作同一个对象的成员变量)
5.1.1 定义银行账户信息类
package com.javareview.thread.synchronizedkeyword;
/**
* 定义银行账户信息类
*/
public class Bank {
/**
* 多个线程对同一对象的成员变量进行操作
*/
private int money = 1000;
/**
* 使用synchronized关键字解决多线程同步问题,即多个线程同时操作某一对象的成员变量
* @param number
* @return
*/
public synchronized int getMoney(int number) {
if (number < 0) {
return -1;
} else if (number > money) {
return -2;
} else if (money < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.out.println("left money: " + money);
return number;
}
}
}
5.1.2 定义取钱线程类
package com.javareview.thread.synchronizedkeyword;
/**
* 定义取钱线程类
*/
public class MoneyThread extends Thread {
private Bank bank;
public MoneyThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
//s1.线程对同一对象的成员变量进行操作
System.out.println(bank.getMoney(800));
}
}
5.1.3 启动多个线程在同一时刻对同一对象的成员变量做操作
package com.javareview.thread.synchronizedkeyword;
/**
* 启动多个线程在同一时刻对同一对象的成员变量做操作
*/
public class TetchMoney2 {
public static void main(String[] args) {
//s1.构建多个线程需要操作的同一对象
Bank bank = new Bank();
//s2.构建多个线程
Thread t1 = new MoneyThread(bank);
Thread t2 = new MoneyThread(bank);
//s3.启动多个线程在同一时刻对同一对象的成员变量做操作
t1.start();
t2.start();
}
}
5.1.4在方法上添加synchronized关键字解决多线程问题
多个线程需要同时操作同一对象的成员变量时,在该对象所在类的成员方法上添加synchronized关键字,来解决线程安全的问题。
6.synchronized关键字
(1)当synchronized修饰一个方法的时候,该方法叫做同步方法。
(2)Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor)。
(3)当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
6.1定义具有同步方法的类
package com.javareview.thread.synchronizedkeyword.synchronizedexample;
/**
* 定义线程操作类
*/
public class Person {
/**
* 定义同步方法
* (1)每隔500毫秒输出一个数字
*/
public synchronized void excute(){
for(int i = 0 ; i < 50 ; i++){
try {
Thread.sleep((long)(Math.random()*500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("example: " + i);
}
}
}
6.2定义线程类,用于调用对象的同步方法。
package com.javareview.thread.synchronizedkeyword.synchronizedexample;
/**
* 定义线程类,用于调用对象的同步方法。
*/
public class PersonOpThread extends Thread {
private Person p;
public PersonOpThread(Person p) {
super();
this.p = p;
}
@Override
public void run() {
this.p.excute();
}
}
6.3创建多个线程同时调用同一个对象的成员方法
package com.javareview.thread.synchronizedkeyword.synchronizedexample;
public class PersonOpThreadTest {
public static void main(String[] args) {
//s1.构建线程需要操作的对象
Person p = new Person();
//s2.创建多个线程同时调用同一个对象的成员方法,为了保证线程安全,则在操作对象类的成员方法中加入了synchronized关键字,保证线程有序调用该同步方法
Thread t1 = new PersonOpThread(p);
Thread t2 = new PersonOpThread(p);
//s3.启动多个线程调用对象的同步方法
t1.start();
t2.start();
}
}
7.关于对象加锁
(1)多个线程类,生成多个线程对象,调用同一个对象中的两个不同同步方法,结果应该是有序的还是无序的呢?(是有序的)
(2)我们所说的加锁,是指给对象加锁。
(3)对象只有一个,它里面有n个synchronized方法.
(4)多线程环境下,当某一个线程去访问了某一个对象的synchronized方法的时候,这时候其他的任何线程都没法访问这个对象里面的所有的synchronized方法。(一定要记住,是给对象加锁,只要给对象的一个方法加锁,其他任何加上了锁的方法,都不能被其他线程所调用)
(5)多线程环境下,某一个线程要访问对象的任何一个synchronized方法,都需要给对象上锁,对象已经被锁住了,就无法再上锁了,除非等第一个线程执行完同步方法。要么同步方法抛异常了,导致方法调用结束,这个时候其他的线程才能去访问该对象的其他的synchronized方法。
8.学生和老师抢占侧所资源案例
8.1定义具有两个同步方法的类
package com.javareview.thread.synchronizedkeyword.multithread;
/**
* 定义具有两个同步方法的类
*/
public class Toilet {
public synchronized void excute(){
for(int i = 0 ; i < 50 ; i++){
try {
Thread.sleep((long)(Math.random()*500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello: " + i);
}
}
public synchronized void excute2(){
for(int i = 0 ; i < 50 ; i++){
try {
Thread.sleep((long)(Math.random()*500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("world: " + i);
}
}
}
8.2定义多个线程类
package com.javareview.thread.synchronizedkeyword.multithread;
/**
* 定义学生线程类
*/
public class StudentThread extends Thread {
private Toilet toilet;
public StudentThread(Toilet toilet) {
super();
this.toilet = toilet;
}
@Override
public void run() {
this.toilet.excute();
}
}
package com.javareview.thread.synchronizedkeyword.multithread;
/**
* 定义老师线程类
*/
public class TeacherThread extends Thread {
private Toilet toilet;
public TeacherThread(Toilet toilet) {
super();
this.toilet = toilet;
}
@Override
public void run() {
this.toilet.excute2();
}
}
8.3定义多个线程调用同一对象的不同同步方法,观察同步方法的执行顺序。
package com.javareview.thread.synchronizedkeyword.multithread;
/**
* 构建学生与老师两个线程对侧所这一公共资源进行抢占
*/
public class MultiThreadTest {
public static void main(String[] args) {
Toilet toilet = new Toilet();
StudentThread stuThread = new StudentThread(toilet);
TeacherThread teaThread = new TeacherThread(toilet);
stuThread.start();
teaThread.start();
}
}
8.4执行结果
hello: 0
hello: 1
hello: 2
...
hello: 47
hello: 48
hello: 49
world: 0
world: 1
...
world: 47
world: 48
world: 49
8.5小结
(1)如果一个对象有多个synchronized方法,多线程环境下,同一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕之前,其他线程是无法访问该对象的任何synchronized方法的。
9.多个线程访问多个对象的synchronized方法
(1)多个线程访问多个对象的synchronized方法。(多个对象,不同线程,访问对象的synchronized方法。)
(2)分析
(a)第一个线程对象调用excute同步方法,第一个对象就被上锁了。
(b)第二个线程对象调用第二个对象的同步方法,第一个对象上锁了,与第二个对象没什么关系,第二个对象肯定还没上锁,因此第二个对象还是照样可以调用对象的synchronized方法的,所以当两个线程同时运行时,这种情况下输出结果就不能保证有序。
(3)因此最关键的是给哪个对象上了锁。
(a)一个对象上了锁,这个对象的其他任何synchronized方法都没法访问了。
(b)如果要是两个对象或多个对象,每个对象上一把锁,对A对象进行上锁,不会影响B对象同步方法的调用。
9.1定义多线程需要访问的类
package com.javareview.thread.synchronizedkeyword.lockmultiobj;
/**
* 定义多线程需要访问的类
*/
public class Person {
public synchronized void excute() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello: " + i);
}
}
public synchronized void excute2() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("world: " + i);
}
}
}
9.2 定义线程类
package com.javareview.thread.synchronizedkeyword.lockmultiobj;
/**
* 定义线程类,调用Person对象的同步方法excute
*/
public class StudentOpThread extends Thread {
private Person person;
public StudentOpThread(Person person) {
super();
this.person = person;
}
@Override
public void run() {
this.person.excute();
}
}
package com.javareview.thread.synchronizedkeyword.lockmultiobj;
/**
* 定义线程类,调用Person对象的同步方法excute2
* @author xiongjie
*
*/
public class TeacherOpThread extends Thread {
private Person person;
public TeacherOpThread(Person person) {
super();
this.person = person;
}
@Override
public void run() {
this.person.excute2();
}
}
9.3 多个线程访问多个对象的synchronized方法。
package com.javareview.thread.synchronizedkeyword.lockmultiobj;
public class MultiThreadTest {
public static void main(String[] args) {
//s1.创建对象1
Person person = new Person();
//s2.创建线程对象访问对象1中的同步方法
StudentOpThread stuThread = new StudentOpThread(person);
//s3.创建对象2
person = new Person();
//s4.创建线程对象调用对象2中的同步方法
TeacherOpThread teaThread = new TeacherOpThread(person);
stuThread.start();
teaThread.start();
}
}
9.4执行结果
是一种乱序现象
hello: 0
hello: 1
world: 0
world: 1
hello: 2
world: 2
...
10.将对象的方法上锁并设置为静态的
10.1定义多线程需要访问的类,具有两个静态的同步方法
(1)说明,相较上例只给多线程需要访问的类的同步方法加上static关键字
package com.javareview.thread.synchronizedkeyword.lockstatic;
/**
* 定义多线程需要访问的类
*/
public class Person {
public static synchronized void excute() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello: " + i);
}
}
public static synchronized void excute2() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("world: " + i);
}
}
}
10.2执行结果
输出有序的数据,两个static修饰的同步方法都属于类级别的方法,不是对象级别的,所以能够保证数字有序输出。
hello: 0
...
hello: 48
hello: 49
world: 0
world: 1
...
world: 48
world: 49
11.将对象的方法上锁,并设置其中的一个方法为静态,另一个方法非静态.
(1)说明,相较上例只给多线程需要访问类的同步方法去掉一个static关键字
11.1定义多线程需要访问的类,去掉其中一个同步方法的static关键字
package com.javareview.thread.synchronizedkeyword.firstsample;
/**
* 定义多线程需要访问的类
*/
public class Person {
public static synchronized void excute() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello: " + i);
}
}
public synchronized void excute2() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("world: " + i);
}
}
}
11.2执行结果
输出乱序数字 static修饰的同步方法属于类级别的,而非static的同步方法属于对象级别的,执行顺序有先有后,所以输出数据乱序。
world: 0
hello: 0
world: 1
hello: 1
world: 2
hello: 2
hello: 3
world: 3
world: 4
hello: 4
le;
/**
* 定义多线程需要访问的类
*/
public class Person {
public static synchronized void excute() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello: " + i);
}
}
public synchronized void excute2() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep((long) (Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("world: " + i);
}
}
}
11.2执行结果
输出乱序数字 static修饰的同步方法属于类级别的,而非static的同步方法属于对象级别的,执行顺序有先有后,所以输出数据乱序。
world: 0
hello: 0
world: 1
hello: 1
world: 2
hello: 2
hello: 3
world: 3
world: 4
hello: 4