目录
1.使用【同步代码块】解决【实现Runnable接口】的线程安全问题
2.使用【同步代码块】解决【继承Thread类】的线程安全问题
3.使用【同步方法】解决【实现Runnable接口】的线程安全问题
4.使用【同步方法】解决【继承Thread类】的线程安全问题
1.线程的生命周期
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整生命周期中通常要经历如下的五种状态。
- 就绪:当一个Thread类或其子类对象被声明并创建时,新生的线程对象处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。
- 运行:当就绪的线程被调用并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。
- 死亡:线程完成了它的全部工作或线程被提前强制性的中止或出现异常导致结束。
2.线程的同步
- 多个线程执行的不确定性引起执行结果的不稳定。
- 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
- 在Java中,通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码。
2.共享数据:多个线程共同操作的变量。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求多个线程必须要共用同一把锁。
1.使用【同步代码块】解决【实现Runnable接口】的线程安全问题
- 在实现Runnable接口创建的多线程方式中,可以考虑使用this充当同步监视器。
/**
* 使用同步代码块来解决实现Runnable接口的线程安全问题
*/
class Window1 implements Runnable {
public int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {//使用当前对象为监视器
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.使用【同步代码块】解决【继承Thread类】的线程安全问题
- 在继承Thread类创建多线程的方式中,慎用this充当同步监视器。考虑使用当前类充当同步监视器。
/**
* 使用同步代码块来解决继承Thread类的线程安全问题
*/
class Window2 extends Thread {
public static int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (Window2.class) { //Window2.class 只会加载一次
if (ticket > 0) {
System.out.println(getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,不妨就把此方法声明成同步的。
- 同步方法仍然涉及到同步监视器,只是不需要显示声明。
- 非静态的同步方法,同步监视器是:this
- 非静态的同步方法,同步监视器是:当前类本身
3.使用【同步方法】解决【实现Runnable接口】的线程安全问题
/**
* 使用同步方法来解决实现Runnable接口的线程安全问题
*/
class Window3 implements Runnable {
public int ticket = 100;
@Override
public void run() {
while (ticket!=0) {
show();
}
}
private synchronized void show(){ //同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
4.使用【同步方法】解决【继承Thread类】的线程安全问题
/**
* 使用同步方法来解决继承Thread类的线程安全问题
*/
class Window4 extends Thread {
public static int ticket = 100;
@Override
public void run() {
while (ticket!=0) {
show();
}
}
private static synchronized void show(){ //同步监视器:Window4.class
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}