JAVA从入门到精通------基础篇------线程

1、什么是进程

 

程序是静止的,只有真正运行时的程序才能被称为进程

举个例子,我们的每一个软件就是一个进程

单核CPU在任何时间点上,只能运行一个进程,宏观并行,微观串行


2、什么是线程

线程,又称轻量级进程(Light Weight Process) 程序中的一个顺序控制流程,同时也是CPU的基本调度单位,进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程

举个例子:迅雷是一个进程,但是我们用迅雷同时下载多个视频,每一个视频就是用到了一个线程,多个下载任务就是多线程

Java虚拟机是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行。


3、进程与线程的区别

进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位

一个程序运行后至少有一个进程

一个进程可以包含多个线程,但是至少需要有一个线程

进程间不能共享数据段地址,但同进程的线程之间可以


4、线程的组成

CPU时间片:操作系统(OS)会为每个线程分配执行时间

运行数据

堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象

栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈

线程的逻辑代码


5、创建线程

在主线程中,无法传递参数到子线程中 thread1和thread2就是子线程

子线程中的执行结果无法返回到主线程中,因为没有返回值,要通过异步返回

第一种方式、

1、继承Thread类

2、重写(覆盖)run方法

3、创建子类对象

4、调用start()方法

//1、继承子类对象
public class FirstLoop extends Thread {
	
	//2、重写run方法
	@Override
	public void run() {
		for (int i=0; i<=10; i++) {
			System.out.println("i=" + i);
		}
	}
}


//1、继承Thread类
public class SecondLoop extends Thread{
	//2、重写run方法
	@Override
	public void run() {
		for (int j=0; j<=10; j++) {
			System.out.println("j=" + j);
		}
	}
}


public class Test11 {
	public static void main(String[] args) {

		//3、创建子类对象
		FirstLoop first = new FirstLoop();
		
		SecondLoop second = new SecondLoop();
		
		//4、调用start方法
		first.start();
		second.start();
		System.out.println("主线程到此结束");
	}
}

第二种方式、

1、实现Runnable接口

2、覆盖(重写)run()方法

3、创建实现类对象

4、创建线程对象

5、调用start()方法

//实现Runnable接口
public class FirstThread implements Runnable{

	//重写run方法
	@Override
	public void run() {
		for(int i=0;i<=10;i++) {
			//运用Thread里边的currentThread方法中的getName方法来获取线程的名字
			System.out.println(Thread.currentThread().getName() + "-----" + i );
		}
	}

}



public class ThreadDemo1 {
	public static void main(String[] args) {
		
		//让两个线程同时执行FirstThread里的任务
		//创建任务对象也就是实现类对象
		
		//创建实现类对象
		FirstThread ft = new FirstThread();
		
		//创建线程对象
		Thread thread1 = new Thread(ft);
		Thread thread2 = new Thread(ft);
		
		//调用start方法
		thread1.start();
		thread2.start();

     
        //在主线程中,无法传递参数到子线程中 thread1和thread2就是子线程
		//子线程中的执行结果无法返回到主线程中

		System.out.println("主线程到此结束");
	}
}


6、线程的状态

 休眠:

         public static void sleep(long millis)

         当前线程主动休眠millis毫秒

public class FirstThread implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<=10;i++) {
			
			//每执行一次休眠一秒
			try {
				//执行到此,进入阻塞状态,休眠时间结束后自动进入到就绪状态
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			
			//运用Thread里边的currentThread方法中的getName方法来获取线程的名字
			System.out.println(Thread.currentThread().getName() + "-----" + i );
		}
	}

}




public class ThreadDemo1 {
	public static void main(String[] args) {
		
		//Thread.sleep可以运用在任何地方
		//等待10秒之后再运行
		System.out.println("下边是等待10秒之后才开始运行的");
		System.out.println("10000毫秒就是10秒");
		System.out.println();

		try {
			Thread.sleep(10000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		
		FirstThread ft = new FirstThread();
		
		Thread thread1 = new Thread(ft);
		Thread thread2 = new Thread(ft);
		
		thread1.start();
		thread2.start();
		
		System.out.println("主线程到此结束");
	}
}

放弃:

         public static void yield()

         当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

public class FirstThread implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<=10;i++) {
			
			//放弃此次的时间片
			//也就是说CPU的时间片明明已经可以执行我了,但是我放弃,我不需要执行
			//反而回到就绪状态,等待下一次被执行,但是下一次有可能还是执行到我
			//总而言之就是我放弃了但并不代表我不会被执行,不能把我排除在外
			Thread.yield();
			
			//运用Thread里边的currentThread方法中的getName方法来获取线程的名字
			System.out.println(Thread.currentThread().getName() + "-----" + i );
		}
	}

}


public class ThreadDemo1 {
	public static void main(String[] args) {
		
		FirstThread ft = new FirstThread();
		
		Thread thread1 = new Thread(ft);
		Thread thread2 = new Thread(ft);
		
		thread1.start();
		thread2.start();
		
		System.out.println("主线程到此结束");
	}
}

结合:

         public final void join()

         允许其他线程加入到当前线程中 


 7、线程安全问题

public class MyArrayList implements Runnable{
	
	//定义一个数组ary长度为10
	String[] ary = new String[10];
	//定义一个size为0用来做数组下角标
	int size = 0;
	
	
	@Override
	public void run() {
		
		//当其中一个thread(有可能是thread1有可能是thread2)进来将aaa传给数组0然后执行睡眠
		//执行不到size++
		//睡眠执行过程中CPU时间片并不会休息而是发生轮转
		//这个时候另一个thread进来开始执行
		//再次执行一遍 ary[size] = "aaa";
		//所以最后展现的aaa是另一个线程执行出来的而不是第一个
		//因为只执行第二个第一个执行发生了覆盖,所以我们叫线程不安全
		
		ary[size] = "aaa";
		
		//用来让另一个线程进来
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		size++;
	}
	
}


public class TestMyArrayList {
	public static void main(String[] args) {
		MyArrayList mal = new MyArrayList();
		
		Thread thread1 = new Thread(mal);
		Thread thread2 = new Thread(mal);
		
		thread1.start();
		thread2.start();
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//将MyArrayList数组里边的数组存进ary中
		String[] ary = mal.ary;
		for(String str : ary) {
			System.out.print(str + " ");
		}
	}
}


8、锁

线程同步1):

同步代码块:

synchronized(临界资源对象){  //对临界资源对象加锁

    //代码 (原子操作)

}

注:每个对象都有一个互斥锁标记,用来分配给线程的

       只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块

       线程退出同步代码块时,会释放相应的互斥锁标记

public class Ticket implements Runnable{

	//模拟票数
	private int ticket = 100;
	
	@Override
	public void run() {
		while(true) {
			if(ticket <= 0) {
				break;
			}
			
			System.out.println(Thread.currentThread().getName()+"售出了第" + ticket+"张票");
			ticket--;
		}
		
	}
	
}

public class Demo1 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		
        //参数1:线程要执行的任务
		//参数2:线程的名字
		Thread meituan = new Thread(ticket, "美团");
		Thread quna = new Thread(ticket,"去哪儿");
		Thread v12306 = new Thread(ticket, "12306");
				
		meituan.start();
		quna.start();
		v12306.start();
	}
}

 线程不安全,票重复

public class Ticket implements Runnable{

	//模拟票数
	private int ticket = 100;
	
	@Override
	public void run() {
		while(true) {
			if(ticket <= 0) {
				break;
			}
		synchronized ("abc") {
			System.out.println(Thread.currentThread().getName()+"售出了第" + ticket+"张票");
			ticket--;
		}
			
			
		}
		
	}
	
}


public class Demo1 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		
        //参数1:线程要执行的任务
		//参数2:线程的名字
		Thread meituan = new Thread(ticket, "美团");
		Thread quna = new Thread(ticket,"去哪儿");
		Thread v12306 = new Thread(ticket, "12306");
				
		meituan.start();
		quna.start();
		v12306.start();
	}
}

 线程不安全,票出现0和-1

public class Ticket implements Runnable{

	//模拟票数
	private int ticket = 100;
	
	@Override
	public void run() {
		while(true) {
			
		synchronized ("abc") {
			
			if(ticket <= 0) {
				break;
			}
			
			System.out.println(Thread.currentThread().getName()+"售出了第" + ticket+"张票");
			ticket--;
		}
			
			
		}
		
	}
	
}


public class Demo1 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		
        //参数1:线程要执行的任务
		//参数2:线程的名字
		Thread meituan = new Thread(ticket, "美团");
		Thread quna = new Thread(ticket,"去哪儿");
		Thread v12306 = new Thread(ticket, "12306");
				
		meituan.start();
		quna.start();
		v12306.start();
	}
}

 线程同步2):

同步方法:

synchronized 返回值类型 方法名称(形参列表){ //对当前对象 (this)加锁

       //代码 (原子操作)

}

注:只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中

       线程退出同步方法时,会释放相应的互斥锁标记

其实锁的作用就是把锁当中的代码执行完之后再执行下一步,这样才可以保证一个个的执行完毕而不会出现多个线程执行同一个结果的情况 

不会出现这些情况我们就称之为线程同步

	public synchronized int run() {
		int sum = 0;
		return sum;
	}

同步规则

注意:

          只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记

          如果调用不包含同步代码块的方法,或者普通方法时,则不需要锁标记,可直接调用

已知JDK中线程安全的类

      StringBuffer

      Vector

      Hashtable

      以上类中的公开方法,均为synchonized修饰的同步方法


9、经典问题

//总体思路  先确定面包的类然后定义一个id和名字 进行封装 以及toString 让最后显现的是字

                  确定商店类 定义货架长度 然后货架存一个消费者买一个货架满的时候wait等待                    notifyAll来唤醒消费者买   消费者买空了消费者wait等待notifyAll唤醒生产者购买

                   定义生产者 实现Runnable接口 定义shop类型变量 重写run方法开启线程定义一共生产                    多少个面包

                   定义消费者 实现Runnable接口 定义shop类型变量 重写run方法开启线程定义一共购买                    多少个面包

                    new shop  new生产者 实现线程  new消费者 实现线程  开启生产者和消费者线程

//建一个面包类
public class Bread {
	//定义面包的id
	private int id;
	//定义面包的名字
	private String productName;
	
	public Bread() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	public Bread(int id, String productName) {
		super();
		this.id = id;
		this.productName = productName;
	}

	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getProductName() {
		return productName;
	}
	public void setProductName(String productName) {
		this.productName = productName;
	}
	
	@Override
	public String toString() {
		return "Bread [id=" + id + ", productName=" + productName + "]";
	}
}





//超市用来存放面包
//建一个存放面包的货架
public class Shop {
	
	//货架:存放面包对象的数组
	//定义  存放面包货架的容量 也就是能存几个面包
	//breadCons 代表货架
	private Bread[] breads=new Bread[6];
	//数组中存放元素的位置
	//定义  存放面包的位置 也就是通过索引来确定第几个面包
	private int index=0;
	
	//生产者向数组存放元素
	//存放面包
	//为了线程安全将整个方法锁起来
	public synchronized void input(Bread bread){
		//判断当前面包工厂(线程)能否存放元素到数组中
		//大于等于6是因为当是6之后的数就是第七个了
		//货架只能存放6个面包
		//所以要大于等于6
		while(index>=6){
			//数组已满,不能存放,当前线程应该等待
			//如果面包放满了6个,那么就不需要继续放面包了
			//这个时候就需要用线程等待,通过wait方法将之后想放进来的面包等待一下
			//等什么时候有人买了,买了的话货架就有空的了,这个时候就再把等待的面包取出来放货架上
			try {
				//当前线程进入阻塞状态,一直会处于阻塞状态
				//指导被唤醒,唤醒后的线程进入到就绪状态
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//如果没进入到循环中,说明可以存放
		//1、存放到数组中
		//将货架的位置放上面包
		breads[index]=bread;
		//提示谁生产了面包  也就是生产者
		System.out.println(Thread.currentThread().getName()+"生产了面包:"+bread);
		//2、index++
		//索引自加
		index++;
		
		//通知消费者可以消费面包
		//唤醒正在处于wait状态的消费者线程
		this.notifyAll(); //因为wait处于阻塞状态的线程唤醒,从阻塞状态转为就绪状态
	}
	//消费者到数组中获取元素
	//取出面包
	public synchronized void output(){
		//判断是否可以消费面包
		// <=0是因为 当等于0的时候线程进来之后经历过下边的index-- 索引变成了 -1
		//就会造成下角标越界
		while(index<=0){
			//让消费者等待,面包已空
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//下角标-- 
		index--;
		//通过索引锁定买走的是哪个面包
		Bread bread=breads[index];
		//提示消费者买了哪个生产者生产的面包
		System.out.println(Thread.currentThread().getName()+"消费了面包:"+bread);
		//买完了一个就将这一个变为null 这样才能让等待的能放进来
		breads[index]=null;
		//唤醒生产者,可以生产者面包
		//因为有空了,唤醒生产者继续存放
		this.notifyAll();
		
	}
	
}





//生产者
public class Product implements Runnable {
	
	//借用shop把生产者和消费者联系起来
	private Shop shop;
	
	
	public Product() {
		super();
	}

	public Product(Shop shop) {
		super();
		this.shop = shop;
	}

	
	
	public Shop getShop() {
		return shop;
	}

	public void setShop(Shop shop) {
		this.shop = shop;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		//生产面包
		for(int i=0; i<=30; i++){
			shop.input(new Bread(i,Thread.currentThread().getName()));
		}
	}

}



//消费者
public class Consumer implements Runnable {
	
	//借用shop把生产者和消费者联系起来
	private Shop shop;
	
	public Consumer() {
		super();
	}
	
	public Consumer(Shop shop) {
		super();
		this.shop = shop;
	}
	


	
	public Shop getShop() {
		return shop;
	}
	public void setShop(Shop shop) {
		this.shop = shop;
	}
	
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		//消费面包
		for(int i=0; i<=30; i++){
			//消费面包
			shop.output();
		}
	}

}



public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//newshop变量名为huiyou为了放进setshop里边通过shop介质连接在一起
		Shop huiyou=new Shop();
		//创建面包工厂
		Product product=new Product();
		//设定商店的名字
		//上边定义了一个huiyou
		product.setShop(huiyou);
		//传生产的牌子
		Thread taoli=new Thread(product,"桃李面包");
		Thread meisilin=new Thread(product,"美思林面包");
		
		Consumer consumer=new Consumer();
		consumer.setShop(huiyou);
		//传购买者的人
		Thread jiabao=new Thread(consumer,"张三");
		Thread haotian=new Thread(consumer,"李四");
		
		taoli.start();
		meisilin.start();
		jiabao.start();
		haotian.start();
	}

}

 

 

 


 线程通信

等待:

        public final void wait()

        public final void wait(long timeout)  //限制时间

        必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其拥有的          所有锁标记。同时此线程阻塞在o的等待队列中。释放锁。进入等待队列。

通知:

        public final void notify()

        public final void notifyAll()

        必须在对obj加锁的同步代码块中,从obj的Waiting中释放一个或全部线程。对自身没有任何            影响

public class Person1 extends Thread{
	@Override
	public void run() {
		synchronized (Lock.obj1) {
			System.out.println("第一个人拿到了油条,等待拿到豆浆");
			synchronized (Lock.obj2) {
				System.out.println("第一个人又拿到了豆浆");
				System.out.println("第一个人油条、豆浆都拿到   可以吃早餐了");
			}
		}
	}
}


public class Person2 extends Thread{
	@Override
	public void run() {
		synchronized (Lock.obj2) {
			System.out.println("第二个人拿到了豆浆,等待拿到油条");
			synchronized (Lock.obj1) {
				System.out.println("第二个人又拿到了油条");
				System.out.println("第二个人豆浆、油条都拿到   可以吃早餐了");
			}
		}
	}
}


public class Demo2 {
	public static void main(String[] args) {
		Person1 liu = new Person1();
		Person2 zheng = new Person2();
		liu.start();
		try {
			//线程插队,获得了两把锁
			//谁插队优先执行谁
			//解决死锁的思路:确定线程执行的先后顺序
			liu.join();
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		zheng.start();
	}
}


10、总结

线程的创建:

        方式1:继承Thread类

        方式2:实现Runnable接口(一个任务Task),传入给Thread对象并执行

线程安全:

        同步代码块:为方法中的局部代码(原子操作)加锁

        同步方法:    为方法中的所有代码(原子操作)加锁

线程间的通信:

        wait() / wait(long timeout) : 等待

        notify() / notifyAll() : 通知

wait()和sleep()的区别?

1、所属类不同

wait方式是Object类的

sleep是Thread类

2、sleep()可以应用在任何位置

wait() 只能应用在锁中,wait()在执行的过程中会释放锁

sleep()不会释放锁

3、sleep必须指定休眠时间

wait可以指定也可以不指定


11、高级多线程

线程池概念

现有问题:

        线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。

        频繁的创建及销毁线程会增加虚拟机回收频率。资源开销,造成程序性能下降。

线程池:

        线程容器,可设定线程分配的数量上限。

        将预先创建的线程对象存入池中,并重用线程池中的线程对象。

        避免频繁的创建和销毁。

 为什么需要线程池?

  • 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。

  • 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。


12、线程池原理

 线程池原理

线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。

内存溢出:内存满了,没有可用的内存,称为内存溢出

内存泄漏:指的是占用的内存发生了无法回收的情况,称为内存泄漏

案例:jvm的后台线程GC,回收的是new出堆内存空间

垃圾回收算法,计数器算法


13、获取线程池

常用的线程池接口和类(所在包java.util.concurrent):

Executor : 线程池的顶级接口。

ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码。

Executors工厂类 : 通过此类可以获得一个线程池。

通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量

通过newCachedtThreadPool()获得动态数量的线程池,如不够则创建新的,无上限

固定数量的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//写一个抢票软件 五个软件通过三个线程来抢

public class Demo4 {
	public static void main(String[] args) {
		//创建线程池
		//1、固定数量的线程池
		
		//我们想创建线程时一般会创建也就是start()很多,但是这样的话容易消耗资源
		//所以我们就固定几个线程,哪个线程想用线程的话就通过这几个固定的线程进行使用
		
		//括号里边放3代表的就是这个线程池里边有三个线程
		//也就是说无论需要执行几个线程只能用这3个线程
		//如果5个线程A B C D E那就是3个线程A B C 使用这3个线程,剩下的两个线程D E只能等待,
		//直到用线程的三个线程A B C其中谁线程用了了才能轮到这两个没用过的线程 D E进行使用	
		ExecutorService es = Executors.newFixedThreadPool(3);
		
		Runnable ticket = new Runnable() {
			
			//一共100张票
			private int ticket = 100;
			@Override
			public void run() {
				while(true) {
					if(ticket <= 0) {
						break;
						
					}
					System.out.println(Thread.currentThread().getName() 
							+ "售出了第" + ticket + "张票");
					ticket--;
				}
			}
		};
		
		for(int i=0;i<5;i++) {
			//通过ExecutorService里的submit方法将需要用的任务传进来
			es.submit(ticket);
		}
	}
}

 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//写一个抢票软件 五个软件通过三个线程来抢

public class Demo4 {
	public static void main(String[] args) {
		
		//创建线程池
		//2、获得动态数量的线程池
		//也就是说需要几个线程,自动就生成几个线程
		ExecutorService es = Executors.newCachedThreadPool();
		
		Runnable ticket = new Runnable() {
			
			//一共100张票
			private int ticket = 100;
			@Override
			public void run() {
				while(true) {
					if(ticket <= 0) {
						break;
						
					}
					System.out.println(Thread.currentThread().getName() 
							+ "售出了第" + ticket + "张票");
					ticket--;
				}
			}
		};
		
		for(int i=0;i<5;i++) {
			//通过ExecutorService里的submit方法将需要用的任务传进来
			es.submit(ticket);
		}
	}
}


14、Callable接口

public interface Callable<V>{

        public V call() throws Exception;

}

JDK5加入,与Runnable接口相似,实现之后代表一个线程任务

Callable具有泛型返回值、可以声明异常。

--------------> 实现多线程的第三种方式

配合Future接口使用


15、Future接口

需求:使用两个线程并发计算1~50、51~100的和在再进行汇总统计

           接收call方法的回收值

概念:异步接收ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值

方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)

什么是异步?什么是同步?

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//实现多线程的第三种方式
//callanle接口实现多线程 
//与runnable的区别是Callanle有返回值,可以把子线程里的结果返回回来  可以是任意对象类型
public class Demo6 {
	public static void main(String[] args) {
		
		//用线程计算1~100之间的数字之和
		
		//必须要使用引用数据类型
	Callable<Integer> com1 = new Callable<Integer>() {

		
		@Override
		public Integer call() throws Exception {
			
			//先计算1~50的数字之和
			int sum1 = 0;
			for(int i=0;i<=50;i++) {
				sum1 = sum1 + i;
			}
			return sum1;
			
		}
		//匿名内部类
	};
	
	Callable<Integer> com2 = new Callable<Integer>() {

		@Override
		public Integer call() throws Exception {
			//在计算51~100的数字之和
			int sum2 = 0;
			for(int i=51;i<=100;i++) {
				sum2 = sum2 + i;
			}
			return sum2;
			
		}
		//匿名内部类
	};
	
    //Future是一个接口
    //是Future的实现类
	//借用FutureTask接口来实现线程
	//com1是1~50的
	FutureTask<Integer> task1 = new FutureTask<Integer>(com1);
	
	//创建线程类
	Thread t1 = new Thread(task1);
	t1.start();
	
	FutureTask<Integer> task2 = new FutureTask<Integer>(com2);
	Thread t2 = new Thread(task2);
	t2.start();
	
	try {
		//借用task1.get()方法才能获取到com1返回的sum1然后再定义一个sum1变量来接收;
		int sum1 = task1.get();
		//借用task2.get()方法才能获取到com2返回的sum2然后再定义一个sum2变量来接收;
		int sum2 = task2.get();
		
		//输出和
		System.out.println("1~100的数字之和:" + (sum1 + sum2));
		
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (ExecutionException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	
	}
}


16、Lock接口

JDK5加入,与synchronized比较,显示定义,结构更灵活

提供更多实用性方法,功能更强大,性能更优越

常用方法:

             void lock() //获取锁,如锁被占用,则等待。

             boolean tryLock()  //尝试获取锁(成功返回true。失败返回false,不阻塞)。

             void unlock()  //释放锁


17、重入锁


18、读写锁

ReentrantReadWriteLock:

        一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。

        支持多次分配读锁,使多个读操作可以并发执行。

互斥规则:

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class Myclass {

		//获取能够读锁和写锁
		private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
		//读锁 读读操作 -- 不阻塞  读写操作 --- 阻塞
		private ReadLock rl = rwl.readLock();
		//写写操作
		private WriteLock wl = rwl.writeLock();
		
		private int value;

		public int getValue() {
			//读操作
			//加锁
			rl.lock();
			
			try {
				Thread.sleep(1000);
				return value;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				//手动释放读锁-----解锁
				rl.unlock();
			}
			
			return 0;
			
		}

		public void setValue(int value) {
			//写操作
			//写锁
			wl.lock();
			
			try {
				Thread.sleep(1000);
				this.value = value;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				//手动释放写锁----解锁
				wl.unlock();
			}
		}
		
		
	
}





import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestReadWriteLock {
	public static void main(String[] args) {
		//new对象
		Myclass mc = new Myclass();
		
		Runnable task1 = new Runnable() {
			
			@Override
			public void run() {
				//写操作
				//因为Myclass中的value是int类型所以随便赋值
				mc.setValue(1);
				
			}
		};
		
		Runnable task2 = new Runnable() {
			
			@Override
			public void run() {
				//读操作
				
				mc.getValue();
				
			}
		};
		
		//创建线程池
		//创建50个线程
		ExecutorService ex = Executors.newFixedThreadPool(50);
		long beginTime = System.currentTimeMillis();
		
		for(int i=0; i<2; i++) {
			ex.submit(task1);
		}
		
		for(int i=0; i<48; i++) {
			ex.submit(task2);
		}
		
		System.out.println("用时:" + (System.currentTimeMillis()-beginTime));
	}
}


19、ReentrantReadWriteLock


20、线程安全的集合


21、Collections中的工具方法

•    Collections工具类中提供了多个可以获得线程安全集合的方法。
•    public static <T> Collection<T> synchronizedCollection(Collection<T> c)
•    public static <T> List<T> synchronizedlist(List<T> list)
•    public static <T> Set<T> synchronizedSet(Set<T> s)
•    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
•    public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
•    public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
•    JDK1.2提供, 接口统一、 维护性高,但性能没有提升, 均以synchonized实现。


22、CopyOnWriteArrayList

线程安全的ArrayList,加强版读写分离。

写有锁,读无锁,读写之间不阻塞,优于读写锁

写入时,先copy一个容器副本、再添加新元素,最后替换引用。

使用方法与ArrayList无异


23、CopyOnWriteArraySet

·    线程安全的Set, 底层使用CopyOnWriteArraylist实现。
·    唯一不同在于, 使用addIf Absent()添加元素, 会遍历数组,
·    如存在元素, 则不添加(扔掉副本)。


24、ConcurrentHashMap

·    初始容量默认为16段 (Segment) ,使用分段锁设计。
·    不对整个Map加锁, 而是为每个Segment加锁。
·    当多个对象存入同一个Segment时, 才需要互斥。
·    最理想状态为16个对象分别存入16个Segment, 井行数量16。
·    使用方式与HashMap无异。


25、Queue接口(队列)

常用方法:
  抛出异常:
•    boolean add(E e) //顺序添加一个元素(到达上限后, 再添加则会抛出异常)
•    E remove()/成得第一个元素井移除(如果队列没有元素时, 则抛异常)
•    E elementO /月天得第一个元素但不移除(如果队列没有元素时, 则抛异常)
 返回特殊值:推荐使用
•    boolean offer(E e) //顺序添加一个元素 (到达上限后, 再添加则会返回false)
•    E pollQ /获得第一个元素并移除 (如果队列没有元素时, 则返回null)
•    E keepQ /获得第一个元素但不移除 (如果队列没有元素时, 则返回null)


26、ConcurrentlinkedQueue

·    线程安全、 可高效读写的队列, 高井发下性能最好的队列。
·    无锁、 CAS比较交换算法, 修改的方法包含三个核心参数(V,E,N)

V:要更新的变量、 E:预期值、 N:新值。

只有当V==E时, V=N;否则表示已被更新过, 则取消当前操作。


27、BlockingQueue接口(阻塞队列)

Queue的子接口, 阻塞的队列, 增加了两个线程状态为无限期等待的方法。

·    方法:
•    void put(E e)//将指定元素插入此队列中, 如果没有可用空间, 则等待。
•    E take()/月天取井移除此队列头部元素, 如果没有可用元素, 则等待。
·    可用于解决生产生、 消费者问题。


28、阻塞队列

ArrayBlockingQueue:

数组结构实现, 有界队列。 (手工固定上限)

•    LinkedBlockingQueue:
·    链表结构实现, 无界队列。 (默认上限Integer.MAX_VALUE)


29、总结

•    ExecutorService线程池接口、 Executors工厂。
•    Callable线程任务、Future异步返回值。
•    Lock、 Reentrantlock重入锁、 ReentrantReadWritelock读写锁。
•    CopyOnWriteArraylist线程安全的Arraylist。
•    CopyOnWriteArraySet线程安全的Set。
•    ConcurrentHashMap线程安全的HashMap。
•    ConcurrentlinkedQueue线程安全的Queue。
•    ArrayBlockingQueue线程安全的阻塞Queue。 (生产者、 消费者)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值