一、并发问题分析
如果一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,此时线程是安全的。
而实际中存在这样的情况:
1.存在两个或者两个以上的线程对象,而且线程之间共享着一个资源。
2.有多个语句操作了共享资源。
二、解决方案
用一个案例来说明:张刘亮和郑旭共用一包面纸,两个人相当于两个线程,每一张纸不可以两人同时抽取
方式一:同步代码块
线程类:
class PaperThread extends Thread{
static int num = 100;//纸的数量 非静态的成员变量,非静态的成员变量数据是在每个对象中都会维护一份数据的。
static Object o = new Object();
public PaperThread (String name) {
super(name);
}
@Override
public void run() {
while(true){
//同步代码块
synchronized ("锁") {
if(num>0){
System.out.println(Thread.currentThread().getName()
+"取走了1张纸,还剩"+(count-1)+"张"");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}else{
System.out.println("用光了..");
break;
}
}
}
}
}
测试类
public class Test{
public static void main(String[] args) {
//创建两个线程
PaperThread thread1 = new PaperThread("张刘亮");
PaperThread thread2 = new PaperThread("郑旭");
//开启线程
thread1.start();
thread2.start();
}
}
同步代码块要注意事项:
1. 任意的一个对象都可以做为锁对象。
2. 在同步代码块中调用了sleep方法并不是释放锁对象的。
3. 只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率的。
4. 多线程操作的锁对象必须是唯一共享的,否则无效。
方式二:同步函数:同步函数就是使用synchronized修饰一个函数。
线程类:
class PaperThread extends Thread{
static int count = 100;
public currentThread(String name){
super(name);
}
//@Override
public synchronized void run() {
getPaper();
}
//静态的函数---->函数所属的类的字节码文件对象--->PaperThread.class 唯一的。
public static synchronized void getPaper(){
while(true){
if(count>0){
System.out.println(Thread.currentThread().getName()
+"取走了1张纸,还剩"+ (count-1)+"张");
count= count - 1000;
}else{
System.out.println("纸用光了...");
break;
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建两个线程对象
PaperThread thread1 = new PaperThread("张刘亮");
PaperThread thread2 = new PaperThread("郑旭");
//调用start方法开启线程取钱
thread1.start();
thread2.start();
}
}
同步函数要注意的事项 :
1. 如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)。
2. 同步函数的锁对象是固定的,不能由你来指定的。
推荐使用:同步代码块。
原因:
1. 同步代码块的锁对象可以由我们随意指定,方便控制。同步函数的锁对象是固定的,不能由我们来指定。
2. 同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步了。
三、死锁
同步机制解决了线程安全问题,但是也同时引发死锁现象。
案例:张刘亮和郑旭打LOL,只有一个鼠标和一个键盘,需要同时获取鼠标和键盘才能打LOL
class DeadLock extends Thread{
public DeadLock(String name){
super(name);
}
public void run() {
if("张刘亮".equals(Thread.currentThread().getName())){
synchronized ("键盘") {
System.out.println("张刘亮拿到了键盘,准备去拿鼠标");
synchronized ("鼠标") {
System.out.println("张刘亮拿到了键盘和鼠标,撸了起来");
}
}
}else if("郑旭".equals(Thread.currentThread().getName())){
synchronized ("鼠标") {
System.out.println("郑旭拿到了鼠标,准备去拿键盘");
synchronized ("键盘") {
System.out.println("郑旭拿到了键盘和鼠标,撸了起来");
}
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
DeadLock thread1 = new DeadLock("张刘亮");
DeadLock thread2 = new DeadLock("郑旭");
//开启线程
thread1.start();
thread2.start();
}
}
死锁现象出现 的根本原因:
1. 存在两个或者两个以上的线程。
2. 存在两个或者两个以上的共享资源。
死锁现象的解决方案:死锁没有解决方案,只能尽量避免。