例子:创建三个窗口买票,总票数100张,使用Runnable接口的方式
-
1.问题:卖票过程中,出现了重票、错票–>出现了线程的安全问题
-
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
-
3.如何解决:当一个线程a在操作ticket时,其他线程不能参与进来。直到线程a操作完成ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变
-
4.在java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
} -
说明:1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
要求:多个线程必须要共用一把锁。
方式二:同步方法
如果操作共享数据的代码完成的声明在一个方法中,我们不妨将此方法声明为同步的
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
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("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
方式一:使用同步代码块解决继承Thread类的方式的线程安全问题
- 例子:创建三个窗口买票,总票数为100张,使用继承Thread类的方式
- 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当对象使用
class Window3 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
// synchronized (Window3.class){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
方式二:使用同步方法解决实现Runnable接口的线程安全问题
- 关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需要显示的声明。
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
class Window4 implements Runnable{
private int ticket = 100;
@Override
// public synchronized void run() {//一个窗口买票
public void run() {
while (true){
show();
}
}
private synchronized void show() {//同步监视器:this
// synchronized (this){
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 w = new Window4();
Thread t01 = new Thread(w);
Thread t02 = new Thread(w);
Thread t03 = new Thread(w);
t01.setName("窗口一");
t02.setName("窗口二");
t03.setName("窗口三");
t01.start();
t02.start();
t03.start();
}
}
使用同步方法处理继承Thread类的方式中线程安全问题
class Window5 extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
// private synchronized void show(){//同步监视器:t1, t2, t3
private static synchronized void show(){//同步监视器:Window5
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest5 {
public static void main(String[] args) {
Window5 t1 = new Window5();
Window5 t2 = new Window5();
Window5 t3 = new Window5();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
方式三:lock锁 —jdk5.0新增
- 1.面试题:synchronized与lock异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(Lock()) - 2.优先使用顺序
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) ->同步方法(在方法体之外)
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定方法lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为" + ticket);
ticket--;
} else {
break;
}
}finally {
//3.调用解锁方法:unlock
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
演示线程的死锁问题
- 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
- 2.说明:
①出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
②使用同步时,要避免死锁。
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
死锁演示
class A{
public synchronized void foo(B b){//同步监视器:A类的对象:a
System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了A实例的foo方法");//①
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:" + Thread.currentThread().getName() + "企图调用B实例的last方法");//③
b.last();
}
public synchronized void last(){//同步监视器:A类的对象:a
System.out.println("进入了A类的last方法内部");
}
}
class B{
public synchronized void bar(A a){//同步监视器:b
System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了B实例的bar方法");//②
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:" + Thread.currentThread().getName() + "企图调用A实例的last方法");//④
a.last();
}
public synchronized void last(){//同步监视器:b
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock implements Runnable{
A a = new A();
B b = new B();
public void init(){
Thread.currentThread().setName("主线程 ");
//调用a对象的foo方法
a.foo(b);
System.out.println("进入主线程之后");
}
@Override
public void run() {
Thread.currentThread().setName("副线程 ");
//调用b对象的bar方法
b.bar(a);
System.out.println("进入副线程之后");
}
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(deadLock).start();
deadLock.init();
}
}
练习:银行有一个账户
- 有两个储户分别向同一个账户存3000元,每次存1000,存三次。每次存完打印账户余额
- 分析:
- 1.是否是多线程问题?是!两个储户线程
- 2.是否共享数据 是!账户
- 3.是否有线程安全问题?是!
- 4.需要考虑如何解决线程安全问题? 同步问题:有三种方式
方式一:继承Thread类
class Account1{
private double balance;
public Account1(double balance){
this.balance = balance;
}
//存钱
public synchronized void deposit(double amt){
if(amt > 0){
balance += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存钱成功。余额为:" + balance);
}
}
}
class Customer1 extends Thread{
private Account1 acct;
public Customer1(Account1 acct){
this.acct = acct;
}
@Override
public void run() {
for(int i = 0; i < 3; i ++){
acct.deposit(1000);
}
}
}
public class AccountTest1 {
public static void main(String[] args) {
Account1 acct = new Account1(0);
Customer1 c1 = new Customer1(acct);
Customer1 c2 = new Customer1(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
方式二:实现Runnable接口
class Account2{
private double balance;
public Account2(double balance){
this.balance = balance;
}
public synchronized void deposit(double amt){
if(amt > 0){
balance += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存钱成功。余额为:" + balance);
}
}
}
class Customer2 implements Runnable{
private Account2 acct;
public Customer2(Account2 acct){
this.acct = acct;
}
@Override
public void run() {
for(int i = 0; i < 3; i ++){
acct.deposit(1000);
}
}
}
public class AccountTest2 {
public static void main(String[] args) {
Account2 acct = new Account2(0);
Customer2 cus = new Customer2(acct);
Thread t1 = new Thread(cus);
Thread t2 = new Thread(cus);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
方式三:lock锁
class Account3{
private double balance;
private ReentrantLock lock = new ReentrantLock();
public Account3(double balance){
this.balance = balance;
}
public void deposit(double amt) {
try {
lock.lock();
if (amt > 0) {
balance += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存款成功。余额为:" + balance);
}
}finally {
lock.unlock();
}
}
}
class Customer3 implements Runnable{
private Account3 acct;
public Customer3(Account3 acct){
this.acct = acct;
}
@Override
public void run() {
for(int i = 0; i < 3; i ++) {
acct.deposit(1000);
}
}
}
public class AccountTest3 {
public static void main(String[] args) {
Account3 acct = new Account3(0);
Customer3 cus = new Customer3(acct);
Thread t1 = new Thread(cus);
Thread t2 = new Thread(cus);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
练习:使用线程安全完成懒汉式单例模式
public class BankTest {
}
//懒汉式
class Bank{
private Bank(){}
private static Bank instance = null;
// public static synchronized Bank getInstance(){
public static Bank getInstance() {
//方式一:效率稍差
// synchronized (Bank.class) {
// if (instance == null) {
//
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率稍高
if(instance == null){
synchronized (Bank.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}