经典卖票问题
窗口1、2、3同时卖100张票
这里用接口形式完成
package JavaThread;
class Window implements Runnable{
int ticket=100;
public void run(){
while(true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第:" + ticket + "张票");
ticket--;
} else {
break;
}
}
}
}
public class Test {
public static void main(String[] args) {
Window w=new Window();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
Thread t3=new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
出现重票或错票的原因:当线程一进入判断时 这时
的票可能是第1张 接着刚判断完便出现阻塞(sleep),接着cpu便去执行线程二而线程二也是判断的第1张票,线程二执行完毕让ticket减去1,ticket为0,这是cpu重新分配给线程一,线程一输出0。
出现错票
而产生重票是因为当线程一执行完输出语句后,线程突然切换导致ticket还没有减去1,别的线程又拿相同的数据进去判断再次输出从而产生重票。
原子性操作概念:
为了保证线程运行的安全问题所以提供了同步机制的解决方法
同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
原理:被同步代码块包含的代码再执行时即便出现阻塞状态别的线程也无法操作这段代码直到此线程执行完毕。
举例:如生活中上厕所,你在一个独立的厕所方便时,别人就算再急也要等你上完。
同步监视器:俗称锁,同步代码块需要一个对象作为参数来充当锁,任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
举例:相当于厕所门上的那把锁,你锁起来别人才进不来。
class saleTh implements Runnable{
private int ticket=100;
//Object obj=new Object();
public void run(){
while(true){
synchronized(this){//此时的this唯一的window对象(第二种方法——>)synchronized(obj){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class safeThread {
public static void main(String[] args) {
saleTh s=new saleTh();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
这是就不会出现重票错票
说明
1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
同步代码快应用到继承Thread的方法上
class window2 extends Thread{
private static int ticket=100;
private static Object obj=new Object();
public void run(){
while(true){
synchronized(window2.class){//window2.class只会加载一次类也是对象synchronized(obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class ThreadPratice2 {
public static void main(String[] args) {
window2 t1=new window2();
window2 t2=new window2();
window2 t3=new window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
这里将数据设置为静态是因为继承的方法需要创建三个对象为了让它们共享100张票所以需要设置为静态,而实现runnable接口的方法天然共享数据的,这里也可以使用类来充当锁,因为类也是对象
同步方法
实现runnable接口
class sale implements Runnable{
private int ticket=100;
public void run(){
while(true){
show();
}
}
private synchronized void show(){//同步监视器 this
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票"+ticket);
ticket--;
}
}
}
public class Thread01 {
public static void main(String[] args) {
sale s=new sale();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
继承Thread
class window extends Thread{
private static int ticket=100;
public void run(){
while(true){
show();
}
}
private static synchronized void show(){//同步监视器 当前的类window.class
//private synchronized void show(){这种方法是错的 同步监视器不为一个 t1 t2 t3
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":票号为" + ticket);
ticket--;
}
}
}
public class Thread02 {
public static void main(String[] args) {
window t1=new window();
window t2=new window();
window t3=new window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法会自动添加对象锁
补充:
释放锁的操作
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、
该方法的继续执行。 - 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。 - 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁。
不会释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、
Thread.yield()方法暂停当前线程的执行 - 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程
挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程