进程:系统分配资源的单位;
线程:处理器任务调度和执行的单位,线程之间共享进程资源。
线程不同步问题:
问题:会出现多个窗口买一张票的情况/卖错票(票号为0/-1)
问题出现原因:一个线程已经运行了,但是还没有执行ticket--,后续线程也进来了,导致他们操作了同一张票号...
解决:如果一个线程已经运行了,但是在运行的过程中阻塞了,也不会给后续线程分配CPU资源,直至该线程运行完毕。
线程安全的两种方法:1.方式一:同步代码块(synchronized);2.方式二:同步方法。
注意:同步代码块和同步方法统一的规则:不能包含过多的代码,也不能包含过少的代码,只包含存在安全隐患的代码。
1.方式一:同步代码块;
书写格式:
synchronized(同步监视器){ //需要同步的代码块} /*** 参数说明:* 1.需要被同步的代码:操作共享数据的代码(注意不能多包含代码,也不能少包含代码);* 共享数据:多个线程共同操作的变量(例如,ticket票号)* 2.同步监视器,俗称:锁。 任何类型的对象都能充当锁* 要求:所有的线程必须保证共享一把锁。* 注意类也是对象(在学习反射的时候会详讲),可以使用(类名.class)来同步监视器;* 类只会被加载一次。* 注意使用继承Thread类时 慎用(this)来充当同步监视器;* 解释:(卫生间的锁不能从外面决定关锁/解锁,只能被进卫生间的人从里面关锁/解锁,也就是一个卫生间在同一时间的关锁/解锁只能被一个已经进入卫生间的人操作)。*/
满足条件:
1.多个线程共用一把锁;
2.任何类型的对象都能充当同步监视器(锁);
3.需要同步的代码,不能多包也不能少包。
1.1.继承Thread类
publicclassPraSaleTicket { publicstaticvoidmain(String[] args) { Salesale01=newSale(); Salesale02=newSale(); Salesale03=newSale(); sale01.setName("窗口01"); sale02.setName("窗口02"); sale03.setName("窗口03"); sale01.start(); sale02.start(); sale03.start(); } } classSaleextendsThread{ privatestaticintticket=100; ObjectmyLock=newObject(); publicvoidrun() { // synchronized (myLock) { // 不能多包含代码,也不能少包含代码while (true){ synchronized (Sale.class) {//当前类为静态类,只能加载一次,所以可以让当前类作为锁if (ticket>0) { try { Thread.currentThread().sleep(100); } catch (InterruptedExceptione) { e.printStackTrace(); } System.out.println(getName() +"_票号为:"+ticket); ticket--; } else { break; } } } } }
1.2.实现Runnable接口
publicclassPraSaleTicket_Runnable { publicstaticvoidmain(String[] args) { SaleTicketssale=newSaleTickets(); Threadthread=newThread(sale); Threadthread2=newThread(sale); Threadthread3=newThread(sale); thread.setName("窗口01"); thread2.setName("窗口02"); thread3.setName("窗口03"); thread.start(); thread2.start(); thread3.start(); } } classSaleTicketsimplementsRunnable{ privateintticket=100; // Object olock = new Object(); // 任何类型的对象都能充当锁。MyLockmyLock=newMyLock(); publicvoidrun() { // 错误的存放位置:Object olock = new Object(); // 任何类型的对象都能充当锁。while (true) { // 错误的存放位置:Object olock = new Object(); // 任何类型的对象都能充当锁。synchronized(myLock) { if (ticket>0) { //如果票号大于0,假设现在票号为1,线程1抢到了CPU,但是还没往后面走,线程2页签到了资源,线程3也抢到了...导致输出的票号出现了:1 0 -1 的现象。//如果此处没有调用sleep(),也会出现重复票号的问题...try { Thread.currentThread().sleep(100); } catch (InterruptedExceptione) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"_票号为:"+ticket); ticket--; } else { break; } } } } } //任何类型的对象都能作为同步监视器。classMyLock{ }
2.方式二:同步方法;
将存在线程安全隐患的代码形成一个方法,即为同步方法。
同步方法写法:
修饰符 (static) synchronized返回值类型方法名(){}
注意事项:
1.多个线程共用一把锁;
2.需要同步的代码,不能多包也不能少包。
3.静态的同步方法:默认使用的同步监视器是,当前类.class;
4.非静态的同步方法:默认使用的同步监视器是,this,指代当前类。
注意此时将需要同步的方法,使用了同步方法来保证线程安全,针对买100张票的话,我们没办法终止循环,所以在循环结束后需要手动终止循环!!!
2.1.继承Thread类
publicclassPraSaleTicket02 { publicstaticvoidmain(String[] args) { Salessale01=newSales(); Salessale02=newSales(); Salessale03=newSales(); sale01.setName("窗口01"); sale02.setName("窗口02"); sale03.setName("窗口03"); sale01.start(); sale02.start(); sale03.start(); } } classSalesextendsThread{ privatestaticintticket=100; publicvoidrun() { while (true){ showTicket(); } } privatestaticsynchronizedvoidshowTicket(){//默认的同步监视器是当前类(Sales.class)// private synchronized void showTicket(){ //使用该方法是错的,因为这样是默认传入的同步监视器是this(也就是sale01,sale02,sale03)//不能保证多个线程共用一把锁,所以线程是不安全的。if (ticket>0) { try { Thread.currentThread().sleep(100); } catch (InterruptedExceptione) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"_票号为:"+ticket); ticket--; } } }
2.2.实现Runnable接口
publicclassPraSaleTicket_Runnable02 { publicstaticvoidmain(String[] args) { SaleTicketsale=newSaleTicket(); Threadthread=newThread(sale); Threadthread2=newThread(sale); Threadthread3=newThread(sale); thread.setName("窗口01"); thread2.setName("窗口02"); thread3.setName("窗口03"); thread.start(); thread2.start(); thread3.start(); } } classSaleTicketimplementsRunnable{ privateintticket=100; publicvoidrun() { while (true) { showTickets(); } } privatesynchronizedvoidshowTickets(){//默认的同步监视器是SaleTicket.classif (ticket>0) { try { Thread.currentThread().sleep(100); } catch (InterruptedExceptione) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"_票号为:"+ticket); ticket--; } }
线程安全的好处/局限:
好处:解决线程安全问题;
局限:让线程效率低,相当于把多线程问题变成了单线程的问题。