一. 引入
class Ticket implements Runnable
{
private int num=100;
public void run()
{
while(true)
{
if(num>0)
{
try{
Thread.sleep(10);
}
catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale:"+num--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果是:
发现:多线程的运行出现了安全问题,打印出0、-1、-2等错票。
二. 问题总结
多线程的运行出现了安全问题
1. 问题的原因
当多条语句在操作同一个线程共享数据时(写操作),一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致了共享数据的错误。
2. 解决方法
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中其它线程不可以参与执行。
(即使其它线程拿到了执行权,也不能进行操作)
3. Java的解决方法
同步代码块
synchronized(对象)
{
需要被同步的代码//与操作共享数据相关的代码
}
class Ticket implements Runnable
{
private int num=100;
Object obj=new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);}
catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale:"+num--);
}
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果是:
三. 同步代码块
对象如同锁
持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取了CPU的执行权也进不去,因为没有锁。
1. 同步的前提
(1)必须要有2个或者2个以上的线程
(2)必须是多个线程使用同一个锁
(3)必须保证同步中只能有一个线程在运行
2. 好处
解决了多线程的安全问题
3. 弊端
多个线程需要判断锁,较为消耗资源
4. 练习
需求:银行有一个金库,有两个储户分别存300元,每次存100元,存3次
目的:该程序是否有安全问题,如果有如何解决
如何找问题:
(1)明确哪些代码是多线程运行的代码
(2)明确共享数据
(3)明确多线程运行代码中哪些语句是操作共享数据的
问题:
class Bank{
private int sum;
public void add(int n){
sum=sum+n;
try{Thread.sleep(10);}
catch(Exception e){}
System.out.println("sum= "+sum);
}
}
class Cus implements Runnable{
private Bank b=new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
class BankDemo{
public static void main(String[] args){
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
运行结果是:
解决问题:
class Bank{
private int sum;
Object obj=new Object();
public void add(int n){
//同步代码块
synchronized (obj){
sum=sum+n;
try{Thread.sleep(10);}
catch(Exception e){}
System.out.println("sum= "+sum);
}
}
}
class Cus implements Runnable{
private Bank b=new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
class BankDemo{
public static void main(String[] args){
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
运行结果是:
四. 同步函数
1. 例子
class Bank{
private int sum;
//同步函数
public synchronized void add(int n){
sum=sum+n;
try{Thread.sleep(10);}
catch(Exception e){}
System.out.println("sum= "+sum);
}
}
class Cus implements Runnable{
private Bank b=new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
class BankDemo{
public static void main(String[] args){
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
运行结果是:
class Ticket implements Runnable
{
private int num=100;
Object obj=new Object();
public void run()
{
while(true)
{
show();
}
}
public synchronized void show(){
if(num>0)
{
try{Thread.sleep(10);}
catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale:"+num--);
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果是:
2. 同步函数的锁是this
函数需要被对象调用,那么函数都有一个所属对象的引用,就是this
所以同步函数使用的锁是this
(1)同步函数的锁不是Object类对象
使用2个线程来卖票
一个线程在同步代码块中,一个线程在同步函数中
都在执行卖票动作
class Ticket implements Runnable{
private int num=500;
Object obj=new Object();
boolean flag=true;
public void run(){
if(flag){
while(true){
//同步代码块
synchronized (obj){
if(num>0){
try{Thread.sleep(10);}
catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"...code: "+num--);
}
}
}
}
else{
while (true){
//同步函数
show();
}
}
}
public synchronized void show() {
if (num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...show: " + num--);
}
}
}
class ThisLockDemo{
public static void main(String[] args){
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
//在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
try{Thread.sleep(10);}
catch(Exception e){}
t.flag=false;
t2.start();
}
}
运行结果是:出现了错票0
原因:
同步的前提出问题了;
两个线程不是使用同一个锁,因此同步函数的锁不是Object类对象
(2)同步函数的锁是this
将同步代码块的对象换成this进行验证
class Ticket implements Runnable{
private int num=500;
Object obj=new Object();
boolean flag=true;
public void run(){
if(flag){
while(true){
//同步代码块
//将同步代码块的对象换成this
synchronized (this){
if(num>0){
try{Thread.sleep(10);}
catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"...code: "+num--);
}
}
}
}
else{
while (true){
//同步函数
show();
}
}
}
public synchronized void show() {
if (num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...show: " + num--);
}
}
}
class ThisLockDemo{
public static void main(String[] args){
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
//在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
try{Thread.sleep(10);}
catch(Exception e){}
t.flag=false;
t2.start();
}
}
运行结果是:没有出现错票
结论:同步函数的锁是this
五. 静态同步函数的锁是class对象
1. 静态同步函数的锁不是this
class Ticket implements Runnable{
private static int num=500;
Object obj=new Object();
boolean flag=true;
public void run(){
if(flag){
while(true){
//同步代码块
//将同步代码块的对象换成this
synchronized (this){
if(num>0){
try{Thread.sleep(10);}
catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"...code: "+num--);
}
}
}
}
else{
while (true){
//静态同步函数
show();
}
}
}
//静态同步函数
public static synchronized void show() {
if (num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...show: " + num--);
}
}
}
class StaticMethodDemo{
public static void main(String[] args){
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
//在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
try{Thread.sleep(10);}
catch(Exception e){}
t.flag=false;
t2.start();
}
}
运行结果是:出现了错票0
原因:
同步的前提出问题了;两个线程不是使用同一个锁,因此静态同步函数的锁不是this
(因为静态方法中不可以定义this)
2. 静态同步函数的锁是class对象
猜想:
静态方法进内存的时候没有对象,是由类调用的。
类进内存有对象,类要先封装成class类对象(字节码类对象);
验证:将同步代码块的对象换成Ticket.class
class Ticket implements Runnable{
private static int num=500;
Object obj=new Object();
boolean flag=true;
public void run(){
if(flag){
while(true){
//同步代码块
//将同步代码块的对象换成Ticket.class
synchronized (Ticket.class){
if(num>0){
try{Thread.sleep(10);}
catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"...code: "+num--);
}
}
}
}
else{
while (true){
//静态同步函数
show();
}
}
}
//静态同步函数
public static synchronized void show() {
if (num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "...show: " + num--);
}
}
}
class StaticMethodDemo{
public static void main(String[] args){
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
//在主线程加入sleep函数,使得线程1和线程2可以分别执行同步代码块和同步函数
try{Thread.sleep(10);}
catch(Exception e){}
t.flag=false;
t2.start();
}
}
运行结果是:没有出现错票
结论:
静态同步函数使用的锁是该方法所在类的字节码文件对象,类名.class