一、使用同步解决买票问题
解决资源共享的同步操作问题,可以使用同步代码块和同步方法两种方式。
第一种:使用同步代码块必须指定一个需要同步的对象,通常将当前对象(this)设置成同步对象。
class SaleTask implements Runnable{ PRivate int ticket = 5;//剩余票数 public void run() { if(ticket>0){ System.out.println("线程"+Thread.currentThread().getName()+"需要卖票"); synchronized (this) { if(ticket>0){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程"+Thread.currentThread().getName()+ "卖了一张票,剩余票数"+(--ticket)+"张"); }else{ System.out.println("线程"+Thread.currentThread().getName()+"需要卖票,已无票"); } } }else{ System.out.println("线程"+Thread.currentThread().getName()+"需要卖票,已无票"); } }}public class Main { public static void main(String[] args){ SaleTask sale = new SaleTask(); for(int i=0;i<8;i++){ //有8个线程需要卖票 Thread t1 = new Thread(sale,"thread"+(i+1)); t1.start(); } }}运行结果:
线程thread1需要卖票线程thread3需要卖票线程thread2需要卖票线程thread6需要卖票线程thread7需要卖票线程thread4需要卖票线程thread8需要卖票线程thread5需要卖票线程thread1卖了一张票,剩余票数4张线程thread5卖了一张票,剩余票数3张线程thread8卖了一张票,剩余票数2张线程thread4卖了一张票,剩余票数1张线程thread7卖了一张票,剩余票数0张线程thread6需要卖票,已无票线程thread2需要卖票,已无票线程thread3需要卖票,已无票第二种:同步方法public synchronized void run() { if(ticket>0){ System.out.println("线程"+Thread.currentThread().getName()+ "需要卖票"); if(ticket>0){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程"+Thread.currentThread().getName()+ "卖了一张票,剩余票数"+(--ticket)+"张"); }else{ System.out.println("线程"+Thread.currentThread().getName()+ "需要卖票,已无票"); } }else{ System.out.println("线程"+Thread.currentThread().getName()+ "需要卖票,已无票"); } }运行结果:
线程thread1需要卖票线程thread1卖了一张票,剩余票数4张线程thread7需要卖票线程thread7卖了一张票,剩余票数3张线程thread3需要卖票线程thread3卖了一张票,剩余票数2张线程thread8需要卖票线程thread8卖了一张票,剩余票数1张线程thread4需要卖票线程thread4卖了一张票,剩余票数0张线程thread6需要卖票,已无票线程thread5需要卖票,已无票线程thread2需要卖票,已无票二、死锁多个线程共享同一资源时需要进行同步,以保证资源操作的完整性,但是过多的同步就有可能产生死锁。
class ThreadTask implements Runnable{ private static String resourceA = "Resource A"; private static String resourceB = "Resource B"; public boolean flag;//标记获取资源的顺序,true表示先获取A,再获取B;false则相反 public void run() { if(flag){ synchronized(resourceA){ System.out.println("线程"+Thread.currentThread().getName()+"获取了资源A"); try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(resourceB){ System.out.println("线程"+Thread.currentThread().getName()+"获取了资源B"); } } } else{ synchronized(resourceB){ System.out.println("线程"+Thread.currentThread().getName()+"获取了资源B"); try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(resourceA){ System.out.println("线程"+Thread.currentThread().getName()+"获取了资源A"); } } } }}public class Main { public static void main(String[] args){ ThreadTask task1 = new ThreadTask(); ThreadTask task2 = new ThreadTask(); task1.flag = true; task2.flag = false; Thread t1 = new Thread(task1,"thread-1"); Thread t2 = new Thread(task2,"thread-2"); t1.start(); t2.start(); }}运行结果:
线程thread-1获取了资源A线程thread-2获取了资源B 两个线程都在等待对方释放手中的资源,这样就产生了死锁。三、线程操作案例——生产者与消费者
线程操作中一个典型的案例——生产者与消费者问题,生产者不断生产,消费者不断消费生产者生产的产品。
从中可以看出,生产者产生资源后将资源放到一个区域中,消费者从这个区域中取出产品,由于线程运行的不确定性,可能会产生下面两种问题:
1)生产者刚向数据存储空间中添加了信息的名称,还没有添加信息的内容,程序就切换到了消费者线程,这样消费者线程读取到的是更新后的信息的名称和上一次信息的内容。
2)生产者连续更新了多次信息,消费者才开始读取,或者,消费者连续多次读取信息,生产者却还没来及更新。
1.程序的基本实现
无论是生产者还是消费者,操作的都是信息(资源),所以需要定义一个信息类。
//信息类(资源)class Info { private String name = "李兴华"; //资源name private String content = "java讲师"; //资源content public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setContent(String content){ this.content = content; } public String getContent(){ return content; }}生产者和消费者相当于两个线程,操作同一个空间,分别实现Runnable接口
//生产者线程class Producer implements Runnable{ private Info info = null; //资源的引用 public Producer(Info info){ this.info = info; } public void run() { boolean flag = false; for(int i=0;i<50;i++){ //循环50次让生产者生产具体的内容 if(flag){ this.info.setName("李兴华"); try{ Thread.sleep(90); }catch(InterruptedException e){ e.printStackTrace(); } this.info.setContent("Java讲师"); flag = false; }else{ this.info.setName("mldn"); try{ Thread.sleep(90); }catch(InterruptedException e){ e.printStackTrace(); } this.info.setContent("www.mldnjava.cn"); flag = true; } } }}//消费者线程class Consumer implements Runnable{ private Info info = null; //资源的引用 public Consumer(Info info){ this.info = info; } public void run() { for(int i=0;i<50;i++){ try{ Thread.sleep(110); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(this.info.getName()+"--->"+this.info.getContent()); } }}主方法:public class Main { public static void main(String[] args){ Info i = new Info(); Producer p = new Producer(i); Consumer c = new Consumer(i); Thread pt = new Thread(p,"thread-1"); Thread ct = new Thread(c,"thread-2"); pt.start(); ct.start(); }}运行结果:
李兴华--->www.mldnjava.cnmldn--->Java讲师李兴华--->www.mldnjava.cnmldn--->Java讲师mldn--->Java讲师李兴华--->www.mldnjava.cnmldn--->Java讲师李兴华--->www.mldnjava.cn李兴华--->www.mldnjava.cnmldn--->Java讲师......2.解决问题1——加入同步
将设置信息的方法和获取信息的方法都设为同步方法,使得无论是设置信息还是读取信息,都需要先获取信息对象,在一段时间内,只有一个线程可以对资源进行操作。
//信息类(资源)class Info { private String name = "李兴华"; //资源name private String content = "Java讲师"; //资源content public synchronized void set(String name,String content){ setName(name); try{ Thread.sleep(90); //加入延迟 }catch(InterruptedException e){ e.printStackTrace(); } setContent(content); } public synchronized void get(){ try{ Thread.sleep(100); //加入延迟 }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(getName()+"--->"+getContent()); } public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setContent(String content){ this.content = content; } public String getContent(){ return content; }}生产者:
//生产者线程class Producer implements Runnable{ private Info info = null; //资源的引用 public Producer(Info info){ this.info = info; } public void run() { boolean flag = false; for(int i=0;i<50;i++){ if(flag){ this.info.set("李兴华", "Java讲师"); flag = false; }else{ this.info.set("mldn", "www.mldnjava.cn"); flag = true; } } }}消费者:
//消费者线程class Consumer implements Runnable{ private Info info = null; //资源的引用 public Consumer(Info info){ this.info = info; } public void run() { for(int i=0;i<50;i++){ this.info.get(); } }}mldn--->www.mldnjava.cn李兴华--->Java讲师mldn--->www.mldnjava.cnmldn--->www.mldnjava.cn李兴华--->Java讲师李兴华--->Java讲师李兴华--->Java讲师mldn--->www.mldnjava.cn李兴华--->Java讲师李兴华--->Java讲师......从运行结果可以看出,信息错乱的问题解决了,但是仍然存在信息重复读取的问题,既然有重复读取,就有重复设置,对于这样的问题需要用到Object类。
3.Object类对线程的支持
1.Object类有以下几种方法是对线程支持的:
从表中可知,可以将线程设为等待状态,也可以唤醒线程。唤醒线程的方法有两个,所有等待的线程一般会按照顺序排列,如果用notify()方法唤醒,则会唤醒第一个线程,若用notifyAll()方法唤醒,则会唤醒所有的等待线程,哪一个线程的优先级高,哪一个线程就有可能先执行。
2.解决问题2——加入等待与唤醒
如果想让生产者不重复生产,消费者不重复读取,可以增加一个标志位,标志位为boolean型,
若为true,表示生产者可以生产,但消费者不可以读取,如果是消费者线程则需要等待;
若为false,表示消费者可以读取,但生产者不可以生产,如果是生产者线程则需要等待。
要完成以上功能,直接修改Info类即可,在Info类中增加一个标志位,通过判断标志位完成等待与唤醒操作。
//信息类(资源)class Info { privateString name = "李兴华"; //资源name privateString content = "Java讲师"; //资源content privateboolean flag = false; //true,表示可以生产,不能消费 //false表示可以消费,不能生产 //生产资源 publicsynchronized void set(String name,String content){ if(!flag){ //为false时,生产者线程等待 try{ super.wait(); //wait()方法会让线程释放手中的锁 }catch (InterruptedException e) { e.printStackTrace(); } } setName(name); try{ Thread.sleep(90); //加入延迟 }catch(InterruptedExceptione){ e.printStackTrace(); } setContent(content); this.flag= false; super.notify(); //唤醒等待的线程 } //消费资源 publicsynchronized void get(){ if(flag){ try{ super.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } try{ Thread.sleep(100); //加入延迟 }catch(InterruptedExceptione){ e.printStackTrace(); } System.out.println(getName()+"--->"+getContent()); this.flag= true; super.notify(); //唤醒等待的线程 } publicvoid setName(String name){ this.name= name; } publicString getName(){ returnname; } publicvoid setContent(String content){ this.content= content; } publicString getContent(){ returncontent; }}运行结果:
李兴华--->Java讲师mldn--->www.mldnjava.cn李兴华--->Java讲师mldn--->www.mldnjava.cn李兴华--->Java讲师mldn--->www.mldnjava.cn李兴华--->Java讲师mldn--->www.mldnjava.cn李兴华--->Java讲师mldn--->www.mldnjava.cn......从运行结果可以发现,生产者每生产一个资源就要等待消费者取走,消费者每取走一个就要等待生产者生产,这样就避免了重复生产和重复取走的问题。
四、线程的生命周期
其中3个方法:
suspend():暂时挂起线程
resume():回复挂起的线程
stop():停止线程
但这三种方法不推荐使用,因为使用这三种方法会产生死锁的问题,在源代码中这三个方法使用了@Deprecated
新闻热点
疑难解答