1.java中线程创建方式
在java中,有三种创建线程的方式:
- 继承Thread类,重写run()方法
- 实现Runnable接口,实现run()方法
- 实现Callable接口,并实现call()方法
下面主要说一下前两种方法.
1.通过继承Thread类来创建线程
继承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循环之后,就好比两个人过一条窄胡同,只能一个人跟着一个人走.
这样写我们就可以实现一遍吃饭一遍说话
2.通过实现Runnable接口来创建线程
通过实现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.通过多线程模拟买火车票
示例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张票.还有两个人买到同一张票.
2.线程状态
线程是一个动态执行的过程,它也有一个从产生到死亡的过程.
- 新生状态: new
- 就绪状态: runnable
- 运行状态: running
- 阻塞状态: blocked
- 执行完毕: dead
各个状态的转换图如下:
进入就绪状态的方式:
- start()方法
- 阻塞解除
- 线程切换,被切换的线程进入到就绪状态
- yield()----礼让进程
进入阻塞状态的方式:
- sleep()方法
- join()方法
- wait()方法
进入终止状态的方式:
- 正常执行完毕
- destroy() |stop() 已过时
- 通过标识手动判断
1.sleep()---线程休眠
作用:
- 模拟网络延迟
- 放大问题的可能性
但是通过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
过年好...
复制代码
2.yield()---高风亮节,礼让线程
当一个线程调用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...
复制代码
3.join()---合并线程,插队线程
示例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...
赶紧去买醋...
把醋递给爸爸...
饺子蘸醋~
复制代码
3.线程安全
上面写的模拟买火车票我们看到有的人买到第-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张票
复制代码
这样就不会再出现买票出错的情况.