线程安全:当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某一个数据时,会对这个数据进行保护,其余线程不能对其访问,直到该线程读取结束以后,其余线程才可使用。防止出现数据不一致或者数据被污染的状况。
线程不安全:多个线程同时操做某个数据,出现数据不一致或者被污染的状况。java
代码示例:面试
package thread_5_10; public class Demo26 { static int a = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100_0000; i++) { a++; } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100_0000; i++) { a--; } } }); //开启线程 t1.start(); t2.start(); //等待线程完成 //t1.join(); //t2.join(); while(t1.isAlive() || t2.isAlive()){ } System.out.println(a); } }
运行结果:安全
493612
结果分析:多线程
我的整理了一些资料,有须要的朋友能够直接点击领取。架构
[Java基础知识大全](https://jq.qq.com/?_wv=1027&k...
)jvm
[22本Java架构师核心书籍](https://jq.qq.com/?_wv=1027&k...
)ide
[从0到1Java学习路线和资料](https://jq.qq.com/?_wv=1027&k...
)工具
[1000+道2021年最新面试题](https://jq.qq.com/?_wv=1027&k...学习
CPU是抢占式执行的(抢占资源)
多个线程操做的是同一个变量
可见性
非原子性
编译期优化(指令重排)优化
volatile是指令关键字,做用是确保本指令不会因编译期优化而省略,且每次要求直接读值。能够解决内存不可见和指令重排序的问题,可是不能解决原子性问题
有两种加锁方式:
synchronized(jvm层的解决方案)
Lock手动锁
尝试获取锁a
使用锁(这一步骤是具体的业务代码)
释放锁
synchronized是JVM层面锁的解决方案,它帮咱们实现了加锁和释放锁的过程
package thread_5_10; public class Demo31 { //循环的最大次数 private final static int maxSize = 100_0000; //定义全局变量 private static int number = 0; public static void main(String[] args) throws InterruptedException { //声明锁对象 Object obj = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { //实现加锁 synchronized (obj){ number++; } } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { synchronized (obj){ number--; } } } }); t2.start(); //等待两个线程执行完成 t1.join(); t2.join(); System.out.println(number); } }
运行结果:
0
解析:
synchronized实现分为:
操做系统层面,它是依靠互斥锁mutex
针对JVM,monitor实现
针对Java语言来讲,是将锁信息存放在对象头中
使用synchronized修饰代码块,(能够对任意对象加锁)
使用synchronized修饰静态方法(对当前类进行加锁)
使用synchronized修饰普通方法(对当前类实例进行加锁)
修饰静态方法:
package thread_5_10; public class Demo32 { private static int number = 0; private static final int maxSize = 100_0000; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { increment(); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { decrement(); } }); t2.start(); t1.join(); t2.join(); System.out.println("最终结果为:"+number); } public synchronized static void increment(){ for (int i = 0; i < maxSize; i++) { number++; } } public synchronized static void decrement(){ for (int i = 0; i < maxSize; i++) { number--; } } }
修饰实例方法:
package thread_5_10; public class Demo33 { private static int number = 0; private static final int maxSize = 100_0000; public static void main(String[] args) throws InterruptedException { Demo33 demo = new Demo33(); Thread t1 = new Thread(new Runnable() { @Override public void run() { demo.increment(); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { demo.decrement(); } }); t2.start(); t1.join(); t2.join(); System.out.println("最终结果:"+number); } public synchronized void increment(){ for (int i = 0; i < maxSize; i++) { number++; } } public synchronized void decrement(){ for (int i = 0; i < maxSize; i++) { number--; } } }
代码示例:
package thread_5_10; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo34 { private static int number = 0; private static final int maxSize = 100_0000; public static void main(String[] args) throws InterruptedException { //建立lock实例 Lock lock = new ReentrantLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { lock.lock(); try{ number++; }finally { lock.unlock(); } } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < maxSize; i++) { lock.lock(); try{ number--; }finally { lock.unlock(); } } } }); t2.start(); t1.join(); t2.join(); System.out.println("最终结果为--> "+number); } }
运行结果:
最终结果为--> 0
注意事项:
lock()必定要放在try外面
若是放在try里面,若是try里面出现异常,尚未加锁成功就执行finally里面的释放锁的代码,就会出现异常
若是放在try里面,若是没有锁的状况下释放锁,这个时候产生的异常就会把业务代码里面的异常给吞噬掉,增长代码调试的难度
公平锁:当一个线程释放锁以后,须要主动唤醒“须要获得锁”的队列来获得锁
非公平锁:当一个线程释放锁以后,另外一个线程恰好执行到获取锁的代码就能够直接获取锁
java语言中,全部锁的默认实现方式都是非公平锁
1.synchronized是非公平锁
2.reentrantLock默认是非公平锁,但也能够显示地声明为公平锁
显示声明公平锁格式:
ReentrantLock源码:
示例一:
package thread_5_10; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo36 { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(true); Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { lock.lock(); try{ System.out.println("线程1"); }finally { lock.unlock(); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { lock.lock(); try{ System.out.println("线程2"); }finally { lock.unlock(); } } } }); Thread.sleep(1000); t1.start(); t2.start(); } }
运行结果:
示例二:
package test; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class test08 { public static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(true); Runnable r = new Runnable() { @Override public void run() { for(char ch: "ABCD".toCharArray()){ lock.lock(); try{ System.out.print(ch); }finally { lock.unlock(); } } } }; Thread.sleep(100); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } }
运行结果:
AABBCCDD
synchronized和lock的区别
关键字不一样
synchronized自动进行加锁和释放锁,而Lock须要手动加锁和释放锁
synchronized是JVM层面上的实现,而Lock是Java层面锁的实现
修饰范围不一样,synchronized能够修饰代码块,静态方法,实例方法,而Lock只能修饰代码块
synchronized锁的模式是非公平锁,而lock锁的模式是公平锁和非公平锁
Lock的灵活性更高
在两个或两个以上的线程运行中,由于资源抢占而形成线程一直等待的问题
当线程1拥有资源并1且试图获取资源2和线程2拥有了资源2,而且试图获取资源1的时候,就发了死锁
package thread_5_11; public class Demo36 { public static void main(String[] args) { //声明加锁的资源 Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { //获取线程名称 String threadName = Thread.currentThread().getName(); //1.获取资源1 synchronized (lock1){ System.out.println(threadName+" 获取到了lock1"); try { //2.等待1ms,让线程t1和线程t2都获取到相应的资源 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName+" waiting lock2"); //3.获取资源2 synchronized (lock2){ System.out.println(threadName+" 获取到了lock2"); } } } },"t1"); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); synchronized (lock2){ System.out.println(threadName+" 获取到了lock2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName+" waiting lock1"); synchronized (lock1){ System.out.println(threadName+" 获取到了lock1"); } } } },"t2"); t2.start(); } }
运行结果:
经过工具来查看死锁:
(1)jdk–>bin–>jconsole.exe
(2)jdk–>bin–>jvisualvm.exe
(3)jdk–>bin–>jmc.exe
1.互斥条件:当资源被一个线程拥有以后,就不能被其余的线程拥有了
2.占有且等待:当一个线程拥有了一个资源以后又试图请求另外一个资源
3.不可抢占:当一个资源被一个线程被拥有以后,若是不是这个线程主动释放此资源的状况下,其余线程不能拥有此资源
4.循环等待:两个或两个以上的线程在拥有了资源以后,试图获取对方资源的时候造成了一个环路
所谓的线程通信就是在一个线程中的操做能够影响另外一个线程,wait(休眠线程),notify(唤醒一个线程),notifyall(唤醒全部线程)
注意事项:
1.wait方法在执行以前必须先加锁。也就是wait方法必须配合synchronized配合使用
2.wait和notify在配合synchronized使用时,必定要使用同一把锁
运行结果:
wait以前 主线程唤醒t1 wait以后
多线程
package thread_5_13; public class demo40 { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { //调用wait方法以前必须先加锁 synchronized (lock){ try { System.out.println("t1 wait以前"); lock.wait(); System.out.println("t1 wait以后"); } catch (InterruptedException e) { e.printStackTrace(); } } } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { //调用wait方法以前必须先加锁 synchronized (lock){ try { System.out.println("t2 wait以前"); lock.wait(); System.out.println("t2 wait以后"); } catch (InterruptedException e) { e.printStackTrace(); } } } },"t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { //调用wait方法以前必须先加锁 synchronized (lock){ try { System.out.println("t3 wait以前"); lock.wait(); System.out.println("t3 wait以后"); } catch (InterruptedException e) { e.printStackTrace(); } } } },"t3"); t1.start(); t2.start(); t3.start(); Thread.sleep(1000); System.out.println("主线程调用唤醒操做"); //在主线程中唤醒 synchronized (lock){ lock.notify(); } } }
运行结果:
t1 wait以前 t2 wait以前 t3 wait以前 主线程调用唤醒操做 t1 wait以后
注意事项:
将lock.notify()修改成lock.notifyAll(),则三个线程都能被唤醒
wait在不传递任何参数的状况下会进入waiting状态(参数为0也是waiting状态);当wait里面有一个大于0的整数时,它就会进入timed_waiting状态
关于wait和sleep释放锁的代码:
wait在等待的时候能够释放锁,sleep在等待的时候不会释放锁
相同点:
(1)wait和sleep均可以使线程休眠
(2)wait和sleep在执行的过程当中均可以接收到终止线程执行的通知
不一样点:
(1)wait必须synchronized一块儿使用,而sleep不用
(2)wait会释放锁,sleep不会释放锁
(3)wait是Object的方法,而sleep是Thread的方法
(4)默认状况下,wait不传递参数或者参数为0的状况下,它会进入waiting状态,而sleep会进入timed_waiting状态
(5)使用wait能够主动唤醒线程,而使用sleep不能主动唤醒线程
1.问:sleep(0)和wait(0)有什么区别
答:(1)sleep(0)表示过0毫秒后继续执行,而wait(0)会一直等待
(2)sleep(0)表示从新触发一次CPU竞争
2.为何wait会释放锁,而sleep不会释放锁
答:sleep必需要传递一个最大等待时间的,也就是说sleep是可控的(对于时间层面来说),而wait是能够不传递时间,从设计层面来说,若是让wait这个没有超时等待时间的机制下释放锁的话,那么线程可能会一直阻塞,而sleep不会存在这个问题
3.为何wait是Object的方法,而sleep是Thread的方法
答:wait须要操做锁,而锁是对象级别(全部的锁都在对象头当中),它不是线程级别,一个线程能够有多把锁,为了灵活起见,全部把wait放在Object当中
4.解决wait/notify随机唤醒的问题
答:可使用LockSupport中的park,unpark方法,注意:locksupport虽然不会报interrupted的异常,可是能够监听到线程终止的指令
都看到这里了,记得点个赞哦!