前言
其实就是针对线程安全问题进行的,最经典的问题就是银行转账的问题,A向B转账的同时,A也在存钱,比如A有1000元,向B转200元,A自己存300元,按道理最后是1100元,如果没有同步机制,那么就可能发生问题,如果在A转账的时候,余额还没有开始减,然后A存钱的时候,拿到的余额是没有减去的,然后A的余额开始减去,但是存钱的线程拿到的余额是没有减去的,那么最后计算赋值可能变成了1000+300=1300了,同样,有可能在存钱的时候还没有计算赋值,另外一个转账线程进来,拿到的值是还没有加起来的,也就是1000元,那么转账线程执行完,1000-200=800,按道理是1100的,所以可能出现钱多了,或者钱少了的问题。
线程安全问题
package test01;
public class TraditionalThreadSychronized {
class Outputer{
// 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
public void output(String name){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
public void init(){
// 注意这里要申明为final
final Outputer out = new Outputer();
// 线程1:打印zhangsan
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.output("zhangsan");
}
}
}).start();
// 线程2:打印lisi
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.output("lisi");
}
}
}).start();
}
public static void main(String[] args) {
new TraditionalThreadSychronized().init();
}
}
如果不考虑线程安全问题的话,理想结果应该是zhangsan,lisi 循环打印,但是这里没有使用同步机制,输出结果中可以发现,出现了原本不想要的结果:
可以发现,打印出来的数据有些有问题。
解决办法: 采用同步机制,使用synchronized
使用同步解决线程安全问题
1、使用当前对象锁
class Outputer{
// 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
public void output(String name){
synchronized(this){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
}
2、使用当前对象属性的对象锁
class Outputer{
private String xxx = "";
// 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
public void output(String name){
synchronized(xxx){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
}
3、使用同步方法
class Outputer{
private String xxx = "";
// 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
public synchronized void output(String name){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
其实,使用同步方法,就是使用的当前对象的对象锁
输出结果:
打印结果中不再出现之前的打印问题,zhangsan,lisi都完整打印出来了,当然如果要实现轮流依次打印那种,之后会给出实现方案的,这里只讨论线程安全机制。
同步之间的互斥问题
第一种情况:同步代码块与同步方法
package test01;
public class TraditionalThreadSychronized {
class Outputer{
// 同步代码块形式
public void output(String name){
synchronized(this){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
// 同步方法形式
public synchronized void output2(String name){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
public void init(){
// 注意这里要申明为final
final Outputer out = new Outputer();
// 线程1:打印zhangsan
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用的是同步代码块形式的方法output
out.output("zhangsan");
}
}
}).start();
// 线程2:打印lisi
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用的是同步方法形式的方法output2
out.output2("lisi");
}
}
}).start();
}
public static void main(String[] args) {
new TraditionalThreadSychronized().init();
}
}
结果:不会出现线程安全问题。
分析:第一个线程调用的是同步代码块的方法output,第二个线程调用的是同步方法形式的方法output2,那么结果会怎么样了,会出现线程安全问题吗?答案当然是不会,前面说到了,同步方法其实使用的就是当前对象的对象锁,而同步代码块中用的也是当前对象的对象锁,所以肯定是互斥的,即不能当一个线程拿到了这个对象的对象锁的时候,其他对象不能调用当前对象需要对象锁的方法,说的通俗点就是,线程1进入output方法的时候,拿到了当前对象的对象锁,线程2没有当前对象的对象锁,所以访问不了方法output2,同样线程2拿到之后,线程1也访问不了output方法。
特殊情况:当然如果同步代码块中使用的是其他对象锁,那么就会出现线程安全问题了,因为两个方法使用的不是同一个对象的对象锁。
第二种情况:同步代码块与静态同步方法
package test01;
public class TraditionalThreadSychronized {
static class Outputer{
// 同步代码块形式
public void output(String name){
synchronized(this){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
// 同步方法形式
public synchronized void output2(String name){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
// 静态同步代码块
public static synchronized void output3(String name){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
public void init(){
// 注意这里要申明为final
final Outputer out = new Outputer();
// 线程1:打印zhangsan
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用的是同步代码块形式的方法output
out.output("zhangsan");
}
}
}).start();
// 线程2:打印lisi
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用的是静态同步方法形式的方法output3
out.output3("lisi");
}
}
}).start();
}
public static void main(String[] args) {
new TraditionalThreadSychronized().init();
}
}
结果:会出现线程安全问题。
分析:同步代码块使用的是当前对象的对象锁,而静态同步方法使用的是当前静态类class的对象锁。
解决办法:同步代码块中使用当前类class的锁,如下:
public void output(String name){
synchronized(Outputer.class){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
第三种情况:同步方法与静态同步方法
代码就省略了
结果:会出现线程安全问题
分析:同步方法使用的是当前类的对象的对象锁,而静态同步方法使用的是当前类的class对象的对象锁