Java中线程创建方式
在java中有三种创建线程的方式
1.继承Thread类,重写run()方法
2.实现Runnable接口,实现run()方法
3.实现Callable接口,并实现call()方法
接下来我来给大家介绍前两种方法
1.通过继承Thread类来创建线程
继承Thread类必须重写run()方法,在run()方法中定义线程体,该方法是新线程的入口处.只有通过调用start()方法才能开启这个线程,如果直接调用run()方法并不能开启线程.
public class ThreadDemo01 extends Thread{
public static void main(String[] args) {
ThreadDemo01 th=new ThreadDemo01();
//开启多线程
th.start();
//th.run() 注意:这是方法的调用 而不是多线程
for(int i=1;i<=20;i++){
System.out.println("我要喝水");
}
}
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.println("一遍吃饭...");
}
}
}
在执行th.start()之后,就会开启这个线程,这个线程中的语句就是写在run()方法中语句.
开启新线程之后就是就像两辆马车同时奔跑,互不影响
如果我们没有开启多线程,就相当于一辆马车先跑,跑到终点后另外一辆马车开炮.
2.通过实现Runnable接口来创建线程
通过实现Runnable接口来创建线程可以避免单继承的局限性,可以实现资源的共享
public class ThreadDemo01 implements Runnable{
public static void main(String[] args) {
ThreadDemo01 th=new ThreadDemo01();
Thread t=new Thread(th);//因为开启线程的方法在Thread类中,Thread做为代理类出现
//开启多线程
t.start();
//th.run() 注意:这是方法的调用 而不是多线程
for(int i=1;i<=20;i++){
System.out.println("我要喝水");
}
}
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.println("一遍吃饭...");
}
}
}
因为在Runnable接口中没有开启线程的方法,所以需要用Thread类作为代理类,调用start()方法,开启多线程
3.通过多线程模拟买火车票
public class BuyTest implements Runnable{
//车票
int tikets=20;
public static void main(String[] args) {
BuyTest bt=new BuyTest();
//开启三个线程
new Thread(bt,"张三").start();
new Thread(bt,"李四").start();
new Thread(bt,"王五").start();
}
@Override
public void run() {
//循环买票
while(true){
if(tikets<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+tikets--+"张票");
}
}
}
程序运行结果如下:
张三正在购买第20张票
王五正在购买第19张票
李四正在购买第18张票
王五正在购买第17张票
李四正在购买第16张票
张三正在购买第17张票
王五正在购买第15张票
李四正在购买第13张票
张三正在购买第14张票
李四正在购买第12张票
张三正在购买第12张票
王五正在购买第11张票
王五正在购买第10张票
李四正在购买第8张票
张三正在购买第9张票
张三正在购买第7张票
李四正在购买第6张票
王五正在购买第5张票
王五正在购买第4张票
李四正在购买第3张票
张三正在购买第3张票
王五正在购买第2张票
李四正在购买第0张票
张三正在购买第1张票
可以看到三个人买20张票,一个线程代表一个人,但是在买票过程中最后面出现了买0张的问题,这是由于线程的不安全问题引起的.
2.线程状态
线程是一个动态执行的过程,它也有一个从产生到死亡的过程
1.新生状态:new
2.就绪状态:runnable
3.运行状态:running
4.阻塞状态:blocked
5.执行完毕:dead
实际状态转换图如下:

进入就绪状态的方式:
1.start()方法
2.阻塞解除
3.线程切换,被切换的线程进入到就绪状态
4.yield()------礼让线程
进入阻塞状态的方式:
1.sleep()方法
2.join()方法
3.wait()方法
进入终止状态的方式:
1.正常执行
2.destroy()|stop()-----已经过时
3.通过标识手动判断
1.sleep()---线程休眠
作用:
1.模拟网络延迟
2.放大问题的可能性
通过sleep()方法进入休眠的线程只会让出CPU资源,并不会释放拥有对象的资源
下面通过sleep()方法模拟倒计时:
public class Demo01 extends Thread{
public static void main(String[] args) {
Demo01 demo01=new Demo01();
demo01.start();
}
public void run() {
for (int i = 10; i >=0; i--) {
try {
Thread.sleep(500);
} 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()---高风亮节,礼让线程
当一个线程调用yeild()方法,暂停当前正在执行的线程对象,并执行其它线程
public class Demo02 implements Runnable{
public static void main(String[] args) {
new Thread(new Demo02(),"A:").start();
new Thread(new Demo02(),"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()---合并线程,插队线程
例子:
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("路上遇到美女,跟其搭讪十秒...");
for(int i=1;i<=10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i+"s....");
}
System.out.println("赶紧去买纸");
System.out.println("把纸递给爸爸..");
}
}
程序运行的结果为:
上厕所没纸
打电话,让儿子带纸回来
接到电话,去买纸...
路上遇到美女,跟其搭讪十秒...
1s....
2s....
3s....
4s....
5s....
6s....
7s....
8s....
9s....
10s....
赶紧去买纸
把纸递给爸爸..
老爸提裤子,出来了
3.线程安全
上面写的模拟买火车票我们看到还会有人买到第零张票,为了解决出现这样的线程不安全问题.
我们需要用到同步synchronized关键字去控制线程安全
关键字synchronized可以同步方法,同步块.
同步方法可以同步静态方法及其成员方法
同步块方法
synchronized(类|this|资源){
代码
}
类:类名.class 一个类的class对象 一个类只有一个class对象
同步的范围太大,效率低.
同步的范围太小,锁不住.
使用关键字synchronized锁的事物应该为不变的事物,变化的事物锁不住
重写模拟买票
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;
}