线程对象只能启动一个对象
示例代码
/*
* 演示 使用线程的注意事项
*
* */
package com.fox.test1;
public class Demo11_1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cat cat1 = new Cat();
//Dog dog1 = new Dog();
cat1.start();
cat1.start();
}
}
class Cat extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("11");
}
}
class Dog implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("22");
}
}
程序运行时报错如下:
程序运行时报错如下:
11
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Unknown Source)
at com.fox.test1.Demo11_1.main(Demo11_1.java:14)
先打出了11,再次启动线程时报错了.
/*
* 演示 使用线程的注意事项
*
* */
package com.fox.test1;
public class Demo11_1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cat cat1 = new Cat();
Dog dog1 = new Dog();
//cat1.start();
//cat1.start();
Thread t = new Thread(dog1);
t.start();
t.start();
}
}
class Cat extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("11");
}
}
class Dog implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("22");
}
}
上面的代码运行后报错:
22
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Unknown Source)
at com.fox.test1.De
正确的启动代码如下:
/*
* 演示 使用线程的注意事项
*
* */
package com.fox.test1;
public class Demo11_1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cat cat1 = new Cat();
Dog dog1 = new Dog();
//cat1.start();
//cat1.start();
Thread t1 = new Thread(dog1);
Thread t2 = new Thread(dog1);
t1.start();
t2.start();
}
}
class Cat extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("11");
}
}
class Dog implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("22");
}
}
结论:不管是继承Thread还是实现Runnable接口创建线程,它们的对象只能启动一次,否则有异常抛出。
线程安全问题
模拟机票售票系统,有三个售票点,在一天卖出2000张票,注意不是每个售票点都卖2000张票,而是三个售票点一共卖出2000张票。
/*
* 演示模拟售票系统
*
* */
package com.fox.test2;
public class Demo11_2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
TickWindow tw1 =new TickWindow();
TickWindow tw2 =new TickWindow();
TickWindow tw3 =new TickWindow();
Thread t1 = new Thread(tw1);
Thread t2 = new Thread(tw1);
Thread t3 = new Thread(tw1);
t1.start();
t2.start();
t3.start();
}
}
//定义售票窗口类
class TickWindow implements Runnable
{
private int sums = 2000;
@Override
public void run() {
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(sums>0)
{
System.out.println(Thread.currentThread().getName()+"正在卖出第"+sums+"张票");
sums--;
}else {
//售票结束
break;
}
}
}
}
运行结果:
Thread-1正在卖出第2000张票
Thread-2正在卖出第2000张票
Thread-0正在卖出第2000张票
Thread-2正在卖出第1997张票
Thread-0正在卖出第1997张票
Thread-1正在卖出第1997张票
Thread-1正在卖出第1994张票
Thread-2正在卖出第1994张票
Thread-0正在卖出第1994张票
Thread-2正在卖出第1991张票
问题:三个窗口卖出都是同一张票,如何解决线程安全问题,如何解决线程并发问题?
在上面的模拟机票售票系统中,极有可能出现同一张票被多个窗口售出的情况,出问题的代码就是:
if(sums>0){
System.out.println(Thread.currentThread().getName()+”正在卖出第”+sums+”张票”);(1)
sums–;(2)
}
假如现在num=1,a线程刚刚执行完语句(1),正要执行语句(2)时,b线程执行if(sums>0)因为这时sums还是1,所以b线程也会执行语句(1),这样就相当于一张票卖了两次,这样的多并发就给我们带来了麻烦。
解决问题的关键在于,保证容易出问题的代码的原子性,所谓的原子性就是指,当a线程在执行某段代码时别的线程必须等待,等a线程执行完问题代码后b线程才能执行这段代码。
Java代码中处理线程同步的方法非常简单,只需要在需要代码同步的代码段中,用synchronized(object){ 需要同步的代码段 }即可。
修改代码:
/*
* 演示模拟售票系统
* 解决线程同步的安全问题
* */
package com.fox.test2;
public class Demo11_2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
TickWindow tw1 =new TickWindow();
TickWindow tw2 =new TickWindow();
TickWindow tw3 =new TickWindow();
Thread t1 = new Thread(tw1);
Thread t2 = new Thread(tw1);
Thread t3 = new Thread(tw1);
t1.start();
t2.start();
t3.start();
}
}
//定义售票窗口类
class TickWindow implements Runnable
{
private int sums = 2000;
@Override
public void run() {
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(this){
if(sums>0)
{
System.out.println(Thread.currentThread().getName()+"正在卖出第"+sums+"张票");
sums--;
}else {
//售票结束
break;
}
}
}
}
}
运行结果:
Thread-0正在卖出第2000张票
Thread-1正在卖出第1999张票
Thread-2正在卖出第1998张票
Thread-0正在卖出第1997张票
Thread-1正在卖出第1996张票
Thread-2正在卖出第1995张票
Thread-0正在卖出第1994张票
Thread-1正在卖出第1993张票
Thread-2正在卖出第1992张票
Thread-0正在卖出第1991张票
Thread-1正在卖出第1990张票
Thread-2正在卖出第1989张票
上面的synchronized传入的对象是this,这里的对象可以是任意的对象。
/*
* 演示模拟售票系统
* 解决线程同步的安全问题
* */
package com.fox.test2;
public class Demo11_2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
TickWindow tw1 =new TickWindow();
TickWindow tw2 =new TickWindow();
TickWindow tw3 =new TickWindow();
Thread t1 = new Thread(tw1);
Thread t2 = new Thread(tw1);
Thread t3 = new Thread(tw1);
t1.start();
t2.start();
t3.start();
}
}
//定义售票窗口类
class TickWindow implements Runnable
{
private int sums = 2000;
Dog dog1 = new Dog();
@Override
public void run() {
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(dog1){
if(sums>0)
{
System.out.println(Thread.currentThread().getName()+"正在卖出第"+sums+"张票");
sums--;
}else {
//售票结束
break;
}
}
}
}
}
class Dog{
}
上面的代码中synchronized中的对象用的是一个普通的dog对象。也可以解决线程安全问题。
线程同步的深入理解
Java任意类型的对象都有一个标志位,该标志位有0,1两种状态。其起始状态为1,当某个线程执行了synchronized(object)语句后,object对象的标志位变为0状态,直到执行完整个synchronized语句内的代码块后,object对象的标志位恢复为1状态。
当另一个线程执行到同样的synchronized(object)语句时,先检查object对象的标志位是否为1,如果是0状态,就表明有其他的线程在执行synchronized(object)语句内的代码块,那么该线程就会被阻塞让出CPU资源,直到另外的线程执行完相同的同步代码块后将状态恢复为1,等待线程的阻塞状态取消,线程继续运行,同时该线程又会将object对象的标志位设置为0防止其他线程再次进入相同的同步代码块中。
对同步机制的解释:如果有多个线程因等待同一个对象锁而处于阻塞状态时,当该对象的标志位恢复到1状态时,只会有一个线程能够进入同步代码的执行,其他的线程仍然处于阻塞状态.
object可以是任意类型的对象。
对象的标志位用术语将就是对象锁。