多线程并发库_<一>

多线程并发库<一>

前言:

    学习不断积累的过程,有些知识虽然现在还不知道有什么用,但却可以为以后学习新知识打下基础,并在关键时候能解决大问题,更重要的是可以增强自信心!

一、传统线程机制的回顾

    按照传统的方式,创建一个线程有两种方式:

    1、创建一个类继承Thread类,即创建一个Thread类的子类;

    2、创建一个类实现Runnable接口,并把该类作为Thread类的构造参数传入;

    在现实的开发中,一般使用的是第二种创建线程的方式,因为它可以避免的Java中单继承的局限性,并且更加体现了面向对象的思想。

    当创建一个线程时,往往是要启动该线程执行特定的任务,那么把要执行的任务写在run()方法中,并且调用对象的start()方法启动线程,即可。

下面代码体现:

package itheima.thread.day01;

public class TranditionThread {

	public static void main(String[] args) {
//		创建线程的第一中形式!
//		匿名内部类的形式
		Thread thread = new Thread(){
			@Override
			public void run() {
				for(int x=0;x<100;x++){
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName());
				}
			}
		};
//		启动线程
		thread.start();
		for(int i=0;i<100;i++){
			System.out.println(Thread.currentThread().getName()+"..........");
		}
//		创建线程的第二种形式
		Thread thread2 =new Thread(new Runnable(){
			@Override
			public void run() {
				
				for(int x=0;x<100;x++){
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName());
				}
			}
		});
//		启动线程
		thread2.start();
	}
}

二、定时器

    在JDK1.5以后,Java提供了定时器的API,对应的类为Timer。Timer一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

    创建定时器,然后调用Schedule方法即可;TimerTask类用于存放定时器要执行的任务,并且把该任务作为参数传给Schedule方法即可。

下面代码体现:

package itheima.thread.day01;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TraditionalTimerTest {
	private static int count =0;
	public static void main(String[] args) {
		
//		定时器,然后调度 任务Task、开始时间、间隔时间
	/*	new Timer().schedule(new TimerTask(){
			@Override
			public void run() {
				System.out.println(new Date().getSeconds());
				System.out.println("bombing.....");
			}
			
		}, 10,1000);//连环炸
	*/
	/*	while(true){
			System.out.println(new Date().getSeconds());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}*/	
//		连环炸弹,每隔2、4秒后爆炸,不断循环
		class MyTimerTask extends TimerTask{
			
		@Override
		public void run() {
			count =(count+1)%2;
			System.out.println(new Date().getSeconds());
			System.out.println("bombing");
//			创建一个新的炸弹
			new Timer().schedule(new MyTimerTask(),2000+2000*count);
		}	
		}
		
		new Timer().schedule(new MyTimerTask(), 2000);
	}
}

三、传统线程的同步互斥与通信

    同步:在多线程中,当创建一个线程时,往往要给与该线程特定的执行任务,那么这些执行任务的代码就写在run方法中;但是当多个线程同时操作同一份数据时,却很容易出现线程的安全问题,比如:在售票系统中,有多个窗口同时售票,那么每个窗口对应的是一个独立的线程,票就是需要共同操作的数据,为了不卖出相同票号的多张票,就必须要确定一个线程操作完了票数据之后另一个线程才能操作票数据,即票数据不能被多个线程同时操作。

    同步互斥技术能解决这种线程安全问题。同步互斥可以通过同步函数同步代码块体现,当被同步的函数是静态函数时,使用的锁是本类的字节码,非静态函数所使用的锁是当前对象,即this。在同步代码块中,锁可以任意指定,但要实现同步互斥,锁必须是同一个锁。

下面代码体现:

package itheima.thread.day01;

public class TraditionalThreadSynchronized {

	public static void main(String[] args) {
		new TraditionalThreadSynchronized().init();
	}
	private void init(){
		
		final Outputer outputer = new Outputer();
		new Thread(new Runnable(){

			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output("22222222");
				}
			}
		}).start();
		
		new Thread(new Runnable(){

			@Override
			public void run() {
					while(true){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output("1111111111111");
				}
			}
		}).start();
	}
	class Outputer{
//		当多个线程同时操作同一段代码时,有可能一个线程还没执行完,
//		另一个线程去抢着去执行,这,就会出现多线程的不安全问题!
//		同步函数:锁:this,当前对象;静态函数:本类的字节码
//		同步代码块:必须保证各个线程使用的是同一锁,
		public  synchronized void output(String name){
			int len = name.length();
			for(int i=0;i<len;i++){
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
	}
}

    通信:其实就是多个线程同时在操作一份数据,但是操作的动作不一样,所以才需要线程之间的进行通信。通信必须以同步互斥为前提,通信可以通过等待唤醒机制完成,wait()、notify()、notifyAll()。

下面代码体现:

package itheima.thread.day01;
/*
 * 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,
 * 接着再回到主线程又循环100,如此循环50次
 * */
public class TraditionalThreadCommunication {

	public static void main(String[] args) {
		
		final Bussiness business = new Bussiness();
//		创建一个线程
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i=1;i<=50;i++){
					business.sub(i);
				} 
			}		
		}).start();
//		主线程
		for(int i=1;i<=50;i++){
			business.main(i);
		}
	}	
}

class Bussiness{
	private boolean bShouldSub = true;
//	互斥
	public synchronized void sub(int i){
		while(bShouldSub){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j=1;j<=10;j++){
			System.out.println("sub thread sequece of"+j+", loop of "+i);
		}
		bShouldSub = true;
		this.notifyAll();//唤醒的是其中一个线程
	}
//	互斥
	public synchronized void main(int i){
		while(!bShouldSub){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j=1;j<=100;j++){
			System.out.println("main thread sequece of"+j+", loop of "+i);
		}
		bShouldSub = false;
		this.notifyAll();//唤醒的是其中一个线程
	}
}

四、多个线程访问共享对象和数据的方式

     如果每个线程执行代码相同,可以使用同一个Runnable对象,这个Runnable对象中存储的就是那个共享数据。

    当每个线程执行的代码不同,可以用不同的Runnable对象,有以下两种方式实现这些Runnable对象之间的数据共享:

    1、将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

    2、这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

    上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

    总之:要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

下面代码体现:

package itheima.thread.day01;

public class MultiThreadShareData {
//	设计4个线程,其中两个线程每次j增加1,另外两个线程对j每次减少1,
	public static void main(String[] args) {
//		共享数据
		final ShareData1 data1 = new ShareData1();
//		两个线程同时操作一个数据
		new Thread(new MyRunnable1(data1)).start();
		new Thread(new MyRunnable2(data1)).start();
		
		new Thread(new Runnable(){	 
			@Override
			public void run() {
				data1.decrement();
			}	
		}).start();
		
		new Thread(new Runnable(){
			@Override
			public void run() {
				data1.increment();
			}	
		}).start();
	}
}

class MyRunnable1 implements Runnable{
//	共享数据
	private ShareData1 data1;
	
	public MyRunnable1(ShareData1 data1){
		this.data1 = data1;
	}
	@Override
	public void run() {
		data1.decrement();
	}
}

class MyRunnable2 implements Runnable{
//	共享数据
	private ShareData1 data1;
	
	public MyRunnable2(ShareData1 data1){
		this.data1 = data1;
	}
	@Override
	public void run() {
		data1.increment();
	}	
}

class ShareData1/* implements Runnable*/{
/*	
	private int count=100;
	@Override
//	如果多个线程要执行的代码相同,可以放在同一个Runnable对象中
	public void run() {
		while(true){
			count--;
		}	
	}*/
	private int j=100;
//	要被多线程操作
	public	synchronized  void increment(){
		j++;
		System.out.println(Thread.currentThread().getName()+" j++  "+j);
	}

	public synchronized void decrement(){
	 	j--;
	 	System.out.println(Thread.currentThread().getName()+" j--  "+j);
	}	
	
}

五、线程范围的共享变量

    线程范围的共享变量:多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行的又是另外一份数据。可以先把要共享的数据与线程的名称以键值对的形式存储在Map集合中,然后再取出。原理如下图所示:

 

下面代码体现:

package itheima.thread.day01;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class ThreadScopeShareData {
//	线程范围内的数据共享
	private static int data =0;
//	用于存储线程名、数据的Map集合
	private static Map<Thread,Integer> threadData = new HashMap<Thread,Integer>();
	
	public static void main(String[] args) {
//		创建两个线程
		for(int i=0;i<2;i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt(10)+1;
					System.out.println(Thread.currentThread().getName()
							+" has put data : "+data);
//					将线程名、数据存放到集合中
					threadData.put(Thread.currentThread(), data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
//	静态内部类
	static class A{
		public void get(){
			int data = threadData.get(Thread.currentThread());
			System.out.println("A from "+Thread.currentThread().getName()
					+" get data : "+data);
		}	
	}
	static class B{
		public void get(){
			int data = threadData.get(Thread.currentThread());
			System.out.println("B from "+Thread.currentThread().getName()
					+" get data : "+data); 
		}
	}
}

    ThreadLocal类专门为实现线程范围的数据共享的API,每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

下面代码演示:

package itheima.thread.day01;

import java.util.Random;

public class ThreadLocalTest {
	
	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
	private static ThreadLocal<MyThreadScopeData> myThreadScopeData =
			new ThreadLocal<MyThreadScopeData>();
	
	public static void main(String[] args) {
		for(int i=0;i<2;i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt(10)+1;
					System.out.println(Thread.currentThread().getName()
							+" has put data : "+data);
//					把数据存到当前线程中
					x.set(data);
					
				/*	MyThreadScopeData myData = new MyThreadScopeData();
					myData.setName("name : "+data);
					myData.setAge(data);
					myThreadScopeData.set(myData);
					*/
					MyThreadScopeData.getThreadInstance().setName("name:"+data);
					MyThreadScopeData.getThreadInstance().setAge(data);
					new A().get();
					new B().get();
					
				}	
			}).start();
		}
	}
	
	static class A{
		public void get(){
//			取当前线程中的数据
			int data =x.get(); 
			System.out.println("A from "+Thread.currentThread().getName()
					+" get data : "+data);
			
			/*MyThreadScopeData myData = myThreadScopeData.get();
			System.out.println("A from "+Thread.currentThread().getName()
					+" getMyData: "+myData.getName()+" , "+
					myData.getAge()
					);*/
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("A from "+Thread.currentThread().getName()
					+" getMyData: "+myData.getName()+" , "+
					myData.getAge()
					);
		}		
	}
	static class B{
		public void get(){
//			取当前线程中的数据
			int data = x.get();
			System.out.println("B from "+Thread.currentThread().getName()
					+" get data : "+data); 
			
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("B from "+Thread.currentThread().getName()
					+" getMyData: "+myData.getName()+" , "+
					myData.getAge()
					);
		}
	}
}

class MyThreadScopeData{
	private String name;
	private int age;
	
//	单例模式:1、构造函数私有化,2、对外提供获取该对象的方法	
	private MyThreadScopeData(){}
	public static MyThreadScopeData getThreadInstance(){
		MyThreadScopeData instance = map.get();
		if(instance == null){
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}
	
//	private static MyThreadScopeData instance = new MyThreadScopeData();
	
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值