了解线程同步的作用
一个多线程的程序,如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,那么这样一来就会造成一种问题,如果这多个线程要操作同一资源的时候就有可能出现资源的同步问题。例如:以之前的卖票程序来讲,如果多个线程同时操作的时候就有可能出现卖出票为负数的问题。
package com.dong1990;
public class fifth {
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() ; // 启动线程
}
}
class MyThread implements Runnable { // 继承Thread类
private int ticket = 5 ; // 一共5张票
public void run(){ // 覆写run()方法
for(int i=0;i<100;i++){ // 超出票数的循环
if(ticket>0){ // 判断是否有剩余票
try {
Thread.sleep(300) ; // 加入延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
}
从运行结果可以发现,程序中加入了延迟操作,所以在运行的最后出现了负数的情况,那么为什么现在会产生这样的问题呢?
从上面的操作代码可以发现对于票数的操作步骤如下:
1、 判断票数是否大于0,大于0则表示还有票可以卖
2、 如果票数大于0,则卖票出去
但是,在上面的操作代码中,在第1步和第2步之间加入了延迟操作,那么一个线程就有可能在还没有对票数进行减操作之前,其他线程就已经将票数减少了,这样一来就会出现票数为负的情况。
如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
解决资源共享的同步操作,可以使用同步代码块和同步方法两种方式完成。
了解同步代码块及同步方法的作用
在代码块上加上“synchronized”关键字的话,则此代码块就称为同步代码块。
同步代码块格式:
synchronized(同步对象){
需要同步的代码 ;
}
使用同步代码块解决以上的同步问题
package com.dong1990;
public class fifth {
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() ; // 启动线程
}
}
class MyThread implements Runnable { // 继承Thread类
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 (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
}
}
除了可以将需要的代码设置成同步代码块之外,也可以使用synchronized关键字将一个方法声明成同步方法。
同步方法定义格式:
synchronized 方法返回值 方法名称(参数列表){}
使用同步方法解决以上问题
package com.dong1990;
public class fifth {
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() ; // 启动线程
}
}
class MyThread implements Runnable { // 继承Thread类
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 (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
了解死锁的产生
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如:现在有张三想要李四的画,李四想要张三的书,那么张三对李四说了:“把你的画给我,我就给你书”,李四也对张三说了:“把你的书给我,我就给你画”,此时,张三在等着李四的答复,而李四也在等着张三的答复,那么这样下去最终结果可想而知,张三得不到李四的画,李四也得不到张三的书,这实际上就是死锁的概念。
package com.dong1990;
public class fifth {
public static void main(String args[]) {
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
t1.start() ; // 启动线程
}
}
class MyThread implements Runnable { // 继承Thread类
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) { // 通过第一个对象
zs.say(); // 调用方法
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} // 加入延迟,要处理异常
synchronized (ls) { // 同步第二个对象
zs.get(); // 调用方法
}
}
} else { // Lisi先执行
synchronized (ls) { // 同步第二个对象
ls.say(); // 调用方法
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} // 加入延迟,要处理异常
synchronized (zs) { // 同步第一个对象
ls.get(); // 调用方法
}
}
}
}
}
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("李四得到书了。");
}
}