看了个视频,嗯,记录下加深下理解。。。java实现多线程的两种方式:继承thread类,这种方式的缺陷就是java的单继承导致的;还有一种方式就是实现Runnable接口,这种方式避免了thread方式由于java单继承带来的缺陷。需要知道的是线程启动后会抢占CPU资源,如果当前new了一个线程,线程就会进入创建的状态,然后我们启动这个线程,thread.start(),这个时候这个线程就会处在就绪状态,如果当前能获取到CPU资源,然后就会处在运行状态,如果这个时候执行了sleep方法或者一些阻塞事件,会导致线程处于阻塞状态,然后sleep到一定时间后阻塞解除,会继续排队到就绪状态,继续等待当前释放CPU释放资源,如果run方法执行完毕的话,那么线程终止,自动销毁。这也是为什么不加synchronized的话会出现乱序的情况。因为你虽然进入了线程队列,但是当前cpu资源不一定是被你获得了,会被后来居上。也就是说你可能是先start()了,但是不一定能先run()。
使用继承thread的方式进行模拟卖票
public class SoldTicketThread extends Thread {
private int ticket = 5;
private String name;
// 构造方法
public SoldTicketThread(String name) {
this.name = name;
}
public void run() {
while (ticket > 0) {
System.out.println(name + "卖出了一张票,剩余" + (--ticket) + "张票");
}
}
public static void main(String[] args) {
// 创建三个线程,模拟三个窗口卖票
SoldTicketThread wd1 = new SoldTicketThread("窗口一");
SoldTicketThread wd2 = new SoldTicketThread("窗口二");
SoldTicketThread wd3 = new SoldTicketThread("窗口三");
// 启动者三个线程,也就是窗口开始卖票
wd1.start();
wd2.start();
wd3.start();
}
}
运行结果:(这是因为我们创建了3个线程,每个线程都有5张票的资源,所以每个线程都卖自己的5张票,无法实现资源共享)
窗口二卖出了一张票,剩余4张票
窗口一卖出了一张票,剩余4张票
窗口一卖出了一张票,剩余3张票
窗口一卖出了一张票,剩余2张票
窗口一卖出了一张票,剩余1张票
窗口一卖出了一张票,剩余0张票
窗口二卖出了一张票,剩余3张票
窗口二卖出了一张票,剩余2张票
窗口二卖出了一张票,剩余1张票
窗口二卖出了一张票,剩余0张票
窗口三卖出了一张票,剩余4张票
窗口三卖出了一张票,剩余3张票
窗口三卖出了一张票,剩余2张票
窗口三卖出了一张票,剩余1张票
窗口三卖出了一张票,剩余0张票
综合上面的结果,我们只能创建一个线程,拿到这5张票的共享资源
public class SoldTicketThread implements Runnable {
private int ticket = 5;
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余" + (--ticket) + "张票");
}
}
public static void main(String[] args) {
// 创建一个实现了runnable接口的对象,拿到这5张票的资源
SoldTicketThread window = new SoldTicketThread();
//将这个资源类作为参数传递到3个线程中
Thread wd1=new Thread(window,"窗口一");
Thread wd2=new Thread(window,"窗口二");
Thread wd3=new Thread(window,"窗口三");
// 启动者三个线程,也就是窗口开始卖票
wd1.start();
wd2.start();
wd3.start();
}
}
运行结果:
窗口一卖出了一张票,剩余4张票
窗口一卖出了一张票,剩余2张票
窗口一卖出了一张票,剩余1张票
窗口二卖出了一张票,剩余3张票
窗口一卖出了一张票,剩余0张票
如果你非要用继承thread的方式的话。。。
public class SoldTicketThread extends Thread {
private int ticket = 5;
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余"
+ (--ticket) + "张票");
}
}
public static void main(String[] args) {
SoldTicketThread window = new SoldTicketThread();
Thread wd1 = new Thread(window, "窗口二");
Thread wd2 = new Thread(window, "窗口一");
Thread wd3 = new Thread(window, "窗口三");
wd1.start();
wd2.start();
wd3.start();
}
}
运行结果:窗口随机,卖票顺序随机
窗口二卖出了一张票,剩余4张票
窗口二卖出了一张票,剩余1张票
窗口二卖出了一张票,剩余0张票
窗口三卖出了一张票,剩余2张票
窗口一卖出了一张票,剩余3张票
好想让这个卖票有个顺序肿么破。。。,使用synchronized锁住卖票操作的整个内容,使一次只卖一次票。给我们的资源加把锁
采用实现Runnable接口实现:
public class SoldTicketThread implements Runnable {
private int ticket = 5;
private synchronized void sale() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余"+ (--ticket) + "张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
while (ticket > 0) {
sale();
// 这里要确保进入卖票环节前就把资源锁住
}
}
public static void main(String[] args) {
SoldTicketThread window = new SoldTicketThread();
Thread wd1 = new Thread(window, "窗口二");
Thread wd2 = new Thread(window, "窗口一");
Thread wd3 = new Thread(window, "窗口三");
wd1.start();
wd2.start();
wd3.start();
}
}
运行结果:(线程间(窗口)会进行抢占cpu资源,是随机的)
窗口三卖出了一张票,剩余4张票
窗口一卖出了一张票,剩余3张票
窗口二卖出了一张票,剩余2张票
窗口一卖出了一张票,剩余1张票
窗口一卖出了一张票,剩余0张票
继承Thread类
public class SoldTicketThread extends Thread {
private int ticket = 5;
public synchronized void sale() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余"
+ (--ticket) + "张票");
}
}
public void run() {
while (ticket > 0) {
sale();
}
}
public static void main(String[] args) {
SoldTicketThread window = new SoldTicketThread();
Thread wd1 = new Thread(window, "窗口二");
Thread wd2 = new Thread(window, "窗口一");
Thread wd3 = new Thread(window, "窗口三");
wd1.start();
wd2.start();
wd3.start();
}
}
运行结果:(窗口会进行抢占cpu资源,是随机的)
窗口三卖出了一张票,剩余4张票
窗口一卖出了一张票,剩余3张票
窗口一卖出了一张票,剩余2张票
窗口一卖出了一张票,剩余1张票
窗口一卖出了一张票,剩余0张票
总结:因为我们只是创建了一个拥有5张票的线程类的对象(有5张火车票),然后把这个对象作为参数传递给3个新创建的thread对象,所以共享了这5张火车票。然后我们再卖票操作加了synchronized操作,就可以实现按顺序卖票了。
需要记住的点是,我觉得初学者老师会觉得程序是从上到下执行的,很符合我们的逻辑,但是线程可以看做是并发操作,并不是说你先start了,你就会先run。
还要记录的点事守护线程的点。。。在Dos环境下,jstack可以查看当前运行java类的pid,然后输入 jstack -l pid可以查看到目前有什么守护线程。。。。