1 多线程安全问题
1.1 起因
在一个进程中,多个线程共享相同的资源(数据或者引用地址),出现的多个线程先后使用、更新或者删除数据造成的重复读,幻读或者脏读问题。
1.2 非安全线程—使用继承Thread类的方式
1.2.1 example
public class SellTicketThread extends Thread{
int tickets=100;
public void run(){
while(tickets>0){
System.out.println(getName()+"--"+tickets);
tickets--;
}
}
}
public static void main(String[] args){
Thread t1=newSellTicketThread();
Thread t2=newSellTicketThread();
Thread t3=newSellTicketThread();
Thread t4=newSellTicketThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
1.2.2解决方案
简单的多线程共享基本数据类型的数据时,使用线程子类内静态化属性的方法可以解决一部分重复读的问题,但是不能解决线程首次并发时的重复读问题。
public class SellTicketThread extends Thread{
public static int tickets=100;
public void run(){
while(tickets>0){
System.out.println(getName()+"--"+tickets);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
tickets--;
}
}
}
1.3 使用实现Runnable接口的方式
1.3.1 example
简单的多线程共享基本数据类型的数据时,使用实现Runnable接口的方式时,由于线程只是实现了run方法,采用的是Runnable实现类的全局变量,而不是像实现Thread类的方式继承了Thread的属性,能够保持共享属性的完全隔离性(除首次开启线程时)。那么,采用实现接口的方式,在使用这个全局变量时,可能存在重复读,脏读,幻读等安全问题。
public class SellTicketTask implements Runnable{
private static int ticket=100;
public void run() {
while(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedExceptione1) {
// TODO Auto-generatedcatch block
e1.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
ticket--;
}
}
}
1.3.2 解决方案
(1) 使用同步代码块
Synchronized(Object){
需要同步的代码
}
public class SellTicketTask implements Runnable{
private int ticket=100;
Object o=new Object();
public void run() {
while(ticket>0){
synchronized(o){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"--"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
}
}
}
}
}
对于synchronized的锁对象的测试
public class SellTicketTask implements Runnable{
private int ticket=100;
Object o=new Object();
Object o2=new Object();
int a=1;
public void run() {
while(ticket>0){
if(a==1){
synchronized(o){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-1-"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
a=2;
}
}
}
if(a==2){
synchronized(o){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-2-"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
a=3;
}
}
}
if(a==3){
synchronized(o){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-3-"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
a=1;
}
}
}
}
}
}
(2) 使用synchronized修饰非静态方法
如果一个方法内代码全部被synchronized代码块包裹,那么这个方法就可以定义为一个同步方法。格式将sychronized加到这个方法的修饰中。由于同步方法的锁就是当前的对象。所有,如果synchronized代码块和同步方法混合使用,需要将synchronized的对象锁设置为当前对象,也就是this。
public class SellTicketTask implements Runnable{
private int ticket=100;
Object o=new Object();
Object o2=new Object();
int a=1;
public synchronized void synmethod(inta){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-"+a+"-"+ticket);
try {
Thread.sleep(10);
} catch(InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
if(a==2){
this.a=3;
}
if(a==3){
this.a=1;
}
}
}
public void run() {
while(ticket>0){
if(a==1){
synchronized(this){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-1-"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
a=2;
}
}
}
if(a==2){
synmethod(2);
}
if(a==3){
synmethod(3);
}
}
}
}
(3) 使用synchronized修饰的静态方法
同步静态方法的格式同同步非静态方法,但是注意引用全局变量的修饰符和类属性使用类调用的问题。另外,静态方法的对象锁不是类的实例对象,因为静态方法的加载先于实例对象。那么,一般,类加载到内存中,最开始是将class文件字节码内容加载到内存,并将这些静态数据转换成方法区中的运行时数据结构,然后在堆中生成一个表示这个类的Class对象。一般,有三种方法得到这个加载类的Class对象。1,Object类的getClass方法;2,类名.class;3,Class类的forName方法。
public class SellTicketTask implements Runnable{
private static int ticket=100;
Object o=new Object();
Object o2=new Object();
static int a=1;
public static synchronized void stasynmethod(inta){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-"+a+"-"+ticket);
try {
Thread.sleep(10);
} catch(InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
if(a==2){
SellTicketTask.a=3;
}
if(a==3){
SellTicketTask.a=1;
}
}
}
public void run() {
while(ticket>0){
if(a==1){
//synchronized(SellTicketTask.class){
try {
synchronized(Class.forName("com.edu.thread.SellTicketTask")){
//synchronized(newSellTicketTask().getClass()){
//使用3种方法得到运行类的class对象
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"-1-"+ticket);
try {
Thread.sleep(10);
} catch(InterruptedExceptione) {
e.printStackTrace();
}
ticket--;
a=2;
}
}
} catch (ClassNotFoundExceptione) {
e.printStackTrace();
}
}
if(a==2){
stasynmethod(2);
}
if(a==3){
stasynmethod(3);
}
}
}
}
2 利用锁解决懒汉式单例的多线程安全问题
2.1 单例模式原型
public class SingleHungry {
private SingleHungrysingleH=new SingleHungry();
private SingleHungry(){}
public static SingleHungrygetSingleH(){
return singleH;
}
}
public class SingleLazy {
private static SingleLazy singleL;
private SingleLazy(){}
public static SingleLazy getSingleL(){
if(singleL==null){
singleL=new SingleLazy();
}
returnsingleL;
}
}
2.2 人为制造懒汉式的线程安全问题
public class SingleLazy {
private static SingleLazy singleL;
private SingleLazy(){}
public static SingleLazy getSingleL(){
if(singleL==null){
try {
Thread.sleep(100);
} catch(InterruptedExceptione) {
e.printStackTrace();
}
singleL=new SingleLazy();
}
return singleL;
}
}
public class TestSingle implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"--"+SingleLazy.getSingleL());
}
}
public static void main(String[] args) throws InterruptedException{
TestSinglets=new TestSingle();
Threadt1=new Thread(ts,"lizi1");
Threadt2=new Thread(ts,"lizi2");
Threadt3=new Thread(ts,"lizi3");
Threadt4=new Thread(ts,"lizi4");
Threadt5=new Thread(ts,"lizi5");
Threadt6=new Thread(ts,"lizi6");
Threadt7=new Thread(ts,"lizi7");
Threadt8=new Thread(ts,"lizi8");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
}
2.3 利用同步锁解决线程安全问题
public class SingleLazy {
private static SingleLazy singleL;
private SingleLazy(){}
public static SingleLazy getSingleL(){
//优先判断null,提高效率。
if(singleL==null){
synchronized(SingleLazy.class){
if(singleL==null){
try {
Thread.sleep(100);
} catch(InterruptedExceptione) {
e.printStackTrace();
}
singleL=new SingleLazy();
}
}
}
return singleL;
}
}
测试方法同上。
3 deadlock
3.1 什么是死锁
两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无其他作用,它们都将无法推进下去。
产生死锁的四个条件:1,互斥条件;2,请求与保持条件;3,不剥夺条件;4,循环等待条件。
3.2 example
public class DeadLock implements Runnable{
private Objecto1=new Object();
private Objecto2=new Object();
public void run() {
while(true){
synchronized(o1){
System.out.println(Thread.currentThread().getName()+"--"+"拿刀");
synchronized(o2){
System.out.println(Thread.currentThread().getName()+"--"+"拿叉");
}
}
synchronized(o2){
System.out.println(Thread.currentThread().getName()+"--"+"拿叉");
synchronized(o1){
System.out.println(Thread.currentThread().getName()+"--"+"拿刀");
}
}
}
}
}