1. 火车上买票产生的问题;
1 : 会产生重复买票 2. 卖票卖到最后可能是负数;
产生问题的原因: CPU执行线程是随机的, 可能会在任何位置停止,当两个或者以上的进程同时操作共享数据时候产生;
解决方法: 保证同一时间只有一个线程操作共享数据;
使用工具1 : 同步代码块( 也叫锁 ) synchronzed( ){ } ; 将共有数据写入大括号, 并且添加任意对象锁,可以用this锁;
使用工具2 :同步方法: 在方法名上,添加synchronized 锁;
使用工具3: JDK1.5出现的Lock类 1. 接口实现类中声明锁;private Reentreantlock lock = new Reentreantlock( ) ;
2. 在run()方法中调用 lock( )方法; 3. 使用Try { 操作共享的代码 } finally{ 释放锁 }
同步代码块执行原理: 当程序进入同步代码块的时候,先看看有没有锁,如果有锁,就带着锁进入到同步代码块中, 执行完毕以后
出代码块,同时交出锁, 没有锁,就在同步代码块前面等待;
1. 卖票问题的联系;
//采用接口实现线程方式,实现资源共享
class TicketRunnable implements Runnable{
//声明50张票;
private int ticket = 50;
//可以声明一下对象锁;
private Object object= new Object;
//重写run()方法;
@Override
public void run () {
while(true){
synchronized(this){
if(ticket>0)
System.out.println(Thread.currentThread().getName() + "-----" + ticket);
ticket--;
}else{
//票卖光了
break;
}
}
//让出CPU的执行资源;
Thread.yield();
}
}
//给main 方法进行测试; public static void main(String[] args) { //创建三个线程, TicketRunnable t = new TicketRunnable(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); //开启线程 t1.start(); t2.start(); t3.start(); }
2 . 封装一个同步方法; 我们需要一个返回值;
class TicketRunnable2 implements Runnable { // 声明50个票 private int ticket = 100; //生产锁,保证同一把;------------------------------------(第一步) private ReentrantLock lock = new ReentrantLock(); @Override // 买票,票-- public void run() { while (true) { // 同步锁 // 获取锁; ------------------------------------(第二步) lock.lock(); try { //try finally----------------------------(第三步) // 判断卖完没有 if (ticket > 0) { // 为了让测试结果更明显加个休眠; try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 卖 System.out.println(Thread.currentThread().getName() + "-----" + ticket); ticket--; } else { // 卖光了 break; } } finally { lock.unlock(); } // 让出cpu的执行资源(增加让出的概率) Thread.yield(); } }
//同步方法 public synchronized static boolean fun() { // 判断卖完没有 if (ticket > 0) { // 为了让测试结果更明显加个休眠; try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 卖 System.out.println(Thread.currentThread().getName() + "-----" + ticket); ticket--; // 返回值 return false; } else { // 卖光了 return true; } }
3. 利用 Lock类加锁
// 采用接口实现线程方式;
class TicketRunnable2 implements Runnable {
// 声明50个票
private int ticket = 100;
//生产锁,保证同一把;------------------------------------(第一步)
private ReentrantLock lock = new ReentrantLock();
public void run() { while (true) { // 同步锁 // 获取锁; ------------------------------------(第二步) lock.lock(); try { //try finally----------------------------(第三步) // 判断卖完没有 if (ticket > 0) { // 为了让测试结果更明显加个休眠; try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 卖 System.out.println(Thread.currentThread().getName() + "-----" + ticket); ticket--; } else { // 卖光了 break; } } finally { lock.unlock(); } // 让出cpu的执行资源(增加让出的概率) Thread.yield(); } }
4. 线程死锁
前提: 1. 必须同步代码块( 锁 )的嵌套; 2 . 锁对象唯一,使用的是同一把锁; 嵌套的锁保证唯一;
测试死锁
class LockB{ // 保证锁唯一, 不让外界创建,构造方法私有化 private LockB{ //定义一个常量;用final修饰,定义成静态的(只能用类名去调用),外界无法调用; public static final LockB lOCK_B = new LockB(); } } //定义A锁; class LockA { // 为了唯一不然外人创建自己创建;构造方法私有化 private LockA() { } // 定义一个常量;外界没法访问到.(对象点成员方法); 所以定义成静态的, public static final LockA lOCK_A = new LockA(); }
2.创建多线程
class DieLockRunnable implements Runnable { //声明一个标记;可以让两种结果重复执行; private boolean isflag=true; //第一次 先进A锁再进B锁; //第二次 先进B锁再进A锁; @Override public void run() { // 死循环,为了增加死锁的几率; while (true) { if (isflag) { // A-->B synchronized (LockA.lOCK_A) { System.out.println("我是if的LOCK_A锁"); synchronized (LockB.lOCK_B) { System.out.println("我是if的LOCK_B锁"); } } } else { // B-->A synchronized (LockB.lOCK_B) { System.out.println("我是if的LOCK_B锁"); synchronized (LockA.lOCK_A) { System.out.println("我是if的LOCK_A锁"); } } } // 修改标记 isflag = !isflag; } } }
3 创建测试类
public static void main(String[] args) { //测试 DieLockRunnable runnable = new DieLockRunnable() ; Thread t1= new Thread(runnable); Thread t2 = new Thread(runnable); t1.start(); t2.start(); }
5. 线程中断
如何让线程停止: stop(); 不推荐使用;
interrupt() 方法中断线程;
1. 调用interrupt方法的时候,线程中如果有wait() sleep()方法的时候, 这时会抛出一个异常,InterruptException异常;
并且清除中断状态; 这时是不会中断线程的;
2. 调用interrupt方法的时候,如果没有上述方法, 这时会设置改变中断状态的值;( true ---flase--)
测试 循环条件用 while( !Thread.interrupted){ } 初始值是true; 后来变成了false ;
最终我们得出的结论是: 用标记中断线程; 1. 接口实现类中 public boolean isflag = false; 2 ,在run 里面while( !isflag ){}
3. 在主程序里面,改变 isflag的状态,这样就可以直接中断线程;
public class Demo06线程如何停止 {
public static void main(String[] args) {
InterruptRunnable interruptRunnable = new InterruptRunnable();
Thread t1 = new Thread(interruptRunnable);
t1.start();
// 给子线几秒的执行时间然后再中断线程;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 中断线程这里就改变了 Thread .interrupted() 的状态, 从true,变成了false;
t1.interrupt();
//标记中断线程
// interruptRunnable.isFlag=true;
System.out.println("中断线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
class InterruptRunnable implements Runnable {
//声明 一个标识用来停止变成;
public boolean isFlag = false;
public void run() {
//直接使用中断的状态来中断线程;可以用类去调静态的;
while (!Thread.interrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 休眠1秒,利用循环完成秒间隔
//long time = System.currentTimeMillis();
//while (System.currentTimeMillis() - time < 1000) {
//}
System.out.println(Thread.currentThread().getName()+"...run");
}
}
}
6 测试: 子线程更改中断线程的标记; 主线程是否能接收到标记改变吗;
结论: 是肯定的 代码如下;
class VolRunnable implements Runnable{ // 当子线程修改的状态不能及时同步到主线程中, (让其他线程及时收到这个信息) //这时可以使用volatile让线程中的这个状态信息,可以及时得到修改; //标记 public volatile boolean isflag = false; //声明一个变量 记录什么时候更改状态 private int num= 0; @Override public void run() { while (!isflag) { num++; try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (num>5) { //更改中断状态 isflag = true; } System.out.println(Thread.currentThread().getName()+"..--run"); } } }
public static void main(String[] args) { VolRunnable volRunnable = new VolRunnable(); Thread t1 = new Thread(volRunnable); t1.start(); //利用中断状态卡住主线程 开始时候一直为true; 后来子线程 //在循环过程中让 isflag的状态发生改变了;从而影响到到了主线程; while (!volRunnable.isflag) { } System.out.println("主线程结束"); }
打印结果
Thread-0..--run Thread-0..--run Thread-0..--run Thread-0..--run Thread-0..--run Thread-0..--run 主线程结束