通过继承Thread类所创建的线程不能实现资源共享功能,
public class MyThread extends Thread{
//定义车票【共享资源】
private int piao=5;
@Override
public void run() {
while(piao>0) {
//我们通过线程的暂停来模拟
//收钱-->打票-->找钱
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"--卖出1张票,还剩"+(--piao)+"张");
}
}
}
//测试类
package com.wangxing.test1;
public class TestMain1 {
public static void main(String[] args) {
MyThread th1=new MyThread();
MyThread th2=new MyThread();
MyThread th3=new MyThread();
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
通过实现Runnable接口所创建的线程可以实现资源共享功能。
package com.wangxing.test1;
public class MyThread implements Runnable{
//定义票数
private int piao=5;
@Override
public void run() {
//得到线程的名称
String name=Thread.currentThread().getName();
boolean flag=true;
while(flag){
if(piao>0){
//线程休眠5秒
//模拟--收钱---》出票---》找钱
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票数-1
piao=piao-1;
System.out.println(name+"卖出一张票,还剩"+piao+"张");
}else{
flag=false;
}
}
}
}
//测试类
package com.wangxing.test1;
public class TestMain {
public static void main(String[] args) {
//得到目标对象
MyThread myth=new MyThread();
Thread th1=new Thread(myth);
Thread th2=new Thread(myth);
Thread th3=new Thread(myth);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.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张”.
经过上面运行程序的分析,我得到的结果是:
当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。
为了解决这种数据不一致的错误情况,我们才学习线程同步。
因为当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。为了解决这种数据不一致的错误情况,我们
才学习线程同步。
线程同步:当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共
资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的
一条线程才能访问资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步也叫线程安全。
有2中方式可以实现线程同步/线程安全
3.1.Synchronized关键字 【同步代码块/同步方法】
(1) 同步代码块
格式:
synchronized(同步对象){
}
package com.wangxing.test1;
/**
* 基于synchronized代码块
* synchronized(同步对象){
*
* }
* @author feng
*
*/
public class SynchronizedDemo1 implements Runnable {
//定义票数
private int piao=5;
@Override
public void run() {
//得到线程的名称
String name=Thread.currentThread().getName();
boolean flag=true;
while(flag){
//同步代码块
//同步对象就是共享数据所在的类构建的对象
synchronized(this){
if(piao>0){
//线程休眠1秒
//模拟--收钱--》出票--》找钱
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
piao=piao-1;
System.out.println(name+"卖出一张票,还剩"+piao+"张");
}else{
flag=false;
}
}
}
}
}
同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多 时候都不知道
这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。
(2) 同步方法
同步方法也是方法,所以它一定是符合方法的定义格式的。
方法的定义格式:
访问限制修饰符 方法返回值类型 方法名称(){
}
同步方法的定义格式:
访问限制修饰符 synchronized 方法返回值类型 方法名称(){
}
package com.wangxing.test1;
/**
* 基于synchronized方法
* 访问限制修饰符 synchronized 方法返回值类型 方法名称(){}
* @author feng
*
*/
public class SynchronizedMethodDemo2 implements Runnable{
private int piao=5;
private boolean flag=true;
@Override
public void run() {
//得到线程名称
String name=Thread.currentThread().getName();
while(flag){
//卖票的同步方法
sellpiao(name);
}
}
//卖票的同步方法
//参数为String。以此区别是谁卖的票
private synchronized void sellpiao(String name){
if(piao>0){
//线程休眠1秒
//模拟--收钱--》出票--》找钱
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//票数减1
piao=piao-1;
System.out.println(name+"卖出一张票,还剩"+piao+"张");
}else{
flag=false;
}
}
}
3.2.通过Lock接口
public interface Lock
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
常用的接口方法
void |
lock() 获得锁。 |
void |
unlock() 释放锁。 |
由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类来创建对象。
Lock接口得子类ReentrantLock
ReentrantLock() 创建一个 ReentrantLock的实例。
实例:
Lock lock=new ReentrantLock();
ReentrantLock reentrantLock=new ReentrantLock();
package com.wangxing.test1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 基于Lock接口的线程同步实现
* Lock接口不能new
* Lock lock=new ReentrantLock();
* void lock()获得锁
* void unlock()释放锁
* @author feng
*
*/
public class LockDemo implements Runnable {
//定义票数
private int piao=5;
private Lock lock=new ReentrantLock();
@Override
public void run() {
//得到线程的名称
String name=Thread.currentThread().getName();
boolean flag=true;
while(flag){
//void lock()获得锁
lock.lock();
if(piao>0){
//线程休眠1秒
//模拟--收钱--》出票--》找钱
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票数-1
piao=piao-1;
System.out.println(name+"卖出一张票,还剩"+piao+"张");
}else{
flag=false;
}
//void unlock() 释放锁。
lock.unlock();
}
}
}
实现线程安全(/线程同步)的Synchronized关键字和Lock接口的区别
synchronized |
Lock |
关键字 |
接口 |
自动锁定资源 |
手动锁定资源 |
不灵活 |
灵活 |
异常时会自动释放锁 |
不会自动释放锁,所以需要在finally中实现释放锁 |
不能中断锁,必须等待线程执行完成释放锁。 |
可以中断锁 |