1.什么是进程
真正运行时的程序,才被称为进程。
单核cpu一次只能运行一个进程;宏观并行,微观串行。
2.什么是线程:又称轻量级进程,程序中的一个顺序控制流程,同时也是CPU的基本调度单位。进程又多个线程组成,
彼此间完成不同的工作,交替执行,称为多线程。
例如:迅雷是一个进程,当中的多个下载任务既是多线程。
java虚拟机是一个进程,当中默认包含主线程(main)。
3.线程的组成:
CPU时间片:操作系统会为每个线程分配执行时间。
运行数据:堆空间:存储线程需使用对象,读多个线程可以共享堆中的对象。
栈空间:存储线程需使用局部变量,每个线程都拥有独立的栈。
线程的逻辑代码
4.创建线程:
创建线程的第一种方式:
创建线程的第二种方式:
5.常用的方法:
休眠:public static void sleep(long millis)表示当前线程主动休眠millis毫秒
放弃:public static void yield()表示当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
结合:public static void join()表示允许其他线程加入到当前线程中。
6.线程的安全
有两种线程安全同步方式:
(1)同步代码块:synchronize(临界资源对象){代码}//对临界资源对象加锁
(2)同步方法:synchronize 返回值类型 方法名称(形参列表){代码}//对当前对象this加锁
JDK中线程安全的类:
StringBuff
Vector
Hashtable
以上类中的公开方法,均为synchronize修饰的同步方法。
死锁:
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
线程通信:
等待:public final void wait()
通知:public final void notifyAll()
消费者与生产者案例:
package com.qf.t1;
public class TestShop {
public static void main(String[] args) {
Shop shop = new Shop();//共享资源对象
Thread p = new Thread(new Product(shop),"生产者");
Thread c = new Thread(new Customer(shop),"消费者");
p.start();
c.start();
}
}
class Goods{
private int id;
public Goods(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Shop{
Goods goods;
boolean flag;//标识商品是否充足
public synchronized void saveGoods(Goods goods)throws InterruptedException{
//1.判断商品是否充足
if(flag == true){
System.out.println("商品充足");
this.wait();//商品充足,生产者不用生产,而等待消费者卖完,进入等待状态!
}
//商品不充足!生产者生产,并存放商场里
System.out.println(Thread.currentThread().getName()+"生产并存放了"+goods.getId()+"件商品");
this.goods = goods;
flag = true;//已经有商品了
//消费者消费
this.notifyAll();//将等待消费的消费者唤醒,前来购买商品!
}
public synchronized void buyGoods()throws InterruptedException{
if(flag == false){//没有商品了,消费者需要等待
System.out.println("商品不充足,需等待");
this.wait();//消费者进入等待!等待生产者生产之后,唤醒!
}
System.out.println(Thread.currentThread().getName()+"购买了"+goods.getId()+"件商品");
this.goods = goods;
flag = false;
//并通知生产者生产
this.notifyAll();
}
}
//生产者
class Product implements Runnable{
Shop shop;
public Product(Shop shop) {
this.shop = shop;
}
public void run(){
//通过循环,生产商品放到商场里
for(int i= 0; i<30;i++){
try {//生产者线程调用存商品的方法。传递一个对象
this.shop.saveGoods(new Goods(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Customer implements Runnable{
Shop shop;//商场
public Customer(Shop shop) {
this.shop = shop;
}
public void run(){
//循环购买商品
for(int i= 0; i<30;i++){
try {
this.shop.buyGoods();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
高级多线程
线程池:线程容量,可设定线程分配的数量上限。
将预先创建的线程对象存入池中,并重用线程池中的线程对象。
避免频繁的创建和销毁。
通过newFixedThreadPool(int nThread) 传入参数,获取固定数量的线程池。
通过newCachedThreadPool()获得动态数量的线程池,没有上限。
Callable接口:interface Callable<V>{
public V call()Throws Exception;
}
具有返回值,可以声明异常。
Future接口:异步接收ExecutorService.submit()所返回的状态结果,包含了call()的返回值
V get方法。案例:
package com.qf.t1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
MyCall mc = new MyCall();
MyCall2 mc2 = new MyCall2();
//通过submit执行提交的任务,Future接受返回结果
Future<Integer> result = es.submit(mc);
Future<Integer> result2 = es.submit(mc2);
//通过Future的get方法,获得线程执行完毕后的结果
Integer value = result.get();
Integer value2 = result2.get();
System.out.println(value + value2);
}
}
//计算1~50的和
class MyCall implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Integer sum = 0;
for(int i = 1;i<=50;i++){
sum = sum+i;
}
return sum;
}
}
//计算50~100的和
class MyCall2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Integer sum = 0;
for(int i = 51;i<=100;i++){
sum = sum+i;
}
return sum;
}
}
Lock接口:与synchronize比较,现实定义,结构更灵活
常用方法:void lock()//获取锁,如锁被占用,则等待。
boolean tryLock()//尝试获取锁
void unlock()//释放锁
重入锁:
读写锁:
ReentrantReadWriteLock:一种支持一写多读的同步锁,读写分离,可分配读锁,写锁
支持多次分配读锁,使多个读操作可以并发执行
互斥规则:
写-写:互斥,阻塞
读-写:互斥,读阻塞写,写阻塞读
读-读:不互斥,不阻塞
Collections中的工具方法:
CopyOnWriteArrayList:(声明方式List<String> list = new CopyOnWriteArrayList<String>();)
线程安全的ArrayList,加强版读写分离。
写有锁,读无锁,读写之间不阻塞,优于读写锁
写入时,先copy一个容器副本,再添加新元素,最后替换引用
使用方式与ArrayList无异
CopyOnWriteArraySet:(声明方式List<String> list = new CopyOnWriteArraySet<String>();)
线程安全的Set,底层使用CopyOnWriteArrayList实现
唯一不同在于:使用addIfAbsent()添加元素,会遍历数组
如存在元素,则不添加。
ConcurrentHashMap
初始容量默认16段(Segment),使用分段锁设计
不对整个Map加锁,而是为每个Segment加锁
当多个对象存入同一个Segment时,才需要互斥
最理想状态为16个对象分别存入16个Segment,并行数量16
使用方式与HashMap无异
Queue接口(队列)
Queue<String> queue = new ConcurrentLinkedQueue<String>();
BlockingQueue接口(阻塞队列)
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
void put(E e)//将指定元素插入此队列中,如果没有空间则等待。
E take()//获取并移除此队列头部元素,如果没有可用元素则等待。
阻塞队列: