在java中,有三种创建线程的方式:
下面主要说一下前两种方法.
继承Thread类必须重写run()方法,在run()方法中定义线程体,该方法是新线程的入口点.只有通过调用start()方法才能开启这个线程,如果直接调用run()方法并不能开启线程.
示例1:
public class ThreadDemo01 extends Thread{ //多线程的线程体 @Override public void run() { for(int i=1;i<=20;i++){ System.out.println("一遍吃饭..."); } } public static void main(String[] args) { //开启多线程 创建线程 ThreadDemo01 th=new ThreadDemo01(); //开启线程 th.start(); //th.run(); 注意:这是方法的调用,不是多线程的开启 for(int i=1;i<=20;i++){ System.out.println("一遍说话..."); } } } 复制代码
在执行th.start()之后,就会开启这个线程,这个线程中的语句就是写在run()方法中的语句.
开启新线程之后就像有了两辆车在两个车道上跑了起来,互不影响.
如果没有用线程写这段代码的话,只能把run()方法中的代码排在主方法的for循环之后,就好比两个人过一条窄胡同,只能一个人跟着一个人走.
这样写我们就可以实现一遍吃饭一遍说话
通过实现Runnable接口创建线程可以避免单继承的局限性,且可以实现资源共享.
示例2:
public class ThreadDemo02 implements Runnable{ //定义线程体的方法,当被调用的时候,会逐行执行里面的代码 @Override public void run() { for(int i=1;i<=100;i++){ System.out.println("一边跑步..."); } } public static void main(String[] args) { ThreadDemo02 th=new ThreadDemo02(); //开启线程//创建线程 Thread t=new Thread(th); //因为开启线程的方法在Thread类中,Thread做为代理类出现 t.start(); for(int i=1;i<=100;i++){ System.out.println("一边听歌..."); } } } 复制代码
因为在Runnable接口中没有开启线程的方法,所以需要用Thread类作为代理类,调用start()方法,开启线程.
示例3:
public class BuyTikets implements Runnable{ //车票 int tikets=20; @Override public void run() { //循环买票 while(true){ if(tikets<=0){ break; } try { Thread.sleep(200); //线程睡眠100ms } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在购买第"+tikets--+"张票"); } } public static void main(String[] args) { BuyTikets bt=new BuyTikets(); //开启三个线程 new Thread(bt,"张三").start(); new Thread(bt,"李四").start(); new Thread(bt,"王五").start(); } } 复制代码
程序运行结果为:
张三正在购买第20张票 李四正在购买第19张票 王五正在购买第18张票 王五正在购买第17张票 李四正在购买第16张票 张三正在购买第15张票 王五正在购买第14张票 李四正在购买第13张票 张三正在购买第13张票 王五正在购买第12张票 张三正在购买第11张票 李四正在购买第11张票 王五正在购买第10张票 李四正在购买第9张票 张三正在购买第9张票 王五正在购买第8张票 李四正在购买第7张票 张三正在购买第7张票 王五正在购买第6张票 张三正在购买第5张票 李四正在购买第4张票 王五正在购买第3张票 张三正在购买第2张票 李四正在购买第1张票 王五正在购买第0张票 张三正在购买第-1张票 复制代码
可以看到三个人买20张票.一个线程代表一个人.20张票由这三个人共同分享.但是这个程序还是有些问题,竟然有人可以买到第0张票,第-1张票.还有两个人买到同一张票.
线程是一个动态执行的过程,它也有一个从产生到死亡的过程.
各个状态的转换图如下:
进入就绪状态的方式:
进入阻塞状态的方式:
进入终止状态的方式:
作用:
但是通过sleep()方法进入休眠的线程并不会释放拥有的对象资源,只会让出CPU资源
通过sleep()方法模拟一下倒计时
示例4:
public class Countdown implements Runnable{ public static void main(String[] args) { new Thread(new Countdown()).start(); } @Override public void run() { for(int i=10;i>=0;i--){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(i==0){ System.out.println("过年好..."); break; } System.out.println(i); } } } 复制代码
程序运行结果为:
10 9 8 7 6 5 4 3 2 1 过年好... 复制代码
当一个线程调用yield()方法,暂停当前正在执行的线程对象,并执行其他线程.
示例5:
public class YieldDemo implements Runnable{ public static void main(String[] args) { new Thread(new YieldDemo(),"A").start(); new Thread(new YieldDemo(),"B").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()+"start..."); Thread.yield(); //静态方法 System.out.println(Thread.currentThread().getName()+"end..."); } } 复制代码
程序运行结果为:
A:start... B:start... A:end... B:end... 复制代码
示例6:
public class JoinDemo { public static void main(String[] args) { new Thread(new Father()).start(); } } class Father implements Runnable{ @Override public void run() { System.out.println("吃饺子没醋..."); System.out.println("给儿子钱,让儿子去买醋.."); Thread th=new Thread(new Son()); th.start(); try { th.join();//合并线程 } catch (InterruptedException e) { e.printStackTrace(); System.out.println("儿子丢了,赶紧去找儿子.."); } System.out.println("饺子蘸醋~"); } } class Son implements Runnable{ @Override public void run() { System.out.println("接过钱,去买醋..."); System.out.println("路边有个电玩城,进去玩10s..."); for(int i=1;i<=10;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i+"s..."); } System.out.println("赶紧去买醋..."); System.out.println("把醋递给爸爸..."); } } 复制代码
程序运行结果为:
吃饺子没醋... 给儿子钱,让儿子去买醋.. 接过钱,去买醋... 路边有个电玩城,进去玩10s... 1s... 2s... 3s... 4s... 5s... 6s... 7s... 8s... 9s... 10s... 赶紧去买醋... 把醋递给爸爸... 饺子蘸醋~ 复制代码
上面写的模拟买火车票我们看到有的人买到第-1张票,有的是两个人买到同一张票.这种情况就是多个线程同时操作同一个资源的时候,才可能会出现线程安全问题.
为了处理这个安全问题,我们可以通过同步 synchronized 关键字控制线程安全
关键字synchronized可以同步方法,同步块.
同步方法可以同步静态方法以及成员方法
同步块的方法:
synchronized(类|this|资源){ 代码 } 复制代码
类: 类名.class 一个类的Class对象 一个类只有一个Class对象
同步的范围太大,效率低.
同步的范围太小,锁不住.
使用关键字synchronized锁的事物应该为不变的事物,变化的事物锁不住.
重写模拟买票:
示例7:
public class BuyTikets implements Runnable { @Override public void run() { while (true) { synchronized (Tikets.class) { if (Tikets.tikets <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在购买第" + Tikets.tikets-- + "张票"); } } } public static void main(String[] args) { BuyTikets td = new BuyTikets(); new Thread(td, "张三").start(); new Thread(td, "李四").start(); new Thread(td, "王五").start(); } } class Tikets { static int tikets = 20; } 复制代码
程序运行结果为:
张三正在购买第20张票 李四正在购买第19张票 李四正在购买第18张票 李四正在购买第17张票 李四正在购买第16张票 李四正在购买第15张票 王五正在购买第14张票 李四正在购买第13张票 张三正在购买第12张票 李四正在购买第11张票 王五正在购买第10张票 李四正在购买第9张票 李四正在购买第8张票 张三正在购买第7张票 李四正在购买第6张票 王五正在购买第5张票 李四正在购买第4张票 李四正在购买第3张票 张三正在购买第2张票 李四正在购买第1张票 复制代码
这样就不会再出现买票出错的情况.