/*
* synchonized关键字 互斥锁
* 对某个对象加锁
*/
public class T {
private int count=10;
//private Object o=new Object(); //new 一个对象当锁 太浪费 可以使用this
public void m(){
synchronized (this){ //任何线程要执行下面的代码,必须要先拿到o的锁 o指向堆内存里对象的锁,不是引用
count--;
System.out.println(Thread.currentThread());
}
}
}
synchronized 锁的是一个对象,不是代码块。
如果代码开始就要加锁,结束才会释放锁 可以写成下面的形式
public synchronized void m(){
...
}
如果是静态的属性或方法 是没有对象的,所以锁定的是当前的类
public static void mm(){ //静态方法时 锁的是当前的类
synchronized(T.class){
}
}
线程重入问题 : 打断另一个线程施法,也可以叫另一个线程断片。
解决方法: 在线程访问的方法上加 synchronized 锁 ,
public class T implements Runnable{
private int count=10;
public synchronized void run(){ //多个线程访问同一个对象造成线程重入问题,因此需要加锁
count--;
System.out.println(Thread.currentThread().getName()+"count="+count);
}
public static void main(String[] args) {
T t = new T(); //new 了一个对象 多个线程访问
for (int i = 0; i < 5; i++) {
new Thread(t,"Thread"+i).start(); //五个线程访问的都是t里面的run方法
}
}
}
package com.ThreadLearning;
/**
* 同步方法和非同步方法是否可以同时调用?
*/
public class T1 {
public synchronized void m1() { //需要锁定当前对象的
System.out.println(Thread.currentThread().getName() + "m1 start ....");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m1 end ....");
}
public void m2() { //不需要锁定当前对象的
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m2 end ....");
}
public static void main(String[] args) {
T1 t = new T1();
new Thread(()->t.m1(),"t1:").start();
// new Thread(()->t.m2(),"t2:").start();
new Thread(t::m2(),"t2:").start();
}
}
/**
* t1:m1 start ....
* t2:m2 end ....
* t1:m1 end ....
*/
import java.util.concurrent.TimeUnit;
/**
* 对业务写加锁,对业务读不加锁
* 产生脏读(dirtyRead)问题
*/
public class Count {
String name;
double balance;
//银行账户 在多个线程访问时需要加锁
public synchronized void set(String name, double balance) {
this.name = name;
/**
* 放大了线程之间的时间差 会造成 脏数据 读不到数据
* 时间差: 非同步方法
*
*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
//写加了锁 读没有加锁
// 具体根据代码的业务逻辑
public double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Count count = new Count();
new Thread(() -> count.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count.getBalance("zhangsan")); //主线程 读出来是0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count.getBalance("zhangsan"));
}
}
4.一个同步方法是否可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说Synchronized获得的锁是可重入的
import java.util.concurrent.TimeUnit;
public class T2 {
synchronized void m1(){
System.out.println("m1 start 。。。。");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
synchronized void m2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
}
一个线程里 可以再次申请已经拥有的锁
结论 : Synchronized获得的锁是可重入的 ,
注意:可能会产生死锁 (互相拿到对方的锁)
扩展:子类调用父类的同步方法 √ this–>child
5.程序在执行过程中,如果出现异常,默认情况下锁就会被释放
所以在并发过程中,有异常要小心处理,不然可能会发生不一致的问题
如:在一个web app处理过程中,多个servlet线程共享访问同一个资源,这时如果异常处理不合适,
在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据。
处理方法: 要想不被释放,可以进行try catch 然后让循环继续
Thread类其实也实现了Runable接口。--->创建时直接new Thread(new Runnable(){public void run(){new T().m1()}}).start();
import javax.sound.midi.SoundbankResource;
import java.util.concurrent.TimeUnit;
/**
* volatile 关键字,使一个变量在多个线程间可见
* A B线程都用到一个变量,java 默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
* 使用Volatile关键字,会让所有的线程读到变量的修改值
*
* 在下面的代码中,running是存在于堆内存的t对象中
* 当线程t1开始运行的时候,会把running值从内存中读取到t1线程的工作区,在运行过程中直接使用这个copy,并不会
* 每次读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
*
* 使用volatile关键字,将会强制所有线程都去内存中读取running的值
*/
public class VolatileDemo {
volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
//缓冲区里的值发生改变时 会给调用这个值的线程发送通知,数据已经发生改变
void m() {
System.out.println("m start ...");
while (running) {
}
System.out.println("m end ...");
}
public static void main(String[] args) {
VolatileDemo t = new VolatileDemo();
new Thread(t::m,"t1").start(); //t1线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running=false; //主线程
}
}
volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
res: 可见性 保证读不保证写===> 写的时候会被覆盖 结果小于10000
package com.ThreadLearning;
import java.util.ArrayList;
/**
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
*/
public class VolatileDemo2 {
volatile int count = 0; //可见性 保证读不保证写===> 写的时候会被覆盖 结果小于10000
void m() {
for (int i = 0; i < 1000; i++) count++;
}
public static void main(String[] args) {
VolatileDemo2 t = new VolatileDemo2();
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) { //10个线程 都加到1000 共10000
threads.add((new Thread(t::m, "Thread=" + i)));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
如果只是对数据进行++ – java 提供了 AtomXXX 类本身方法都是原子性的
AtomicInteger atomicInteger= new AtomicInteger(0).incrementAndGet() //count++ 不具备原子性
两个原子性操作是不具备原子性的,在两个原子性操作之间还会有其他操作。
AtomXXX 比 Synchronized 性能更好。
package com.ThreadLearning;
import java.util.concurrent.TimeUnit;
/**
* 锁定某对象,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成另一个对象,则锁定的对象发生改变
* 应该避免将锁对象的引用变成另外的对象
*/
public class T {
Object o= new Object();
void m(){
synchronized (o){
while (true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T t = new T();
//启动第一个线程
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动第二个线程
Thread t2 = new Thread(t::m, "t2");
t.o=new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释这句代码 t2将永远不能执行
t2.start(); //证明锁在堆内存的对象上的
}
}
不要用字符串常量当锁对象
import java.util.concurrent.TimeUnit;
/**
* 不要以字符串常量作为锁对象
* 在下面的例子中,m1和m2 其实锁定的是同一个对象
* 这种情况还会发生诡异的现象,比如你用到了一个类库,在该类库中锁定字符串"hello"
* 但是你读不到源码,在自己的代码中也锁定了"hello",这时候可能就会发生诡异的死锁阻塞
* 因为你的程序和你用到的类库不经意间使用了同意把锁
*/
public class T {
String s1="hello";
String s2="hello";
void m1(){
synchronized (s1){
}
}
void m2(){
synchronized (s2){
}
}
//jetty 曾经有这样的bug
}
package com.ThreadLearning;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 面试题:
* 实现一个容器:提供俩个方法,add size
* 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5时。线程给出提示并结束
*/
public class MyContainer {
volatile List list = new ArrayList();
public void add(Object o) {
list.add(o);
}
public int size() {
return list.size();
}
public static void main(String[] args) {
MyContainer c = new MyContainer();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add>>>" + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (true) {
if (c.size() == 5) {
break;
}
System.out.println("t2 结束。。");
}
}, "t2").start();
}
}
wati notify 是调用锁定对象的方法
先启动t2 不等于5时 wait 等于5 时 notify 执行T2
notify 不释放锁 所以 notify后还需要在wait一下
最简单的方法 Latch(门闩)替代wait notify
好处是通信简单,同时也可以指定等待时间
CountDownLatch不涉及锁定,当count为0时当前线程继续执行
当不涉及同步,只是涉及线程通信的时候,用synchronzied_wait/notify就太重了
这时应该考虑countdownLatch/cyclicbarrier/semaphore
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 面试题:
* 实现一个容器:提供俩个方法,add size
* 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5时。线程给出提示并结束
*/
public class MyContainer {
volatile List list = new ArrayList();
public void add(Object o) {
list.add(o);
}
public int size() {
return list.size();
}
public static void main(String[] args) {
MyContainer c = new MyContainer();
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2启动");
if (c.size()!=5){
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
}, "t2").start();
new Thread(()->{
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add--"+i);
if (c.size()==5){
//打开门闩,让t2执行
latch.countDown(); // 0 就开了 countDown一次 减一次
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}