线程同步机制
三大不安全案例
同步方法与同步代码块
synchronized(this)同步方法与同步代码块解析
修改三大不安全案例:
JUC并发包的测试JUC安全类型的集合
lock锁和死锁
转链接:多线程详解
线程同步机制
并发:同一个对象被多个线程同时操作
线程同步:多个线程访问同一个对象,并且某些线程还想修改这个对象时,线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成队列,等待前一个使用完毕,下一个线程再使用
队列和锁:队列和锁保证线程同步的安全性
synchronized锁存在问题(高并发铺垫):
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,枷锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程放锁会导致有点急倒置,引起性能问题
三大不安全案例
不安全买票:
public class UnsafeBuyTicket implements Runnable {
public static void main(String[] args) {
UnsafeBuyTicket unsafeBuyTicket = new UnsafeBuyTicket();
Thread t1 = new Thread(unsafeBuyTicket,"你们");
Thread t2 = new Thread(unsafeBuyTicket,"我");
Thread t3 = new Thread(unsafeBuyTicket,"黄牛");
t1.start();
t2.start();
t3.start();
}
private int ticketnums=10;
boolean flag=true;//标识位,标识位是什么,看线程的停止
@Override
public void run() {
//买票
while (flag) {
//判断是否有票
if (ticketnums<=0) {
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到"+ticketnums--);
}
}
}
不安全取钱:
//两个人去取钱,两个账户
public class UnsafeBank {
public static void main(String[] args) {
Account account= new Account(100,"结婚基金");
new Drawing(account,50,"你").start();
new Drawing(account,100,"你女朋友").start();
}
}
class Account{
int money;//余额
String name;//账户
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread{
Account account;
int drawmoney;
int nowmoney;//现在手里有多少钱
public Drawing(Account account,int drawmoney,String name){
super(name);
this.drawmoney=drawmoney;
this.account=account;
}
//取钱
@Override
public void run() {
if (account.money-drawmoney<0) {
System.out.println(Thread.currentThread().getName()+"钱不够取不了");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-取得钱
account.money=account.money-drawmoney;
//手里的钱
nowmoney=nowmoney+drawmoney;
System.out.println(account.name+"余额为"+account.money);
System.out.println(Thread.currentThread().getName()+"手里的钱"+nowmoney);
}
}
不安全列表(不懂lambda表达式看目录链接,多线程详解):
import java.util.ArrayList;
import java.util.List;
//不安全的原因,一样的,比如两个线程,同一个瞬间操作了一个位置,覆盖掉,元素就会少
public class UnsafeList {
public static void main(String[] args) {
List<String>list=new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,就是synchronized关键字,他包括方法和块:
同步方法:public snchronized void method(int args){}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,指导该方法返回时才释放锁,后面的被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法声明为synchronized将会影响效率
同步代码块:synchronized(Obj){}
Obj称之为同步监视器,可以是任何对象,同步方法中不需要Obj,因为同步方法的同步监视器就是this,或者class(反射)
synchronized(this)同步代码块
规则:
- 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
- 二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
- 三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
- 四、当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
- 五、以上规则对其它对象锁同样适用.
下面举个简单的例子:
同步代码块
public void run() {
while (true) {
synchronized (this) {//同步代码块,在方法中其中this也可以改成“ ”,空字符串形势
if (num>0) {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"票数"+num--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
同步方法
private synchronized void cell(){//定义方法后在重写的run方法中进行调用
if (num>0) {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"票数"+num--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
完整的同步代码块代码
public class synchronizedpractice implements Runnable {
int num =20;
public static void main(String[] args) {
synchronizedpractice sy=new synchronizedpractice();
Thread t1=new Thread(sy,"线程1");
Thread t2=new Thread(sy,"线程2");
Thread t3=new Thread(sy,"线程3");
t1.start();
t2.start();
t3.start();
}
public void run() {
while (true) {
synchronized (this) {
if (num>0) {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"票数"+num--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
完整的同步方法代码
public class synchronizedpractice implements Runnable {
int num =20;
public static void main(String[] args) {
synchronizedpractice sy=new synchronizedpractice();
Thread t1=new Thread(sy,"线程1");
Thread t2=new Thread(sy,"线程2");
Thread t3=new Thread(sy,"线程3");
t1.start();
t2.start();
t3.start();
}
private synchronized void cell(){//构造私有
if (num>0) {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"票数"+num--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
while (true) {
this.cell();//这个地方用的this指调用本类的被隐藏的方法
}
}
}
修改三大不安全案例:
买票安全:
class UnsafeBuyTicket implements Runnable{
public static void main(String[] args) {
UnsafeBuyTicket unsafeBuyTicket = new UnsafeBuyTicket();
new Thread(unsafeBuyTicket,"自己").start();
new Thread(unsafeBuyTicket,"你").start();
new Thread(unsafeBuyTicket,"黄牛").start();
}
int TicketNuns=10;
boolean flag=true;
//买票
@Override
public void run() {
while (flag) {
Buy();
}
}
//同步方法,锁的是this(UnsafeBuyTicket)
private synchronized void Buy(){
if (TicketNuns<=0) {
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了第"+TicketNuns--+"张票");
}
}
取钱安全:
//两个人去取钱,两个账户
public class UnsafeBank {
public static void main(String[] args) {
Account account= new Account(100,"结婚基金");
new Drawing(account,50,"你").start();
new Drawing(account,100,"你女朋友").start();
}
}
class Account{
int money;//余额
String name;//账户
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread{
Account account;
int drawmoney;
int nowmoney;//现在手里有多少钱
public Drawing(Account account,int drawmoney,String name){
super(name);
this.drawmoney=drawmoney;
this.account=account;
}
//取钱
@Override
public void run() {
//同步代码块,要锁账户,默认是锁本身哟,
// 本身是银行哟,就是this哟,所以这里要用代码块
//锁的就是变化的量,需要增删改查的对象
synchronized (account){
if (account.money-drawmoney<0) {
System.out.println(Thread.currentThread().getName()+"钱不够取不了");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-取得钱
account.money=account.money-drawmoney;
//手里的钱
nowmoney=nowmoney+drawmoney;
System.out.println(account.name+"余额为"+account.money);
System.out.println(Thread.currentThread().getName()+"手里的钱"+nowmoney);
}
}
}
集合安全:
import java.util.ArrayList;
import java.util.List;
//不安全的原因,一样的,比如两个线程,同一个瞬间操作了一个位置,覆盖掉,元素就会少
public class UnsafeList {
public static void main(String[] args) {
List<String>list=new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
JUC并发包的测试JUC安全类型的集合
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJuc {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
});
}
System.out.println(list.size());
}
}
死锁:
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的场景,某个同步块同时拥有“两个以上对象的锁”时,就可能会发生死锁的问题
//一句话,相互想要对方的东西,坚持着,谁也不给
public class DeadLock {
public static void main(String[] args) {
Makeup g1=new Makeup(0,"灰姑娘");
Makeup g2=new Makeup(2,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有一份,用static是因为这样两个线程才是拿的同一个口红和镜子
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice;//选择
String girlName;//使用化妆的人
public Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
try {
makeup();//方法放进来
} catch (InterruptedException e) {
e.printStackTrace();
}
//化妆
}
//化妆,互相持有对方的锁,需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁,想要镜子
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror) {//一秒钟后想获得镜子
System.out.println(this.girlName + "获得镜子的锁");
}
}
} else {synchronized (mirror){//拿着镜子的锁,想要口红
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
结果:僵持
发生死锁就是因为抱着对方的锁不放,这个时候把他拿出来,就可以解决问题:
//一句话,相互想要对方的东西,坚持着,谁也不给
public class DeadLock {
public static void main(String[] args) {
Makeup g1=new Makeup(0,"灰姑娘");
Makeup g2=new Makeup(2,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice;//选择
String girlName;//使用化妆的人
public Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
try {
makeup();//方法放进来
} catch (InterruptedException e) {
e.printStackTrace();
}
//化妆
}
//化妆,互相持有对方的锁,需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁,想要镜子
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) {//一秒钟后想获得镜子
System.out.println(this.girlName + "获得镜子的锁");
}
} else {synchronized (mirror){//拿着镜子的锁,想要口红
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(2000);
}
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
结果:
避免死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而被阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
lock锁
强大的线程同步机制,通过显式定义同步锁对象来实现同步(synchronized是隐式的),关键词lock。
也是属于juc并发的领域,java.util.concurrent.locks.Lock接口。
ReentrantLock类实现了Lock(可重入锁)。
Lock锁只有代码块锁,synchronized有代码块和方法锁
使用lock锁,JVM将花费少量时间来调度线程,性能更好和扩展性
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int TicketNums=10;
//定义lock锁
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();//枷锁
if (TicketNums<0) {
break;
}
System.out.println(Thread.currentThread().getName()+"拿了第"+TicketNums--+"票");
}finally {
lock.unlock();//解锁,如果同步代码快有异常,要将unlock放在finally语句块
}
}
}
}
更多安全锁,请看单例模式与锁,关联性问题哟
本文记录java复习过程,喜欢点击关注,会持续更新