目录
以性能换安全
1.synchronized 同步
synchronized 同步,也叫做暗锁,自动释放锁。
锁得住的条件是:同一个对象(同一个资源)
如果synchronized 修饰方法,就是给这个方法加锁。这时候如果有个线程被JVM调度执行,只有等这个线程执行完毕以后,这个线程才会自动释放锁,其它的线程才有机会执行。
(1)不同的对象竞争同一个资源(锁得住)
两个线程对象t1和t2竞争同一个UserRunnable对象
如下面的代码创建了两个线程,run()方法被synchronized修饰,只有等一个线程执行完run()方法并自动释放锁,另一个线程才能执行:
package com.sync;
public class UserRunnable implements Runnable{
@Override
public synchronized void run() {
// TODO Auto-generated method stub
for(int i=0;i<=10;i++)
{
System.out.println(Thread.currentThread().getName()+",执行"+i);
}
}// 自动释放锁
}
package com.sync;
public class Test {
public static void main(String[] args) {
UserRunnable ur = new UserRunnable(); // 只有一个Runnable对象
Thread t1 = new Thread(ur);
Thread t2 = new Thread(ur);
t1.start();
t2.start();
}
}
运行结果:
(2)不同的对象竞争不同的资源(锁不住)
两个线程使用不同的UserThread
对象,锁的是不同的对象
两个线程使用不同的锁,没有竞争关系,所以无法实现同步机制。
package com.sync1;
public class UserThread extends Thread{
//synchronized是一个加锁操作
//synchronized首先是对同一个资源(同一个对象)的加锁
//锁得住的条件是:同一个对象
public synchronized void run()
{
for(int i=0;i<=10;i++)
{
System.out.println(Thread.currentThread().getName()+",执行"+i);
}
}
}
package com.sync1;
public class Test {
public static void main(String[] args) {
UserThread u1 = new UserThread();
UserThread u2 = new UserThread();
u1.start();
u2.start();
}
}
运行结果:
解决方法:
package com.sync1;
public class Test {
public static void main(String[] args) {
// UserThread u1 = new UserThread();
// UserThread u2 = new UserThread();
//
// u1.start();
// u2.start();
UserThread u1 = new UserThread();
Thread t1 = new Thread(u1);
Thread t2 = new Thread(u1);
t1.start();
t2.start();
}
}
(3)单例模式加锁
使用synchronized给getInstance()方法加锁后,一个线程进入后立刻锁住,对象new完后自动解锁,此时对象已经创建完成,别的线程进入不用重新创建对象,所以两个线程返回的地址一样。
package com.sync2;
public class User {
private static User u ;
private User()
{
}
public synchronized static User getInstance()
{
if(null == u)
{
System.out.println(Thread.currentThread().getName()+"创建对象");
u = new User();
}
return u;
}
}
package com.sync2;
public class UserThread1 extends Thread{
public void run()
{
User u1 = User.getInstance();
System.out.println(u1);
}
}
package com.sync2;
public class UserThread2 extends Thread{
public void run()
{
User u2 = User.getInstance();
System.out.println(u2);
}
}
package com.sync2;
public class Test {
public static void main(String[] args) {
UserThread1 u1 = new UserThread1();
UserThread2 u2 = new UserThread2();
u1.start();
u2.start();
}
}
运行结果:
synchronized 同步块
synchronized(),()里面一定是引用类型对象,必须是同一个对象。
对需要竞争的代码进行锁定,降低锁定的范围,优化性能。
下面是一个多线程银行账户操作模拟系统,包含:
- 1个银行账户(Bank)
- 3个支付平台线程(支付宝、微信、京东)
- 使用同步机制保证账户操作的线程安全
1. Bank 类(共享资源)
package com.sync3;
//银行类
public class Bank {
// 卡号
private String bankNumber = "";
// 账户的金额
private double money = 0.0;
public Bank(String bankNumber, double money) {
this.bankNumber = bankNumber;
this.money = money;
}
//操作银行账户的方法 synchronized 修饰方法,会给整个方法加锁,导致整个方法被锁定,导致锁定的范围过大
//synchronized 同步块,对需要竞争的代码进行锁定,降低锁定的范围,优化性能
public void operatorBank(double operatorMoney)
{
System.out.println("欢迎您到银行办理具体的业务");
synchronized(Bank.class)// ()里面一定是引用类型对象,必须是同一个对象
{
this.money += operatorMoney;
System.out.println(Thread.currentThread().getName()+",操作的金额是:"+operatorMoney
+",现在账户剩余的金额是:"+this.money);
}
System.out.println("谢谢您,欢迎下次光临");
}
}
2. 支付线程类
package com.sync3;
public class JindongThread extends Thread{
double opMoney;
Bank bank;
public JindongThread(String threadName,double opMoney,Bank bank)
{
super(threadName);
this.opMoney = opMoney;
this.bank = bank;
}
public void run()
{
this.bank.operatorBank(this.opMoney);
}
}
package com.sync3;
public class WeixinThread extends Thread{
double opMoney;
Bank bank;
public WeixinThread(String threadName,double opMoney,Bank bank)
{
super(threadName);
this.opMoney = opMoney;
this.bank = bank;
}
public void run()
{
this.bank.operatorBank(this.opMoney);
}
}
package com.sync3;
public class ZhifubaoThread extends Thread{
double opMoney;
Bank bank;
public ZhifubaoThread(String threadName,double opMoney,Bank bank)
{
super(threadName);
this.opMoney = opMoney;
this.bank = bank;
}
public void run()
{
this.bank.operatorBank(this.opMoney);
}
}
3.Test类
package com.sync3;
/**
* synchronized同步,就是加锁的操作,保证多线程竞争同一个资源时的安全。
*/
public class Test {
public static void main(String[] args) {
Bank bank = new Bank("10086", 1000.0);
ZhifubaoThread z = new ZhifubaoThread("支付宝", 300, bank);
WeixinThread w = new WeixinThread("微信", -400, bank);
JindongThread j = new JindongThread("京东", 600, bank);
z.start();
w.start();
j.start();
}
}
运行结果:
2.JUC并发包
JUC 是 java.util.concurrent 包及其子包(如 java.util.concurrent.atomic 和 java.util.concurrent.locks)的非官方但广为流传的缩写,全称是 Java Util Concurrent。它是 Java 标准库中为并发编程提供强大、高性能、线程安全的工具类的核心包。
明锁机制
公平锁 (Fair Lock):new ReentrantLock(true);
原则:遵循“先来后到”的公平原则。
行为:当锁被释放时,会优先分配给等待时间最长的线程。就像现实中排队一样,先来的先获得服务。
优点:所有线程都能得到执行机会,不会产生“饥饿”现象。
缺点:性能开销较大。因为需要维护一个有序队列来管理线程,上下文切换更频繁。
非公平锁 (Non-fair Lock):new ReentrantLock(false);
原则:允许“插队”。
行为:当锁被释放时,所有正在尝试获取锁的线程(包括刚来和已经等待的)都会去竞争,谁抢到就是谁的。如果没抢到,才会被加入到等待队列的末尾。
优点:吞吐量高,性能更好。减少了线程切换的开销,充分利用了CPU时间片。
缺点:可能导致某些线程长时间等待,永远拿不到锁(饥饿)。
package com.demo2;
import java.util.concurrent.locks.Lock;
public class Buy implements Runnable {
Lock lock;
private boolean flag = true;
private int sum = 10;
public Buy(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (flag) {
lock.lock(); // 明锁
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ",买到了票,是第" + this.sum-- + "张票");
if (this.sum <= 1) {
this.flag = false;
}
lock.unlock(); // 一定要手动释放锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.demo2;
//JUC并发包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
//明锁机制
//new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行
//new ReentrantLock(false); 是false是非公平锁,公非平的意思就是会有一个线程独占执行
Lock lock = new ReentrantLock(true);
Buy buy = new Buy(lock);
new Thread(buy,"张三线程").start();
new Thread(buy,"李四线程").start();
new Thread(buy,"王五线程").start();
}
}
公平锁运行结果:
非公平锁运行结果:
3.volatile关键字
volatile
是Java提供的一种轻量级的同步机制,用于确保变量的可见性和一致性。
可见性:
当一个线程修改了volatile变量时,新值会立即被刷新到主内存
其他线程读取该变量时,会强制从主内存重新读取最新值
解决了线程间数据不可见的问题
不保证原子性:
volatile不能保证复合操作的原子性
比如
count++
这样的操作(读取-修改-写入)不是原子性的
下面这段代码展示了volatile
最经典的用法——作为状态标志位:
package com.volatiledemo;
//volatile不能保证非原子操作的可见性和一致性
public class Test {
public static void main(String[] args) {
UserThread u = new UserThread();
u.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
u.setFlag(false);
}
}
package com.volatiledemo;
public class UserThread extends Thread{
// 当flag声明为volatile时,主线程修改flag = false后,UserThread立即能看到这个变化
private volatile boolean flag =true;
private int a = 0;
//private boolean flag =true;
public void run()
{
System.out.println(Thread.currentThread().getName()+",线程开始运行");
while(flag)
{
a =10;
a++;
}
System.out.println(Thread.currentThread().getName()+",线程结束运行"+"a的值为:"+a);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
运行结果:
以空间(内存)换安全
1.线程本地变量:ThreadLocal
ThreadLocal是Java提供的线程局部变量,它为每个使用该变量的线程提供独立的变量副本,实现了线程间的数据隔离。
工作原理:
- ThreadLocal内部使用ThreadLocalMap存储数据
- 以当前线程作为key来存储和检索值
- 每个线程都有自己独立的ThreadLocalMap
下面代码中虽然四个线程共享同一个UserRunnable实例,但由于使用了ThreadLocal:
每个线程都有自己独立的User对象
茉莉1线程设置的年龄不会影响栀子1线程的年龄值
各线程的年龄值保持独立,互不干扰
package com.threadlocal;
import java.util.Random;
public class UserRunnable implements Runnable {
// 线程本地变量 key-value
ThreadLocal<User> userLocal = new ThreadLocal<User>();
private User getUser() {
// key:对象hascode() value:对应这个对象
// 首先尝试从ThreadLocal获取User对象,每个User对象就是一个键值对
User u = userLocal.get();
// 如果不存在则创建新的User对象并存入ThreadLocal,确保每个线程有自己独立的User实例
if (null == u) {
u = new User();
System.out.println(u);
userLocal.set(u);
}
return u;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() + ",执行run方法");
Random r = new Random();
int age = r.nextInt(100);
System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);
// 从ThreadLocal获取当前线程的User对象
User u = this.getUser();
u.setAge(age);
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
UserRunnable u = new UserRunnable();
Thread s1 = new Thread(u, "茉莉1");
Thread s2 = new Thread(u, "栀子1");
Thread s3 = new Thread(u, "茉莉2");
Thread s4 = new Thread(u, "栀子2");
s1.start();
s2.start();
s3.start();
s4.start();
}
}
package com.threadlocal;
public class User {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
运行结果: