本章目标
了解线程同步的作用
了解同步代码块及同步方法的作用
了解死锁的产生
同步与死锁
一个多线程的程序,如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,那么这样一来就会造成一种问题,如果这多个线程要操作同一资源的时候就有可能出现资源的同步问题。例如:以之前的卖票程序来讲,如果多个线程同时操作的时候就有可能出现卖出票为负数的问题。
问题的引出
class MyThread implements Runnable{//实现 Runnable 接口
private int ticket = 5;//一共 5 张票
public void run(){//覆写 run() 方法
for(int i=0; i<100; i++){//超出票数的循环
if(ticket > 0){//判断是否有剩余的票
try{
Thread.sleep(300);//加入延迟
}catch(Exception e){
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
}
public class SyncDemo01 {
public static void main(String[] args) {
MyThread mt = new MyThread();//定义线程对象
Thread t1 = new Thread(mt);//定义 Thread 对象
Thread t2 = new Thread(mt);//定义 Thread 对象
Thread t3 = new Thread(mt);//定义 Thread 对象
t1.start();//启动线程
t2.start();//启动线程
t3.start();//启动线程
}
/* 结果:
* 卖票:ticket = 5
* 卖票:ticket = 3
* 卖票:ticket = 4
* 卖票:ticket = 2
* 卖票:ticket = 1
* 卖票:ticket = 0
* 卖票:ticket = -1
* */
}
程序的问题
从运行结果可以发现,程序中加入了延迟操作,所以在运行的最后出现了负数的情况,那么为什么现在会产生这样的问题呢?
从上面的操作代码可以发现对于票数的操作步骤如下:
——1、 判断票数是否大于0,大于0则表示还有票可以卖
——2、 如果票数大于0,则卖票出去
但是,在上面的操作代码中,在第1步和第2步之间加入了延迟操作,那么一个线程就有可能在还没有对票数进行减操作之前,其他线程就已经将票数减少了,这样一来就会出现票数为负的情况

问题的解决
如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
问题的解决
解决资源共享的同步操作,可以使用同步代码块和同步方法两种方式完成。
同步代码块
在代码块上加上“synchronized”关键字的话,则此代码块就称为同步代码块。
同步代码块格式:
——synchronized(同步对象){
需要同步的代码 ;
}
使用同步代码块解决以上的同步问题
class MyThread implements Runnable{//实现 Runnable 接口
private int ticket = 5;//一共 5 张票
public void run(){//覆写 run() 方法
for(int i=0; i<100; i++){//超出票数的循环
synchronized(this){//设置需要同步的操作
if(ticket > 0){//判断是否有剩余的票
try{
Thread.sleep(300);//加入延迟
}catch(Exception e){
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
}
}
public class SyncDemo02 {
public static void main(String[] args) {
MyThread mt = new MyThread();//定义线程对象
Thread t1 = new Thread(mt);//定义 Thread 对象
Thread t2 = new Thread(mt);//定义 Thread 对象
Thread t3 = new Thread(mt);//定义 Thread 对象
t1.start();//启动线程
t2.start();//启动线程
t3.start();//启动线程
}
/* 结果:
* 卖票:ticket = 5
* 卖票:ticket = 4
* 卖票:ticket = 3
* 卖票:ticket = 2
* 卖票:ticket = 1
* */
}
同步方法
除了可以将需要的代码设置成同步代码块之外,也可以使用synchronized关键字将一个方法声明成同步方法。
同步方法定义格式:
——synchronized 方法返回值 方法名称(参数列表){}
使用同步方法解决以上问题
class MyThread implements Runnable{//实现 Runnable 接口
private int ticket = 5;//一共 5 张票
public void run(){//覆写 run() 方法
for(int i=0; i<100; i++){//超出票数的循环
this.sale();
}
}
public synchronized void sale(){//声明同步方法
if(ticket > 0){//判断是否有剩余的票
try{
Thread.sleep(300);//加入延迟
}catch(Exception e){
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
public class SyncDemo03 {
public static void main(String[] args) {
MyThread mt = new MyThread();//定义线程对象
Thread t1 = new Thread(mt);//定义 Thread 对象
Thread t2 = new Thread(mt);//定义 Thread 对象
Thread t3 = new Thread(mt);//定义 Thread 对象
t1.start();//启动线程
t2.start();//启动线程
t3.start();//启动线程
}
/* 结果:
* 卖票:ticket = 5
* 卖票:ticket = 4
* 卖票:ticket = 3
* 卖票:ticket = 2
* 卖票:ticket = 1
* */
}
方法定义的完整格式
学习完了synchronized关键字之后,下面就可以给出Java中方法定义的完整格式了:
——访问权限{public|default|protected|private} [final] [static] [synchronized] 返回值类型|void 方法名称(参数类型 参数名称,…..) [throws Exception1,Exception2]{
[return [返回值|返回调用处]] ;
}
死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如:现在有张三想要李四的画,李死想要张三的书,那么张三对李四说了:“把你的画给我,我就给你书”,李四也对张三说了:“把你的书给我,我就给你画”,此时,张三在等着李四的答复,而李四也在等着张三的答复,那么这样下去最终结果可想而知,张三得不到李四的画,李四也得不到张三的书,这实际上就是死锁的概念。
定义两个类
class Zhangsan{//定义表示张三的类
public void say(){//定义 say() 方法
System.out.println("张三对李四说:“你给我画,我就把书给你。”");
}
public void get(){//定义得到东西的方法
System.out.println("张三得到画了。");
}
}
class Lisi{//定义表示李四的类
public void say(){//定义 say() 方法
System.out.println("李四对张三说:“你给我书,我就把画给你。”");
}
public void get(){//定义得到东西的方法
System.out.println("李四得到书了。");
}
}
测试死锁
public class ThreadDeadLock implements Runnable{//定义多线程类
private static Zhangsan zs = new Zhangsan();//实例化 static 型对象,数据共享
private static Lisi ls =new Lisi();//实例化 static 型对象,数据共享
private boolean flag = false;//声明标志位,用于判断哪个对象先执行
public void run(){//通过 run() 方法
if(flag){//判断标志位,Zhangsan 先执行
synchronized (zs) {//通过第 1 个对象
zs.say();//调用方法
try{
Thread.sleep(500);//加入延迟
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized (ls) {//同步第 2 个对象
zs.get();//调用方法
}
}
}else{//Lisi 先执行
synchronized (ls) {//同步第 2 个对象
ls.say();//调用方法
try{
Thread.sleep(500);//加入延迟
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized (zs) {//同步第 1 个对象
ls.get();//调用方法
}
}
}
}
public static void main(String[] args) {
ThreadDeadLock t1 = new ThreadDeadLock();//实例化线程对象
ThreadDeadLock t2 = new ThreadDeadLock();//实例化线程对象
t1.flag = true;//设置标记
t2.flag = false;//设置标记
Thread thA = new Thread(t1);//实例化 Thread 类对象
Thread thB = new Thread(t2);//实例化 Thread 类对象
thA.start();//启动线程
thB.start();//启动线程
}
/* 结果:
* 张三对李四说:“你给我画,我就把书给你。”
* 李四对张三说:“你给我书,我就把画给你。”
* */
}