·多线程安全问题
一.观察线程不安全
代码示例:
public class ThreadDemon2 {
private static class Counter{
private long n=0;
public void increment(){
n++;
}
public long value(){
return n;
}
}
public static void main(String[] args) throws InterruptedException{
final int COUNT=10000;
Counter counter=new Counter();
//新建一个线程执行n++操作
Thread thread=new Thread(()->{
for(int i=0;i<COUNT;i++){
counter.increment();
}
},"李四");
thread.start();
//在main函数里执行n++操作
for(int i=0;i<COUNT;i++){
counter.increment();
}
//等待thread执行完毕
thread.join();
//输出值
System.out.println(counter.value());
}
}
运行结果:
运行结果并不是20000,也不唯一,之所以会出现上述情况是因为n++操作不是原子性的
实际上n++/n–操作是分为三步来执行的:
①从主内存复制n的值到线程的工作内存
②在工作内存中完成对n的++/–操作
③将n值写回主内存
试想若在n值还没有写回主内存之前,有别的线程拷贝主内存的值,那它拷贝的就是修改前的值,这样共享变量发生了修改的丢失造成了线程不安全
二.线程不安全原因
三.解决线程不安全
对上述不安全的代码进行加锁操作
代码示例:
public class ThreadDemon2 {
private static class Counter{
private long n=0;
public synchronized void increment(){
n++;
}
public long value(){
return n;
}
}
public static void main(String[] args) throws InterruptedException{
final int COUNT=10000;
Counter counter=new Counter();
//新建一个线程执行n++操作
Thread thread=new Thread(()->{
for(int i=0;i<COUNT;i++){
counter.increment();
}
},"李四");
thread.start();
//在main函数里执行n++操作
for(int i=0;i<COUNT;i++){
counter.increment();
}
//等待thread执行完毕
thread.join();
//输出值
System.out.println(counter.value());
}
}
运行结果:
同步块在已进入的线程执行完之前会阻塞后面其他线程的进入
·synchronized关键字
一.锁的SynchronizedDemo对象
public class SynchronizedDemo {
public synchronized void methond(){
}
public static void main(String[] args) {
SynchronizedDemo demo=new SynchronizedDemo();
demo.methond(); //进入方法会锁demo指向对象中的锁,出方法会释放demo指向对象中的锁
}
}
二.锁的SynchronizedDemo类的对象
public class SynchronizedDemo {
public synchronized static void methond(){
}
public static void main(String[] args) {
methond(); //进入方法会锁SynchronizedDemo.class指向对象中的锁,出方法会释放SynchronizedDemo.class指向对象中的锁
}
}
三.明确锁的对象
public class SynchronizedDemo {
public void methond(){
//进入代码块会锁this指向对象中的锁,出代码块会释放this指向对象中的锁
synchronized (this){
}
}
public static void main(String[] args) {
SynchronizedDemo demo=new SynchronizedDemo();
demo.methond();
}
}
public class SynchronizedDemo {
public void methond(){
// //进入代码块会锁SynchronizedDemo.class指向对象中的锁,出代码块会释放SynchronizedDemo.class指向对象中的锁
synchronized (SynchronizedDemo.class){
}
}
public static void main(String[] args) {
SynchronizedDemo demo=new SynchronizedDemo();
demo.methond();
}
}
·volatile关键字
保证可见性,保证有序性,不能保证原子性
class ThreadDemo{
private volatile int n;
}
·线程间的通信
一.wait()方法
1.wait()方法就是使线程停止运行,让当前线程进入等待状态,同时也会让当前线程释放掉它所持有的锁。直到其他线程调用此对象的notify()方法或notifyAll()方法,当前线程被唤醒,进入就绪状态。
2.wait()方法只能在同步方法中或同步块中调用,如果调用wait()时,没有持有适当的锁,就会抛出异常
代码示例:
public class ThreadDemo3 {
public static void main(String[] args) throws InterruptedException{
Object object=new Object();
synchronized (object){
System.out.println("等待中.....");
object.wait();
System.out.println("等待时间到");
}
System.out.println("main方法结束");
}
}
运行结果:
上述同步代码块中调用了wait()方法后会一直都是等待中,因为没有线程将它唤醒
二.notify()方法
1.方法notify()就是唤醒线程,也要在同步方法或者同步代码块中调用,该方法用来通知那些可能等待该对象的对象锁的其他线程,对其发出notify(),并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑出一个wait()状态的线程
2.在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码之后才会释放对象锁
三.notifyAll()方法
notify()方法只是唤醒某一个等待线程,notifyAll()方法可以一次唤醒所有的等待线程
四.wait()和sleep()的对比
1.wait要用在同步方法或者同步代码块中,wait执行时会先释放掉锁,等待被唤醒时再重新请求锁
2.sleep是无视锁的存在的,请求之后锁不会释放,没有锁也不会请求
3.wait是Object方法,sleep是Thread类的静态方法