java多线程及线程安全详解

Posted nianzhilian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java多线程及线程安全详解相关的知识,希望对你有一定的参考价值。

为什么要使用多线程:

单线程只能干一件事  而多线程可以同时干好多事(将任务放到线程里执行  效率高)

而所谓同时干并不是真正意义上的同时   只是(这里就叫CPU)cpu在每个线程中随机切换来执行 线程中要干的活

多线程编写:

1)第一种:(线程类)

class Stu1 extends Thread{

  //重写 run方法 

}

调用:Stu1 su = new Stu1();

su.start()//内部会自动调用run方法    把run方法放到线程上调用

2)第二种:普通任务类(由于第一种类只能是单继承 就没法实现继承其他父类并且继承线程类 所以第一种方法扩展性比较差)

直接创建线程对象    线程内干的事  放到一个自定义对象里面实现   (用到修饰设计模式)

底层代码

class Thread{

private Runnable r

public void Thread(Runnable r){ //利用有参构造将自定义对象传进来

this.r = r;

}

public void start(){

  r.run();

}

}

class Stu implements Runnable{}  通过实现接口   线程类底层start调用的run方法 实际就是我们自定义类中的run方法

Thread th = new  Thread(new Stu())  //直接创建线程对象

th.start();

我们用第二种方法创建多线程如下:

public static void main(String[] args) {
//		不同线程干同一件事
		SaleWindow sw = new SaleWindow();
		Thread t1 = new Thread(sw);
		Thread t2 = new Thread(sw);
		t1.setName("窗口A");
		t2.setName("窗口B");
		t1.start();
		t2.start();
	}

 

public class SaleWindow implements Runnable {
	private int ticketCount = 10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
//		多个窗口卖票
		for(int i = 0;i<10;i++){
			if(ticketCount>0){
				//字符串拼接信息   变量+""  就可以拼接成字符串
				System.out.println(Thread.currentThread().getName()+"卖出"+ticketCount+"张票");
				ticketCount--;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
//					e.printStackTrace();
				}
			}
		}
	}
	
}

 最后结果图:

技术分享图片

从运行结果来看:同一张票卖给了二个人  这是在现实生活中不允许的 这样就会产生线程安全问题。

而产生线程安全问题的有三个要素必须同时满足才会产生线程安全:

1、必须有共享资源
2、必须是多线程环境
3、每个线程都适用了共享资源

而上面的例子:票是共享资源、又是多线程环境、线程执行任务的时候又使用了共享资源 所以会产生线程安全

怎么解决线程安全?

解决线程安全其核心:就是将某一个线程中的任务给锁(同步锁)起来,这个时候JVM就不得不等到本任务里的代码执行完以后在去执行另外一个线程里的任务。

二种方法:

1、同步代码块:

public class SaleWindow implements Runnable {
	private int ticketCount = 10;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		// 多个窗口卖票
		for (int i = 0; i < 10; i++) {//可以随意设置锁
			synchronized (this) {
				if (ticketCount > 0) {
					// 字符串拼接信息 变量+"" 就可以拼接成字符串
					System.out.println(Thread.currentThread().getName() + "卖出"
							+ ticketCount + "张票");
					ticketCount--;
					try {
						Thread.sleep(500);//每隔500毫秒 线程休眠 随后自己自动会唤醒(目的是为了调节线程速度)
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						// e.printStackTrace();
					}
				}
			}
		}
	}

}

 2、同步方法:

public class SaleWindow implements Runnable {
	private int ticketCount = 10;
     //默认固定的锁对象this
//将产生线程安全的代码封装到方法里并设置成同步方法 public synchronized void syncB() { if (ticketCount > 0) { // 字符串拼接信息 变量+"" 就可以拼接成字符串 System.out.println(Thread.currentThread().getName() + "卖出" + ticketCount + "张票"); ticketCount--; try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block // e.printStackTrace(); } } } @Override public void run() { // TODO Auto-generated method stub // 多个窗口卖票 for (int i = 0; i < 10; i++) { syncB(); } } }

 结果图:

技术分享图片

最后结果每个窗口不能卖同样的票解决了线程安全问题

线程之间的通信

(ps:线程之间的通信和线程安全无关联二者不是一回事)

怎样才能让二个线程之间进行通信?

线程之间是相互独立的互不联系 而真正意义上的通信是通过中间件(同步锁 必须是同一把锁)来达到线程之间通信的目的

案例:二个线程来回交替输出一条数据(意思就是必须按照我说一句你说一句的这个规则走)

class BingB extends Thread {
	public void run(){
		for(int i = 0;i<5;i++){
			System.out.println("冰冰美,如花丑");
		}
	}
}

class RuH extends Thread{
	public void run(){
		for(int i = 0;i<5;i++){
			System.out.println("如花美,冰冰丑");
		}
	}
}

public class Test {
	public static void main(String[] args) {
		BingB bb = new BingB();
		RuH rh = new RuH();
		bb.start();
		rh.start();
	}
}

 结果图:此结果不是交替出现的

技术分享图片

要想达到交替的目的代码如下:

class MyLock{
	static Object o = new Object();
}

class BingB extends Thread {
	public void run(){
		for(int i = 0;i<5;i++){
			//加锁(意思是必须等到本线程的任务代码执行完以后才去执行别的线程的代码)
			synchronized (MyLock.o) {
				System.out.println("冰冰美,如花丑");
				MyLock.o.notify();//唤醒另一个线程(这里表示如花线程)
				try {
					MyLock.o.wait();//暂时彻底休眠本线程(不会自动唤醒 需要手动唤醒)   同时解锁  阻塞线程 代码就不会往下继续走  jvm会切换到另外一个线程中去
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
				}
			}
		}
	}
}

class RuH extends Thread{
	public void run(){
		for(int i = 0;i<5;i++){
			synchronized (MyLock.o) {
				System.out.println("如花美,冰冰丑");
				MyLock.o.notify();//唤醒另一个线程(这里表示如冰冰程)
				try {
					MyLock.o.wait();//暂时彻底休眠本线程   同时解锁  阻塞线程
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

public class Test {
	public static void main(String[] args) {
		BingB bb = new BingB();
		RuH rh = new RuH();
		bb.start();
		rh.start();
	}
}

 技术分享图片

多线程状态流程图:

技术分享图片

写到这里有关线程的基本讲解完毕,如果内容有我自己理解错误的地方还请各位大神指教,小弟不吝求教!




以上是关于java多线程及线程安全详解的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程:线程同步详解

Java线程 — 线程同步及安全问题

Java线程池详解

Java并发多线程编程——集合类线程不安全之HashMap的示例及解决方案

Java并发多线程编程——集合类线程不安全之HashSet的示例及解决方案

Java多线程中joinyieldsleep方法详解