线程安全:当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某一个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取结束之后,其他线程才可以使用。防止出现数据不一致或者数据被污染的情况。
线程不安全:多个线程同时操作某个数据,出现数据不一致或者被污染的情况。
代码示例:
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
线程不安全的因素:
volatile是指令关键字,作用是确保本指令不会因编译期优化而省略,且每次要求直接读值。可以解决内存不可见和指令重排序的问题,但是不能解决原子性问题
有两种加锁方式:
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实现分为:
修饰静态方法:
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外面
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的区别
在两个或两个以上的线程运行中,因为资源抢占而造成线程一直等待的问题
当线程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之后
注意事项:
关于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的异常,但是可以监听到线程终止的指令