一、继承Thread类和实现Runnable接口是否可以实现资源共享?
1.通过继承Thread类所创建的线程不能实现资源共享功能, 例如:
package com.Test.java; public class ShouPiao extends Thread { //定义车票【共享资源】 private int piao = 5; public void run() { while(piao>0) { //我们通过线程的暂停来模拟 //收钱-->打票-->找钱 try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-卖出1张票,还剩"+(--piao)+"张"); } } } package com.Test.java; public class Test { public static void main(String[] args) { ShouPiao p = new ShouPiao(); ShouPiao p2 = new ShouPiao(); ShouPiao p3 = new ShouPiao(); p.setName("窗口1"); p2.setName("窗口2"); p3.setName("窗口3"); p.start(); p2.start(); p3.start(); } }执行结果: 2.通过实现Runnable接口所创建的线程可以实现资源共享功能。 例如:
package com.Test.java2; public class ShouPiao implements Runnable { //定义车票【共享资源】 private int piao = 5; public void run() { while(piao>0) { //我们通过线程的暂停来模拟 //收钱-->打票-->找钱 try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-卖出1张票,还剩"+(--piao)+"张"); } } } package com.Test.java2; public class Test { public static void main(String[] args) { //创建目标对象 ShouPiao p = new ShouPiao(); //创建线程对象 Thread read = new Thread(p); Thread read2 = new Thread(p); Thread read3 = new Thread(p); read.setName("窗口1"); read2.setName("窗口2"); read3.setName("窗口3"); read.start(); read2.start(); read3.start(); } }执行结果: 通过上面的实现Runnable接口的买票程序可以实现资源共享,但是卖出会卖出剩下负数的情况。
分析:当窗口3开始卖最后一张票的时候,窗口3判断还有一张票,这时窗口3开始收钱打票,当窗口3开始收钱打票的时候,线程就切换给了窗口1,由于窗口3还有来得及对票数减1,因此窗口1判断还有一张票,这时窗口1开始收钱打票,当窗口1开始收钱打票的时候,线程就切换给了窗口2,由于窗口1还有来得及对票数减1,因此窗口2判断还有一张票,这时窗口2开始收钱打票,线程切换给了窗口3,所以窗口3输出“窗口3卖出1张票,还剩0张”,输出结束以后线程就切换给窗口1,由于窗口3已经对票数减1,所以窗口1输出剩余票数的时候在窗口3减1以后的基础上再一次减1,就得到剩余-1张票,所以窗口1输出“窗口1卖出1张票,还剩-1张”,输出结束以后线程就切换给窗口2,由于窗口1已经对票数减1,所以窗口2输出剩余票数的时候在窗口1减1以后的基础上再一次减1,就得到剩余-2张票,所以窗口2输出“窗口2卖出1张票,还剩-2张”. 经过上面运行程序的分析,我得到的结果是: 当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。 为了解决这种数据不一致的错误情况,我们才学习线程同步。
二、为什么需要线程同步/线程安全?
因为当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。为了解决这种数据不一致的错误情况,我们才学习线程同步。
三、什么是线程同步/线程安全? 线程同步:当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步也叫线程安全。
四、线程同步/线程安全的实现方式有几种,分别是什么,有什么区别?
有三种方式可以实现线程同步/线程安全
1.同步代码块 格式: synchronized(同步对象){
} 例如:
package comsynchronized; public class ShouPiao implements Runnable { //定义车票【共享资源】 private int piao = 5; public void run() { synchronized (this) { while(piao>0) { //我们通过线程的暂停来模拟 //收钱-->打票-->找钱 try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-卖出1张票,还剩"+(--piao)+"张"); } } } } package comsynchronized; public class Test { public static void main(String[] args) { //创建目标对象 ShouPiao p = new ShouPiao(); //创建线程对象 Thread read = new Thread(p); Thread read2 = new Thread(p); Thread read3 = new Thread(p); read.setName("窗口1"); read2.setName("窗口2"); read3.setName("窗口3"); read.start(); read2.start(); read3.start(); } }执行结果: 同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。
2.同步方法 同步方法也是方法,所以它一定是符合方法的定义格式的。 方法的定义格式: 访问限制修饰符 方法返回值类型 方法名称(){
} 同步方法的定义格式: 访问限制修饰符 synchronized 方法返回值类型 方法名称(){
} 例如:
package comsynchronized.fangfa.java; public class ShouPiao implements Runnable { //定义车票【共享资源】 private int piao = 5; private boolean flag = true; public void run() { while(flag) { Piao(); } } public synchronized void Piao() { if(piao>0) { //我们通过线程的暂停来模拟 //收钱-->打票-->找钱 try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-卖出1张票,还剩"+(--piao)+"张"); }else { flag = false; } } } package comsynchronized.fangfa.java; public class Test { public static void main(String[] args) { //创建目标对象 ShouPiao p = new ShouPiao(); //创建线程对象 Thread read = new Thread(p); Thread read2 = new Thread(p); Thread read3 = new Thread(p); read.setName("窗口1"); read2.setName("窗口2"); read3.setName("窗口3"); read.start(); read2.start(); read3.start(); } }执行结果: 3.通过Lock接口 public interface Lock Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作 常用的接口方法
1.void lock() 获得锁。
2.void unlock() 释放锁。
由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类来创建对象。 Lock接口得子类ReentrantLock ReentrantLock() 创建一个 ReentrantLock的实例。
Lock lock=new ReentrantLock();
ReentrantLock reentrantLock=new ReentrantLock(); 例如:
package com.lock.java; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ShouPiao implements Runnable { //定义车票【共享资源】 private int piao = 5; //创建Lock对象 private Lock ck = new ReentrantLock(); public void run() { ck.lock(); while(piao>0) { //我们通过线程的暂停来模拟 //收钱-->打票-->找钱 try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-卖出1张票,还剩"+(--piao)+"张"); } ck.unlock(); } } package com.lock.java; public class Test { public static void main(String[] args) { ShouPiao p = new ShouPiao(); Thread read = new Thread(p); Thread read2 = new Thread(p); Thread read3 = new Thread(p); read.setName("窗口1"); read2.setName("窗口2"); read3.setName("窗口3"); read.start(); read2.start(); read3.start(); } }执行结果: 区别:
synchronizedLock关键字接口自动锁定资源手动锁定资源不灵活灵活-异常时会自动释放锁不会自动释放锁,所以需要在finally中实现释放锁不能中断锁,必须等待线程执行完成释放锁。可以中断锁