线程同步
Posted 小布丁value
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程同步相关的知识,希望对你有一定的参考价值。
一,线程同步
- 问题的提出
例 题
模拟火车站售票程序,开启三个窗口售票。
public class Rwindow implements Runnable {
private int ticket =3;
@Override
public void run() {
while(ticket>0){
System.out.println("买票号"+ ticket);
ticket--;
}
}
}
class test1{
public static void main(String[] args) {
Rwindow t = new Rwindow();
Thread thread = new Thread(t);
Thread thread1 = new Thread(t);
Thread thread2= new Thread(t);
thread.setName("窗口1");
thread.setName("窗2");
thread.setName("窗3");
thread.start();
thread1.start();
thread2.start();
}
}
理想状态
极端状态 1 2 3 都sleep了进入阻塞状态
安全问题
由此就导致多线程出现了安全问题
.问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。
1.synchronized同步代码块:
synchronized(对象){
//需要被同步的代码块
}`
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:
多个线程共同操作的变量。比如: ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
这一点非常重要
Runnable()
Runnable方式可以
把synchronized里的关键字换成this
因为只有一个对象,
但是继承不可以
,因为有三个对象
synchronized里的关键字也可以换成 当前类.class
反射,类只会被加载一次,所有产生的Class对象唯一
继承
一定要加static 哦
2.synchronized同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步(synchronized)方法
注意:
1.同步方法仍然涉及到同步监视器,只是不需要显示的声明
2.非静态的同步方法,同步监视器 this
静态同步监视器,当前类本身
public synchronized void show(String name){
}
Runnable()
继承
懒汉单例模式变成线程安全的
懒汉单例模式:
class Bank{
private Bank(){}
private static Bank instance =null;
public static Bank getInstance() {
//方式一:效率较差
if (instance == null) {
instance = new Bank();
}
return instance;
}
}
方式一:
class Bank{
private Bank(){}
private static Bank instance =null;
public static Bank getInstance() {
//方式一:效率较差
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
return instance;
}
因为后面等待的数据就不用再判断等不等于null
直接返回instance 就好
方式二:
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
死锁
来看个例子:
在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
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();
}
}
Lock(锁)
- 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。
构造函数:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
注意:如果同步代码有异常,要将unlock()写入finally语句块
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
隐式锁,出了作用域自动释放
Lock手动挡,快 , synchronized 自动挡 - Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) ->同步方法
(在方法体之外)
实战
class Account{
private double balance;
private ReentrantLock lock = new ReentrantLock();
public double depisit(double money){
if (money > 0) {
balance += money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "余额" + balance);
return balance;
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct=acct;
}
@Override
public void run() {
for(int i=0;i<3;i++){
acct.depisit(1000);
}
}
}
class Test{
public static void main(String[] args) {
Account acct = new Account();
Customer t1= new Customer(acct);
Customer t2= new Customer(acct);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
同步代码块一睡准出错:线程不安全
三种方式都可以,博主这里用了最简单的
以上是关于线程同步的主要内容,如果未能解决你的问题,请参考以下文章