1.背景
例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
*
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
2.Java解决方案:同步机制
在Java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
*
* synchronized(同步监视器){
* //需要被同步的代码/操作共享数据的代码
*
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
锁:是控制多个线程访问共享资源的一种方式,一般来说,一个锁能够防止多个线程同时访问共享资源。
* 要求:多个线程必须要共用同一把锁。
*
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
实现runnable接口的代码中使用synchronized同步代码块
package main.java.ThreadImplements; /** * @Author lx * @Description 实现runnable接口的代码中使用synchronized同步代码块 * @Date 9:11 2020/8/5 * @Version */ class windowsSyn implements Runnable{ private int ticket = 100; @Override public void run() { while (true) { //注意synchronized包含的代码块的范围不能少也不能多 synchronized (this) { //同步监视器(锁)可以使用this指代当前对象 WindowsIplementsSyn在此例中只创建了一个 if (ticket > 0) { try { Thread.sleep(50); //等待时间 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前窗口号" + Thread.currentThread().getName() + ":" + ticket); ticket--; } else { break; } } } } } public class WindowsIplementsSyn { public static void main(String[] args) { windowsSyn W1 = new windowsSyn(); //只创建了一个对象 //多个线程共用一个对象 Thread t1 = new Thread(W1); Thread t2 = new Thread(W1); Thread t3 = new Thread(W1); t1.start(); t2.start(); t3.start(); } }
继承Thread方式的代码中使用Synchronized同步代码块
package main.java.ThreadExtends; /** * @Author lx * @Description 继承Thread方式的代码中使用Synchronized同步代码块 * @Date 10:29 2020/8/5 * @Version */ class windows extends Thread{ private static int ticket = 100; //将ticket设为静态全局变量 只加载一次 @Override public void run() { while (true) { // synchronized (this) { //在这里不能用this 因为此例中创建了多个对象 每进循环一次都是不同的对象,线程安全问题依旧存在 synchronized (window.class) { //多个线程必须使用同一把锁 window.class 只会加载一次 if (ticket > 0) { System.out.println("当前窗口号:" + Thread.currentThread().getName() + "还剩票数:" + ticket); ticket--; } else break; } } } } public class WindowsExtendsSyn { public static void main(String[] args) { //创建了多个对象 windows w1 = new windows(); windows w2 = new windows(); windows w3 = new windows(); w1.start(); w2.start(); w3.start(); } }
方式二:同步方法
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
* 关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
1.实现Runnable接口方式的代码中使用Synchronized同步方法
package main.java.ThreadImplements; /** * @Author lx * @Description 实现Runnable接口方式的代码中使用Synchronized同步方法 * @Date 17:27 2020/8/5 * @Version */ class windows2 implements Runnable{ private int ticket = 100; @Override public void run() { while (true) { show(); } } private synchronized void show(){ if (ticket > 0) { try { Thread.sleep(50); //等待时间 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前窗口号" + Thread.currentThread().getName() + ":" + ticket); ticket--; } } } public class WindowImpMethod { public static void main(String[] args) { windows2 W1 = new windows2(); //只创建了一个对象 //多个线程共用一个对象 Thread t1 = new Thread(W1); Thread t2 = new Thread(W1); Thread t3 = new Thread(W1); t1.start(); t2.start(); t3.start(); } }
2.继承Thread方式的代码中使用Synchronized同步方法
package main.java.ThreadExtends; /** * @Author lx * @Description 继承Thread方式的代码中使用Synchronized同步方法 * @Date 15:36 2020/8/5 * @Version */ class windows1 extends Thread{ private static int ticket = 100; //将ticket设为静态全局变量 只加载一次 @Override public void run() { while (true) { show(); //调用当前类的静态show方法 } } private synchronized static void show(){ //静态的同步方法 同步监视器是:当前类本身 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前窗口号:" + Thread.currentThread().getName() + "还剩票数:" + ticket); ticket--; } } } public class WinExtendsMethod { public static void main(String[] args) { //创建了多个对象 windows1 w1 = new windows1(); windows1 w2 = new windows1(); windows1 w3 = new windows1(); w1.start(); w2.start(); w3.start(); } }