一、为什么有线程安全问题存在?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题,但是做读的操作是不会发生数据冲突问题。
二、线程不安全问题怎么解决?
使用synchronized、jdk1.5并发包lock
三、synchronizd的使用方法
- 使用同步代码块
就是将可能发生线程安全问题的代码,给包裹起来。
//“同一个数据”部分相当于一把锁,可以使用空的Object对象作为入参传入
synchronized(同一个数据){
可能会发生线程冲突问题
}
注意:synchronized包裹的部分一定要是可能会发生线程安全问题的代码,而且synchrozied关键字只适合单个jvm,分布式集群环境下不能使用,在分布式集群环境下要使用分布式锁zookeeper。package chauncy.threadtrain; class ThreadTrain implements Runnable { // 火车票总数 private int count = 100; //两个线程一定要用同一把锁 private Object obj=new Object(); @Override public void run() { while (count > 0) { try { Thread.sleep(40); } catch (InterruptedException e) { } synchronized(obj){ // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } } /** * 什么是线程不安全 * 当多个线程同时操作同一个共享的全局变量,可能会受到其他线程的干扰。会发生数据冲突的问题。 * 线程不安全问题怎么解决? * 使用synchronize、jdk1.5的并发包lock * 使用synchronized关键字包裹起来的代码 * 每次只能让当前一个线程进行执行 * @classDesc: 功能描述(模拟线程不安全问题) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo1 { public static void main(String[] args) { //线程类一定要用一个实例,因为要重现变量共享的问题 ThreadTrain threadTrain = new ThreadTrain(); // 1.创建两个线程 Thread thread1 = new Thread(threadTrain); Thread thread2 = new Thread(threadTrain); thread1.start(); thread2.start(); } }
- 使用同步函数(同步方法)
什么是同步函数?
在方法上修饰synchronized称为同步函数。
同步函数用的是什么锁?
同步函数使用this锁。package chauncy.threadtrain; class ThreadTrain2 implements Runnable { // 火车票总数 private int count = 100; //两个线程一定要用同一把锁 private Object obj = new Object(); @Override public void run() { while (count > 0) { show(); } } public synchronized void show() { if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } /** * @classDesc: 功能描述(使用同步函数(同步方法)) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo2 { public static void main(String[] args) { //线程类一定要用一个实例,因为要重现变量共享的问题 ThreadTrain2 threadTrain2 = new ThreadTrain2(); // 1.创建两个线程 Thread thread1 = new Thread(threadTrain2); Thread thread2 = new Thread(threadTrain2); thread1.start(); thread2.start(); } }
package chauncy.threadtrain; class ThreadTrain3 implements Runnable { // 火车票总数 private int count = 100; //两个线程一定要用同一把锁 private Object obj = new Object(); public boolean flag=true; @Override public void run() { //线程1 flag为true 线程2 flag为false if(flag){ while (count > 0) { //入参为obj情况下线程不安全,为this情况线程安全,证明同步函数使用的是this锁。 synchronized(this){ if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } }else{ while (count > 0) { show(); } } } public synchronized void show() { if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } /** * @classDesc: 功能描述(证明同步函数是this锁) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { //线程类一定要用一个实例,因为要重现变量共享的问题 ThreadTrain3 threadTrain3 = new ThreadTrain3(); // 1.创建两个线程 Thread thread1 = new Thread(threadTrain3); Thread thread2 = new Thread(threadTrain3); thread1.start(); Thread.sleep(10); threadTrain3.flag=false; thread2.start(); } }
- 使用静态同步函数
什么是静态同步函数?
方法上加上static关键字,并且使用synchronized关键字修饰 。
静态同步函数使用的锁就不是this锁,因为this锁是属于对象级别的,静态同步函数应该使用类锁,即该类的字节码文件。
可以使用getClass方法获取,也可以使用当前线程类的类名.class 获取该线程类的字节码文件。package chauncy.threadtrain; class ThreadTrain4 implements Runnable { // 火车票总数 private static int count = 100; //两个线程一定要用同一把锁 private Object obj = new Object(); public boolean flag=true; @Override public void run() { //线程1 flag为true 线程2 flag为false if(flag){ while (count > 0) { //入参为obj情况下线程不安全,为this情况线程安全,证明同步函数使用的是this锁。 synchronized(ThreadDemo4.class){ if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } }else{ while (count > 0) { show(); } } } /** * static 如果修饰的方法,直接通过类名.方法名进行调用 当class文件也是字节码文件被加载时,才会被初始化。 static存放在方法区 永久区 * this关键字表示当前对象的锁,static使用类.class的锁 * @methodDesc: 功能描述() * @author: ChauncyWang * @param: * @returnType: void */ public static synchronized void show() { if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } /** * @classDesc: 功能描述(静态同步函数) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo4 { public static void main(String[] args) throws InterruptedException { //线程类一定要用一个实例,因为要重现变量共享的问题 ThreadTrain4 threadTrain4 = new ThreadTrain4(); // 1.创建两个线程 Thread thread1 = new Thread(threadTrain4); Thread thread2 = new Thread(threadTrain4); thread1.start(); Thread.sleep(10); threadTrain4.flag=false; thread2.start(); } }
四、多线程死锁
- 死锁的类型:数据库死锁、线程死锁、行锁、表锁等。
- 什么是多线程死锁?
在同步中嵌套同步,导致锁无法释放。 - 锁在什么时候释放?
锁一般在代码执行完毕之后自动释放,让其它线程拿到锁执行。 - 怎么避免死锁?
同步中尽量不要嵌套同步。 - 死锁的代码实现:
package chauncy.threadtrain; class ThreadTrain5 implements Runnable { // 火车票总数 private static int count = 100; //两个线程一定要用同一把锁 private Object obj = new Object(); public boolean flag=true; @Override public void run() { //线程1 flag为true 线程2 flag为false if(flag){ while (count > 0) { //锁一般是在代码执行完毕之后自动释放,让其它线程拿到锁执行 //如果flag为true 先拿到obj这把锁,再拿到this锁,才能执行代码 //如果flag为false 先拿到this这把锁,再拿到obj锁,才能执行代码 synchronized(obj){ show(); } } }else{ while (count > 0) { show(); } } } public synchronized void show() { synchronized(obj){ if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100总数减去现有数量count加1为当前出售的第几张票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } } /** * @classDesc: 功能描述(死锁实现) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo5 { public static void main(String[] args) throws InterruptedException { //线程类一定要用一个实例,因为要重现变量共享的问题 ThreadTrain5 threadTrain5 = new ThreadTrain5(); // 1.创建两个线程 Thread thread1 = new Thread(threadTrain5); Thread thread2 = new Thread(threadTrain5); thread1.start(); Thread.sleep(40); threadTrain5.flag=false; thread2.start(); } }
五、线程间实现同步(线程安全)的问题总结
- 什么是多线程安全?
多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。做读操作是不会发生数据冲突问题。 - 如何解决多线程之间线程安全问题?
使用多线程之间同步或使用锁(lock)。 - 为什么使用线程同步或使用锁能解决线程安全问题呢?
将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。被包裹的代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。 - 什么是多线程之间同步?
当多个线程共享同一个资源,不会受到其他线程的干扰。 - 什么是同步代码块?
是将可能会发生线程安全问题的代码,给包括起来。只能让当前一个线程进行执行,被包裹的代码执行完成之后才能释放所,让后才能让其他线程进行执行。 - 同步代码块与同步函数区别?
同步代码使用自定锁(明锁),同步函数使用this锁。 - 同步函数与静态同步函数区别(一个静态方法和一个非静态静态怎么实现同步?)?
同步函数使用this锁,静态同步函数使用字节码文件,也就是类.class。 - 什么是多线程死锁?
同步中嵌套同步,解决办法:同步中尽量不要嵌套同步。