线程
-
线程
-
创建线程方式一:继承Thread类
- A extends Thread
- 覆盖Thread中的run方法,将线程的任务代码封装到run方法中。
- 在主函数中:
A a = new A();
a.start() //创建并启动线程
-
创建线程方式二:实现Runnable接口
- Class A implements Runnable
- 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
- 在主函数中:
A a = new A();
Thread t = new Thread(a); //接口引用作为参数传到Thread类中
t.start();
第二种方式的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装。
- 避免了java单继承的局限性。
-
同步
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
同步的前提:同步中必须有多个线程并使用同一个锁。
问题案例
1 class TicketSouce implements Runnable 2 { 3 //票的总数 4 private int ticket=10; 5 public void run() 6 { 7 for(int i=1;i<50;i++) //每个窗口能卖49张票 8 { 9 if(ticket>0) 10 { 11 //休眠1s秒中,为了使效果更明显,否则可能出不了效果 12 try { 13 Thread.sleep(1000); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 System.out.println(Thread.currentThread().getName()+"号窗口卖出"+this.ticket--+"号票"); 18 } 19 } 20 } 21 } 22 public class Test { 23 public static void main(String args[]) 24 { 25 TicketSouce mt=new TicketSouce(); 26 //基于火车票创建三个窗口 27 new Thread(mt,"a").start(); 28 new Thread(mt,"b").start(); 29 new Thread(mt,"c").start(); 30 } 31 32 }
a号窗口卖出9号票
b号窗口卖出10号票
c号窗口卖出8号票
a号窗口卖出7号票
c号窗口卖出7号票
b号窗口卖出6号票
c号窗口卖出5号票
a号窗口卖出4号票
b号窗口卖出3号票
c号窗口卖出2号票
b号窗口卖出1号票
a号窗口卖出0号票
c号窗口卖出-1号票
可以看到a号窗口和和c号窗口都卖出了7号票,并且a号和c号窗口分别卖出了0号和-1号票。造成这种情况的原因是:
1、a线程和b线程在ticket=7的时候,a线程取出7号票以后,ticket还没来的及减1,b线程就取出了ticket,此时ticket还等于7;
2、在ticket=1时,b线程取出了1号票,ticket还没来的及减1,a、c线程就先后进入了if判断语句,这时ticket减1了,那么当a、c线程取票的时候就取到了0号和-1号票。
1 // 两个储户,每个都到银行存钱,每次存100,共存3次 2 class Bank{ 3 private int sum; 4 void add(int num){ 5 sum = sum + num; 6 7 //休眠1s秒中,为了使效果更明显,否则可能出不了效果 8 try{ 9 Thread.sleep(1000); 10 }catch(InterruptedException i){} 11 12 System.out.println("sum="+sum); 13 } 14 } 15 16 class Cus extends Bank implements Runnable{ 17 18 public void run(){ 19 20 for(int i=0;i<3;i++){ 21 add(100); 22 } 23 } 24 } 25 26 public class test { 27 28 public static void main(String[] args) { 29 Cus c = new Cus(); 30 Thread t1 = new Thread(c); 31 Thread t2 = new Thread(c); //两个进程 32 33 t1.start(); 34 //t1.start(); //一个线程不能开启两次,会抛出无效线程状态异常 35 t2.start(); 36 37 } 38 }
会出现输出值相同的情况,原因是这段代码里的add()方法里没有限制进程进入,(可能会出现当进程0进入时sum变成100,进程0还没打印结果就进入休眠状态,此时进程1进入此方法中,sum值变成200,200ms后进程0唤醒,结果进程0和进程1均输出sum=200的情况),当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。为了解决这种线程安全问题,需要用到同步。
线程安全问题产生的原因:
- 多个线程在操作共享的数据。
- 操作共享数据的线程代码有多条。
解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
-
形式一:同步代码块
1 Synchronized(对象) 2 { 3 需要被同步的代码 ; 4 }
问题出在哪就把同步代码块加在哪,不用把run()方法里面的所有内容加进去。
1 // 两个储户,每个都到银行存钱,每次存100,共存3次 2 class Bank{ 3 private int sum; 4 Object obj = new Object(); //只生成一个obj对象当做同步锁,不要放在add()方法里,否则调用一次就会生成一个obj对象 5 void add(int num){ 6 7 synchronized(obj){ //问题出在哪段代码就把在那一段封装到synchronized块中; 8 sum = sum + num; 9 10 try{ 11 Thread.sleep(200); 12 }catch(InterruptedException i){} 13 14 System.out.println("sum="+sum);} 15 } 16 } 17 18 class Cus extends Bank implements Runnable{ 19 20 public void run(){ 21 22 for(int i=0;i<3;i++){ 23 add(100); 24 } 25 } 26 } 27 28 public class test { 29 30 public static void main(String[] args) { 31 Cus c = new Cus(); 32 Thread t1 = new Thread(c); 33 Thread t2 = new Thread(c); //两个进程 34 35 t1.start(); 36 t2.start(); 37 38 } 39 40 }
- 形式二:同步函数
即有synchronized关键字修饰的方法。
1 public synchronized void show(){}
// 验证同步函数用的是this锁 /* 函数需要被对象调用。那么函数都有一个所属对象调用,就是this 所以同步函数使用的锁是this 通过该程序进行验证 使用两个线程来卖票 一个线程在同步代码块中 一个线程在同步函数中 都在执行卖票操作 */ class Ticket implements Runnable { private int tick = 1000; //Object obj = new Object(); boolean flag = true; public void run() { if(flag)//第一个进入的线程实行下面代码 { while(true) { //synchronized(obj)//由于同步函数的是以this对象为锁的 此处如果使用obj对象作为锁 //则不能实现同步 输出的数据会出现错误 程序的安全性不能得到保证 synchronized(this)//和下面的show函数使用同样的锁 可以保证同步 { if(tick>0) { try{Thread.sleep(40);}catch(Exception e){} System.out.println(Thread.currentThread().getName() +" .....code..."+ tick--); } } } }//第二个进入的代码实行下面代码 else while(true) show();//this.show(); } public synchronized void show()//同步函数 以this为锁 { if(tick>0) { try{Thread.sleep(40);}catch(Exception e){} System.out.println(Thread.currentThread().getName() +" .....show..."+ tick--); } } } class ThisLockDemo { public static void main(String []args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start();//开启第一个线程 但不一定马上执行 t.flag = false;//改变标志 try{Thread.sleep(40);}catch(Exception e){}//让主线程睡眠40毫秒 保证第一个线程先开始运行 且标志位改变 t2.start(); } }
点击查看Synchronized 静态方法和非静态方法的异同

浙公网安备 33010602011771号