1.首先来说说创建线程的两种方式
一种方式是继承Thread类,并重写run()方法
public class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
//线程使用
MyThread mt = new MyThread(); //创建线程
mt.start(); //启动线程
实例:
public class ThreadDemo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程一"); // 第一种设置线程名称方法
myThread1.start();
MyThread myThread2 = new MyThread();
myThread2.setName("线程二"); // 第二种设置线程名称方法
myThread2.start();
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.getName() + " " + i);
}
}
}
运行结果为:
线程一 0
线程二 0
线程一 1
线程二 1
线程一 2
线程二 2
线程一 3
线程二 3
线程一 4
线程二 4
注意:每次运行结果顺序都不一样
另外一种方式是实现Runnable接口
public class MyThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
//线程使用
MyThread mt = new MyThread();
Thread thread = new Thread(mt); //创建线程
thread.start(); //启动线程
实例:
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable,"线程一");
thread1.start();
Thread thread2 = new Thread(myRunnable,"线程二");
thread2.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
运行结果为:
线程二 0
线程一 0
线程二 1
线程一 1
线程二 2
线程一 2
线程二 3
线程一 3
线程二 4
线程一 4
注意:这两种方法每次运行都是不一样的
2.两种方式创建线程比较
第一点:通过创建线程方式可以看出,一个是继承一个是实现接口,但是Java是只能继承一个父类,可以实现多个接口的一个特性,所以说采用Runnable方式可以避免Thread方式由于Java单继承带来的缺陷。
第二点:Runnable的代码可以被多个线程共享(Thread实例),适合于多个多个线程处理统一资源的情况。
举例说明:模拟卖票,假设还剩10张票,分别采用两种方式来创建线程模拟
1.先用继承Thread的方法
public class ThreadTicketDemo {
public static void main(String[] args) {
ThreadTicket thread1 = new ThreadTicket("窗口一");
thread1.start();
ThreadTicket thread2 = new ThreadTicket("窗口二");
thread2.start();
}
}
class ThreadTicket extends Thread{
private int num = 10;
public ThreadTicket(String name) {
super(name);
}
@Override
public void run() {
while (true) {
num --;
if (num == 0) {
System.out.println(this.getName() + "票已卖完");
break;
} else {
System.out.println(this.getName() + "卖出一张票,剩余" + num + "张票");
}
}
}
}
结果为:
窗口一卖出一张票,剩余9张票
窗口二卖出一张票,剩余9张票
窗口一卖出一张票,剩余8张票
窗口二卖出一张票,剩余8张票
窗口一卖出一张票,剩余7张票
窗口二卖出一张票,剩余7张票
窗口一卖出一张票,剩余6张票
窗口二卖出一张票,剩余6张票
窗口一卖出一张票,剩余5张票
窗口二卖出一张票,剩余5张票
窗口二卖出一张票,剩余4张票
窗口二卖出一张票,剩余3张票
窗口二卖出一张票,剩余2张票
窗口二卖出一张票,剩余1张票
窗口二票已卖完
窗口一卖出一张票,剩余4张票
窗口一卖出一张票,剩余3张票
窗口一卖出一张票,剩余2张票
窗口一卖出一张票,剩余1张票
窗口一票已卖完
可以看出共卖出了20张票,变成了每个线程都有10张票了
2.再用实现了Runnable接口的方法
public class RunnableTicketDemo {
public static void main(String[] args) {
RunnableTicket runnable = new RunnableTicket();
Thread thread1 = new Thread(runnable,"窗口一");
thread1.start();
Thread thread2 = new Thread(runnable,"窗口二");
thread2.start();
}
}
class RunnableTicket implements Runnable{
private int num = 10;
@Override
public void run() {
while (true) {
synchronized(this) {
try {
Thread.sleep(100); //更好的体现结果
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num > 0) {
num --;
if (num == 0) {
System.out.println("票已卖完");
} else {
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + num + "张票");
}
}
}
}
}
}
运行结果为:
窗口二卖出一张票,剩余9张票
窗口一卖出一张票,剩余8张票
窗口一卖出一张票,剩余7张票
窗口一卖出一张票,剩余6张票
窗口二卖出一张票,剩余5张票
窗口一卖出一张票,剩余4张票
窗口二卖出一张票,剩余3张票
窗口二卖出一张票,剩余2张票
窗口一卖出一张票,剩余1张票
票已卖完
在这个代码中我加入了synchronized(this) {}这一行代码,这个可以防止多个线程对一个数据的操作,如果不加会出现如下结果:
窗口二卖出一张票,剩余8张票
窗口二卖出一张票,剩余7张票
窗口一卖出一张票,剩余8张票
窗口二卖出一张票,剩余6张票
窗口一卖出一张票,剩余5张票
窗口二卖出一张票,剩余4张票
窗口一卖出一张票,剩余3张票
窗口二卖出一张票,剩余2张票
票已卖完
窗口一卖出一张票,剩余1张票
注意synchronized放置在while循环内部而不是外部,这两种结果会不一样的,解释如下:
(1)写在while外面的时候:比如你截图的这个代码功能应该是模拟售票的吧。比如有三个线程t1,t2,t3模拟1、2、3号窗口售票。如果你写在while外面的话,比如t2一旦拿到cpu执行权,并且判断synchronized(){}通过的话,就一直是t2窗口在卖票了,因为这时候t1和t3判断synchronized(){}是锁的都进不来,t2就一直在while循环,直到t2while循环完毕出来的时候,t1或者t3才能进去,这时候票已经卖完了。
说简单点就是,t1,t2,t3任意一个线程先拿到执行权的话就会一直是它在循环了。因为没有能停下来的语句,别的线程也执行不了,因为synchronized(){}是锁的。只有该线程循环完出来的时候,synchronized(){}才是打开的。这时候就不能满足t1,t2,t3随机进入while循环
(2)如果写在while里面就不同了,这三个线程不论是谁得到cpu执行权都会先进入while循环,然后判断synchronized(){}是否是锁的,如果不是就可以卖出一张票,如果是就等待别的线程先执行完。
(3)简单拿模拟窗口卖票来说,我们看到的执行效果的区别就是:
A:锁在while外面的时候,运行程序,不管有多少个窗口也总是一个窗口在卖票,从头卖到尾绝对永远都是先进入循环的那个线程会把所有票卖完
B:锁在while循环里面的时候,会是随机的一个窗口在卖,如果是三个线程的话,就应该是三个窗口都有在卖。
synchronized
首先synchronized就像一把锁,多个线程同时竞争synchronized代码块的资源,当一个线程先抢到这个资源时,就会上锁,别的线程就不能访问,只能等到当前线程执行完sychronized里面的代码才会释放锁,然后别的线程才可以竞争访问,接着又是上锁和释放锁的过程。
上面这种写法是用synchronized修饰代码块,下面我用synchronized来修饰方法,还是同样的例子:
public class RunnableTicketDemo1 {
public static void main(String[] args) {
RunnableTicket1 runnable = new RunnableTicket1();
Thread thread1 = new Thread(runnable,"窗口一");
thread1.start();
Thread thread2 = new Thread(runnable,"窗口二");
thread2.start();
}
}
class RunnableTicket1 implements Runnable{
private int num = 10;
// synchronized修饰方法
public synchronized void saleTicket() {
try {
Thread.sleep(100); //更好的体现结果
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num > 0) {
num --;
if (num == 0) {
System.out.println("票已卖完");
} else {
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + num + "张票");
}
}
}
@Override
public void run() {
while (true) {
saleTicket();
}
}
}
和上面的代码等价
最后一个例子来模拟取钱,一个是从ATM取钱,一个从柜台存折取钱同时取:
public class ThreadBankDemo {
public static void main(String[] args) {
Bank bank = new Bank();
BankThread p1 = new BankThread(bank);
p1.start();
BankThread p2 = new BankThread(bank);
p2.start();
}
}
class BankThread extends Thread{
private Bank bank = null;
public BankThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
System.out.println("取钱:" + bank.getMoney(400));
}
}
class Bank{
private int money = 500;
// 取钱的方法,返回取钱的数目
// 当一个线程去调用同步方法的时候,这个线程就获取了当前对象的锁
// 其他线程当调用同步方法的时候只能等待,因为无法获取对象的锁
//只有等第一个线程释放对象的锁方可进入
public synchronized int getMoney(int number) {
if (number < 0) {
return -1;
} else if (money < 0) {
return -2;
} else if (number - money > 0) {
return -3;
} else {
try {
Thread.sleep(100); //更好的体现结果
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.out.println("余额:" + money);
}
return number;
}
}
结果:
余额:100
取钱:400
取钱:-3