Java 多线程同步和多线程安全

一,多线程同步

1.1,多线程同步定义:

一次只有一个线程可以读写共享变量。当有一个线程正在访问共享变量时,其他线程应该等到第一个线程完成之后再访问。并且多个线程不会干扰。(多个线程同时操作一个对象,在各种不同情况下,都不会造成不同的后果。)

注意区分这几个概念:线程 多线程 多线程并发 多线程安全 多线程同步

并发(concurrency)简单来说,就是cpu在同一时刻要执行多个任务。Java并发则由多线程实现的;

1.2,什么情况下需要同步

       当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.

       多线程同步之间的关系:没多线程环境就不需要同步,因为单线程本来就是同步执行的;有多线程环境也不一定需要同步。 

1.3,多线程同步的几种方式

1.3.1,Synchronized关键字

  • 使用synchronized必须有一些条件:

        1.必须要有两个或者两个以上的线程需要发生同步。

        2.多个线程想同步,必须使用同一把锁

        3.保证只有一个线程进行执行

  • synchronized原理:

        1.首先有一个线程已经拿到了锁,其他线程已经有cup执行权,一直排队,等待释放锁。

        2.锁是在什么时候释放?代码执行完毕或者程序抛出异常都会被释放掉。

        3.锁已经被释放掉的话,其他线程开始进行抢锁(资源竞争),谁抢到谁进入同步中去,其他线程继续等待。

  • 弊端:效率非常低,多个线程需要判断锁,比较消耗资源,抢锁的资源。

synchronized使用有实例方法中的同步代码块、静态方法中的同步代码块、实例同步方法、静态同步方法;

  • 实例方法中的同步代码块

       案例:

public class SynchronizedDemo2 {
	
	public static void main(String[] args) {
		final OutPrint3 op = new OutPrint3();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				while(true){
					op.out1();
				}
			}
		}.start();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				while (true) {
					op.out2();
				}
			}
		}.start();
	}
	

}

//实例方法中的同步代码块,锁对象可以是任意对像,也可以是this或者字节码对象,但是要保证多线程使用的锁对象是同一个;
class OutPrint3{
	public Object ob = new Object();
	public void out1(){
		synchronized(ob){
			System.out.print("窗");
			System.out.print("前");
			System.out.print("明");
			System.out.print("月");
			System.out.print("光");
			System.out.print("\r\n");
		}
	}
	
	public void out2(){
		synchronized (ob) {
			System.out.print("疑");
			System.out.print("是");
			System.out.print("地");
			System.out.print("上");
			System.out.print("霜");
			System.out.print("\r\n");
		}
		
	}
}
  • 静态方法中的同步代码块
public class SynchronizedDemo2 {
	
	public static void main(String[] args) {
		final OutPrint3 op = new OutPrint3();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				while(true){
					op.out1();
				}
			}
		}.start();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				while (true) {
					op.out2();
				}
			}
		}.start();
	}
	

}

//静态方法中的同步代码块,锁对象可以是静态成员变量对象,也可以是字节码对象;条件是多线程使用的锁对象是同一把锁;
class OutPrint3{
	public static Object ob = new Object();
	public static void out1(){
//		synchronized(ob){
		synchronized(OutPrint3.class){
			System.out.print("窗");
			System.out.print("前");
			System.out.print("明");
			System.out.print("月");
			System.out.print("光");
			System.out.print("\r\n");
		}
	}
	
	public void out2(){
//		synchronized (ob) {
		synchronized (OutPrint3.class) {
			System.out.print("疑");
			System.out.print("是");
			System.out.print("地");
			System.out.print("上");
			System.out.print("霜");
			System.out.print("\r\n");
		}
	}
}
  • 实例同步方法
public class SynchronizedDemo3 {
	
	public static void main(String[] args) {
		final OutPrint4 op = new OutPrint4();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				while(true){
					op.out1();
				}
			}
		}.start();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				while (true) {
					op.out2();
				}
			}
		}.start();
	}
	

}

//实例同步方法,锁对象是this,条件是多线程使用的锁对象是同一把锁,才能实现多线程同步
class OutPrint4{

	public synchronized void out1(){
		System.out.print("窗");
		System.out.print("前");
		System.out.print("明");
		System.out.print("月");
		System.out.print("光");
		System.out.print("\r\n");
	}
	
	public synchronized void out2(){
		System.out.print("疑");
		System.out.print("是");
		System.out.print("地");
		System.out.print("上");
		System.out.print("霜");
		System.out.print("\r\n");
	}
}
  • 静态同步方法
public class SynchronizedDemo3 {
	
	public static void main(String[] args) {
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				int i = 0;
				while(true){
					++i;
					if(i<2000)
						OutPrint4.out1();
					else
						break;
				}
			}
		}.start();
		new Thread(){
			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				int i = 0;
				while(true){
					++i;
					if(i<2000)
						OutPrint4.out2();
					else
						break;
				}
			}
		}.start();
	}
}

//静态同步方法,锁对象是字节码对象,条件是多线程使用的锁对象是同一把锁,才能实现多线程同步;
class OutPrint4{

	public static synchronized void out1(){
		System.out.print("窗");
		System.out.print("前");
		System.out.print("明");
		System.out.print("月");
		System.out.print("光");
		System.out.print("\r\n");
	}
	
	public static synchronized void out2(){
		System.out.print("疑");
		System.out.print("是");
		System.out.print("地");
		System.out.print("上");
		System.out.print("霜");
		System.out.print("\r\n");
	}
}

以上四种主要实现多线程同步输出打印功能,如下图:

下图是没有实现多线程同步的输出,可以看出代码块没同步,导致输出语序错乱;

1.3.2,ReentrantLock锁机制

1.3.3,特殊域变量volatile实现线程同步

1.3.4,ThreadLocal 局部变量实现线程同步

二,多线程安全

2.1,多线程并发操作同一数据时, 就有可能出现线程安全问题

2.2,解决办法:

使用同步技术可以解决线程安全问题, 把操作数据的代码进行同步, 不要多个线程一起操作;

2.3,多线程不安全案例:铁路售票,一共100张,通过三个窗口卖完.

以下代码有个问题就是:创建了三个窗口(线程)每个窗口都有自己的一百张票(ticketCount);所以要想三个窗口共享100张票,就需要把ticketCount成员变量改为静态的,所有Ticket对象共享;

public class Thread_ticket {
	
	public static void main(String[] args) {
		new Ticket("窗口1").start();
		new Ticket("窗口2").start();
		new Ticket("窗口3").start();
	}
}


class Ticket extends Thread{
	public int ticketCount = 100;
	
	public Ticket(String name) {
		super();
		this.setName(name);
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		while(ticketCount>0){
			System.out.println(this.getName()+"...出售第" + (100-ticketCount+1) +"张票");
			ticketCount--;
		}
	}
}

修改ticketCount成员变量为静态的:这样一处改变处处变;

class Ticket extends Thread{
    
	public static int ticketCount = 100; //加上static修饰变成静态变量,所有Ticket对象共享;
	
	public Ticket(String name) {
		super();
		this.setName(name);
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		while(ticketCount>0){
                try {
			    Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
            if(ticketCount>0){			
			    System.out.println(this.getName()+"...出售第" + (100-ticketCount+1) +"张  票");
			    ticketCount--;
            }
		}
	}
}

打印输出:

原因分析:线程2卖第100张票的时候,执行到休眠方法休眠了;接着线程100也开始了卖第1张票,执行到休眠方法是也休眠;最后线程3也开始卖第100张票,执行到休眠方法开始休眠;这是线程2休眠结束卖了第100票,ticketCount--还没执行,这是线程1休眠结束了也卖了第100张票,然后线程1和线程2都卖了第100张票结束时ticketCount= -1,所以等线程3休眠结束时,就卖了第102张票了;

以上就是多线程操作同一个数据时,出现了线程安全问题;

解决办法:线程同步;例如通过同步代码块解决:

public class ThreadTicket3 {

	public static void main(String[] args) {
		new Ticket3("窗口1").start();
		new Ticket3("窗口2").start();
		new Ticket3("窗口3").start();
	}
}

class Ticket3 extends Thread {
	public static int ticketCount = 100; // 加上static修饰变成静态变量,所有Ticket对象共享;

	public Ticket3(String name) {
		super();
		this.setName(name);
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		while (ticketCount > 0) {
			synchronized (Ticket3.class) {//Ticket.class字节码对象保证了所有线程使用的时同一把锁
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if (ticketCount > 0) {
					System.out.println(this.getName() + "...出售第"
							+ (100 - ticketCount + 1) + "张票");
					ticketCount--;
				}
			}
		}

		//第二种逻辑
//		while (true) {
//			synchronized (Ticket3.class) {
//				if (ticketCount <= 0) {
//					break;
//				}
//				try {
//					Thread.sleep(10);
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//				System.out.println(this.getName() + "...出售第"
//						+ (100 - ticketCount + 1) + "张票");
//				ticketCount--;
//			}
//		}
	}
}

总结,多线并发有可能会造成多线程安全问题,可以使用多线程同步技术解决线程安全问题;以上已经介绍了同步技术,接着介绍了多线程并发造成多线程安全问题及使用同步技术解决多线程安全问题;

发布了93 篇原创文章 · 获赞 50 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/ezconn/article/details/100060812