Java面向对象
Java异常
Java数组
Java常用类
Java集合
Java IO流
Java线程
Java反射
Socket编程
Java注解开发
Java GoF设计模式
HashMap
Java内存模型
Java线性表

Java线程同步

线程安全问题

在多线程程序中,当多个线程同时操作堆区/方法区同一个数据时,可能引发数据不一致的现象, 称为线程安全问题。

出现线程安全问题, 怎么办?

让每个线程都访问自己的局部变量, 不会产生线程安全问题

如果多个线程必须同时操作堆区/方法区同一个数据 , 采用线程同步技术

线程如何同步?

语法:

synchronized ( 锁对象 ) {

同步代码块

}

工作原理:

● 线程要执行同步代码块,必须先获得锁对象

● 任意对象都可以作为锁对象, 任意对象都有一个内置锁

● 一个锁对象最多被一个线程持有

● 线程持有了锁对象后,会一直持有, 直到执行完同步代码块后才会释放锁对象

package com.wkcto.chapter07.sync.p2;
/**
 * 银行帐户类
 * @author 蛙课网
 *
 */
public class BankAccount {
	int balance = 10000 ; 			//余额 ,单位:万元
	private static final Object OBJ = new Object(); 		//常量
	
	//取钱的操作, 约定, 每次取钱1000
	public void withdraw() {
		synchronized ( OBJ ) { 		//一般使用常量作为锁对象
			System.out.println(Thread.currentThread().getName() +  "取钱 前, 余额为:" + balance);
			balance -= 1000;
			System.out.println(Thread.currentThread().getName() + "取了1000万后, 余额为:" + balance);
		}
		/*
		 * 1) xiaoming获得了CPU执行权, 执行withdraw()方法, 先获得OBJ锁对象, 执行同步代码块
		 * 2) xiaoming在执行同步代码块期间, CPU执行权被baby抢走了, xiaoming转为就绪状态
		 * 		babay获得CPU执行权, 要也执行同步代码块, 必须先获得OBJ锁对象,
		 * 		现在OBJ锁对象被xiaoming持有, baby线程转到等待锁对象池中阻塞
		 * 3) xiaoming重新获得CPU执行权, 继续执行同步代码块, 执行完同步代码块后, 释放OBJ锁对象
		 * 4) 等待锁对象池中的baby如果获得了锁对象, 转为就绪状态
		 */
	}
	
}
package com.wkcto.chapter07.sync.p2;
/**
 * 定义一个线程类,模拟 从银行帐户 中取钱
 * @author 蛙课网
 *
 */
public class PersonThread extends Thread {
	private BankAccount bankaccount; 		//银行帐户 
	
	public PersonThread(BankAccount bankaccount) {
		super();
		this.bankaccount = bankaccount;
	}

	@Override
	public void run() {
		bankaccount.withdraw();
	}
}
package com.wkcto.chapter07.sync.p2;
/**
 * 使用线程模拟多人同时从某一帐户中取钱
 * 	线程同步
 * @author 蛙课网
 *
 */
public class Test01 {

	public static void main(String[] args) {
		//先开户
		BankAccount account = new BankAccount();
		
		//定义三个线程模拟三个人, 是从同一个帐户中取钱 
		PersonThread xiaoming = new PersonThread(account);
		PersonThread bingbing = new PersonThread(account);
		PersonThread baby = new PersonThread(account);
		
		xiaoming.setName("xiaoming");
		bingbing.setName("bingbing");
		baby.setName("baby");
		
		xiaoming.start();
		bingbing.start();
		baby.start();
		
	}

}

同步代码块

同步代码块要想实现同步,必须保证使用同一个锁对象

只要使用了同一个锁对象的同步代码块就可以同步

package com.wkcto.chapter07.sync.p3;
/**
 * 同步代码块, 只要使用相同的锁对象就可以实现同步
 * @author 蛙课网
 *
 */
public class Test01 {
	public static void main(String[] args) {
		PrintNum pNum = new PrintNum();
		
		//创建线程,调用m1()
		new Thread(new Runnable() {
			@Override
			public void run() {
				pNum.m1();
			}
		}).start();
		
		//创建线程,调用m2()
		new Thread(new Runnable() {
			@Override
			public void run() {
//				pNum.m2();			//当使用this对象作为锁对象时, 可以同步
				new PrintNum().m2();  //使用this作为锁对象, 不能同步,  与第一个线程的锁对象不是一个
			}
		}).start();
	}
}
package com.wkcto.chapter07.sync.p3;
/**
 * 定义类
 * 	提供两个 方法,分别打印字符串
 * @author 蛙课网
 *
 */
public class PrintNum {
	private static final Object OBJ = new Object(); 		//定义常量 
	
	public void m1() {
//		synchronized (OBJ) {			//经常使用常量作为锁对象
		synchronized ( this ) {			//有时也会使用this作为锁对象 , 就是调用m1()方法的对象
			for(int i = 1; i<=200; i++){
				System.out.println("wkcto is NB website");
			}			
		}
	}
	
	public void m2() {
//		synchronized (OBJ) {
		synchronized (this) {
			for(int i = 1; i<=200; i++){
				System.out.println("sxbdqn is a good school");
			}
			
		}
	}
}
ackage com.wkcto.chapter07.sync.p4;
/**
 * 使用当前类的运行时类作为锁对象
 * @author 蛙课网
 *
 */
public class Test02 {

	public static void main(String[] args) {
		//创建线程, 打印wkcto
		new Thread(new Runnable() {
			@Override
			public void run() {
				while( true ){
					printText("wkcto");
				}
			}
		}).start();
		
		//创建线程, 打印sxbdqn
		new Thread(new Runnable() {
			@Override
			public void run() {
				while( true ){
					printText("sxbdqn");
				}
			}
		}).start();
	}

	private static final Object OBJ = new Object();	//常量 
	
	//静态方法,打印一个字符串
	public static void printText( String  text) {
//		synchronized (OBJ) {
		synchronized ( Test02.class ) {  	//使用当前类的运行时类对象作为锁对象, 有人把它称为类锁
											//可以简单的理解 为把当前类的字节码文件作为锁对象
			for( int i = 0 ; i<text.length() ; i++){
				System.out.print( text.charAt(i));
			}
			System.out.println();
		}
		
	}
}

同步方法

package com.wkcto.chapter07.sync.p5;
/**
 * 同步实例方法, 就是把整个方法体作为同步代码块, 默认锁对象 是this对象
 */
public class Test01 {

	public static void main(String[] args) {
		Test01 obj = new Test01();
		
		//创建线程, 调用m1()
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.m1();
			}
		}).start();
		
		//创建线程, 调用m2()
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.m2();
			}
		}).start();
		
	}
	
	/*
	 * 把整个方法体作为同步代码块,并且使用this作为锁对象时, 可以直接使用synchronized修饰方法, 称为同步方法 
	 * 同步实例方法, 就是把整个方法体作为同步代码块, 默认锁对象 是this对象
	 */
	public synchronized void m1() {
		for(int i =1; i<=500; i++){
			System.out.println( Thread.currentThread().getName() + "-->" + i);
		}
	}
	
	public void m2() {
		synchronized ( this ) {
			for(int i =1; i<=500; i++){
				System.out.println( Thread.currentThread().getName() + "======>" + i);
			}
		}
	}

}
package com.wkcto.chapter07.sync.p5;
/**
 * 同步静态方法, 就是把整个方法体作为同步代码块, 默认锁对象 是当前类的运行时类对象
 */
public class Test02 {

	public static void main(String[] args) {
		
		//创建线程, 调用m1()
		new Thread(new Runnable() {
			@Override
			public void run() {
				Test02.m1();
			}
		}).start();
		
		//创建线程, 调用m2()
		new Thread(new Runnable() {
			@Override
			public void run() {
				Test02.m2();
			}
		}).start();
		
	}
	
	/*
	 * 把整个方法体作为同步代码块,并且使用当前类的运行时类对象(Test02.class)作为锁对象时, 可以直接使用synchronized修饰方法, 称为同步方法 
	 * 同步静态方法, 就是把整个方法体作为同步代码块, 默认锁对象 是当前类的运行时类对象(Test02.class)
	 */
	public synchronized static void m1() {
		for(int i =1; i<=500; i++){
			System.out.println( Thread.currentThread().getName() + "-->" + i);
		}
	}
	
	public static void  m2() {
		synchronized ( Test02.class ) {
			for(int i =1; i<=500; i++){
				System.out.println( Thread.currentThread().getName() + "======>" + i);
			}
		}
	}

}