14、多线程
1、什么是进程

2、什么是线程
线程,又称轻量级进程(Light Weight Process )。进程中的一条执行路径,也是CPU的基本调度单位。
一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。

3、进程和线程的区别
1、进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位
2、一个程序运行后至少有一个进程。
3、一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。
4、进程间不能共享数据段地址,但同进程的线程之间可以
4、线程的组成
任何一个线程都具有基本的组成部分:
CPU时间片:操作系统(OS)会为每个线程分配执行时间。
运行数据:
堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
线程的逻辑代码。
5、线程的特点
1、线程抢占式执行
效率高
可防止单一线程长时间独占CPU
2、在单核CPU中,宏观上同时执行,微观上顺序执行。
6、创建线程
创建线程三种方式
1、【继承Thread类,重写run方法】
2、【实现Runnable接口】
3、实现Callable接口
6.1、创建线程(1)
第一种方式: 继承Thread类
获取线程ID和线程名称
1、在Thread的子类中调用 this.getId() 或 this.getName()。
2、使用 Thread.currentThread().getId() 和 Thread.currentThread().getName ()
修改线程名称
1、调用线程对象的setName()方法
2、使用线程子类的构造方法赋值

/** * 线程类 * */ public class MyThread extends Thread { public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { for(int i=0;i<100;i++) { //this.getId获取线程Id //this.getName获取线程名称 //第一种方式 this.getId和this.getName(); //System.out.println("线程id:"+this.getId()+" 线程名称:"+this.getName()+" 子线程............."+i); //第二种方式 Thread.currentThread() 获取当前线程 System.out.println("线程id:"+Thread.currentThread().getId()+" 线程名称:"+Thread.currentThread().getName()+" 子线程。。。。。。。"+i); } } }
public class TestThread { public static void main(String[] args) { //1创建线程对象 MyThread myThread=new MyThread("我的子线程1"); //2启动线程,不能使用run方法 //修改线程名称 //myThread.setName("我的子线程1"); myThread.start();//myThread.run() //创建第二个线程对象 MyThread myThread2=new MyThread("我的子线程2"); //myThread2.setName("我的子线程2"); myThread2.start(); //主线程执行 for(int i=0;i<50;i++) { System.out.println("主线程======================"+i); } } }
6.2、买票案例
使用继承Thread类实现4个窗口各卖100张票?
/** * 卖票窗口类(线程类) * */ public class TicketWin extends Thread{ private int ticket=100;//票 public TicketWin() { } public TicketWin(String name) { super(name); } @Override public void run() { //卖票功能 while(true) { if(ticket<=0) { break; } System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票"); ticket--; } } }
public class TestWin { public static void main(String[] args) { //创建四个窗口 TicketWin w1=new TicketWin("窗口1"); TicketWin w2=new TicketWin("窗口2"); TicketWin w3=new TicketWin("窗口3"); TicketWin w4=new TicketWin("窗口4"); //启动线程 w1.start(); w2.start(); w3.start(); w4.start(); } }
6.3、创建线程(2)
第二种方式:实现Runnable接口

public class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<100;i++) { System.out.println(Thread.currentThread().getName()+" ........."+i); } } }
public class TestRunnable { public static void main(String[] args) { // //1创建MyRunnable对象,表示线程要执行的功能 // MyRunnable runnable=new MyRunnable(); // //2创建线程对象 // Thread thread=new Thread(runnable, "我的线程1"); // //3启动 // thread.start(); // // for(int i=0;i<50;i++) { // System.out.println("main............"+i); // } //创建可运行对象 Runnable runnable=new Runnable() { @Override public void run() { for(int i=0;i<100;i++) { System.out.println(Thread.currentThread().getName()+" ........."+i); } } }; //创建线程对象 Thread thread=new Thread(runnable, "我的线程1"); //启动线程 thread.start(); for(int i=0;i<50;i++) { System.out.println("main............"+i); } } }
6.4、4个窗口共卖100张票
/** * 票类(共享资源) * */ public class Ticket implements Runnable { private int ticket=100;//100张票 @Override public void run() { while(true) { if(ticket<=0) { break; } System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票"); ticket--; } } }
public class TestTicket { public static void main(String[] args) { //1创建票对象 Ticket ticket=new Ticket(); //2创建线程对象 Thread w1=new Thread(ticket, "窗口1"); Thread w2=new Thread(ticket, "窗口2"); Thread w3=new Thread(ticket, "窗口3"); Thread w4=new Thread(ticket, "窗口4"); //3启动线程 w1.start(); w2.start(); w3.start(); w4.start(); } }
(2)存钱和取钱
方式一
/** * 银行卡 * */ public class BankCard{ private double money;//余额 public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
/** * 存钱 * */ public class AddMoney implements Runnable { private BankCard card; public AddMoney(BankCard card) { this.card=card; } @Override public void run() { for(int i=0;i<10;i++) { card.setMoney(card.getMoney()+1000); System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney()); } } }
/** * 取钱 * */ public class SubMoney implements Runnable{ private BankCard card; public SubMoney(BankCard card) { this.card = card; } @Override public void run() { for(int i=0;i<10;i++) { if(card.getMoney()>=1000) { card.setMoney(card.getMoney()-1000); System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney()); }else { System.out.println("余额不足,请赶快存取"); i--; } } } }
public class TestBankCard { public static void main(String[] args) { //1创建一张银行卡 BankCard card=new BankCard(); //2创建存钱、取钱 AddMoney add=new AddMoney(card); SubMoney sub=new SubMoney(card); //3创建两个线程 Thread chenchen=new Thread(add, "晨晨"); Thread bingbing=new Thread(sub, "冰冰"); //4启动线程 chenchen.start(); bingbing.start(); } }
方式二
public class TestBankCard2 { public static void main(String[] args) { BankCard card=new BankCard(); //存钱 Runnable add=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { card.setMoney(card.getMoney()+1000); System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney()); } } }; //取钱 Runnable sub=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { if(card.getMoney()>=1000) { card.setMoney(card.getMoney()-1000); System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney()); }else { System.out.println("余额不足,请赶快存取"); i--; } } } }; //创建线程对象,并启动 new Thread(add, "明明").start(); new Thread(sub,"丽丽").start(); } }
7、线程的状态
(1)基本

(2)等待

(3)阻塞

8、常见方法
8.1、休眠
public static void sleep(long millis)。
当前线程主动休眠millis毫秒。
/** * 线程类 * */ public class SleepThread extends Thread{ @Override public void run() { for(int i=0;i<10;i++) { System.out.println(Thread.currentThread().getName()+"......."+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class TestYield { public static void main(String[] args) { YieldThread y1=new YieldThread(); YieldThread y2=new YieldThread(); y1.start(); y2.start(); } }
8.2、放弃
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public class YieldThread extends Thread { @Override public void run() { for(int i=0;i<10;i++) { System.out.println(Thread.currentThread().getName()+"........"+i); //主动放弃cpu Thread.yield(); } } }
public class TestYield { public static void main(String[] args) { YieldThread y1=new YieldThread(); YieldThread y2=new YieldThread(); y1.start(); y2.start(); } }
8.3、加入
public final void join()
/** * join方法的使用 */ public class JoinThread extends Thread { @Override public void run() { for(int i=0;i<30;i++) { System.out.println(Thread.currentThread().getName()+"......"+i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class TestJoin { public static void main(String[] args) { JoinThread j1=new JoinThread(); j1.start(); try { j1.join();//加入当前线程(main),并阻塞当前线程,直到加入线程执行完毕 } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } //for 主线程 for(int i=0;i<20;i++) { System.out.println(Thread.currentThread().getName()+"========="+i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
8.4、优先级
线程对象.setPriority()
线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多
/** * 优先级 * */ public class PriorityThread extends Thread{ @Override public void run() { for(int i=0;i<50;i++) { System.out.println(Thread.currentThread().getName()+"============"+i); } } }
public class TestPriority { public static void main(String[] args) { PriorityThread p1=new PriorityThread(); p1.setName("p1"); PriorityThread p2=new PriorityThread(); p2.setName("p2"); PriorityThread p3=new PriorityThread(); p3.setName("p3"); p1.setPriority(1); p3.setPriority(10); //启动 p1.start(); p2.start(); p3.start(); } }
8.5、守护线程
线程对象.setDaemon(true);设置为守护线程
线程有两类:用户线程(前台线程)、守护线程(后台线程)。
如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
垃圾回收器线程属于守护线程
public class DeamonThread extends Thread { @Override public void run() { for(int i=0;i<50;i++) { System.out.println(Thread.currentThread().getName()+"----------"+i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class TestDeamon { public static void main(String[] args) { //创建线程(默认前台线程) DeamonThread d1=new DeamonThread(); //设置线程为守护线程 d1.setDaemon(true); d1.start(); for(int i=0;i<10;i++) { System.out.println("主线程:----------"+i); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
9、线程安全问题

需求:
A线程将“Hello”存入数组;B线程将“World”存入数组。
多线程安全问题:
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
/** * 线程安全问题: * 两个线程同时向一个数组中存放数据 * */ public class ThreadSafe { private static int index=0; public static void main(String[] args) throws Exception{ //创建数组 String[] s=new String[5]; //创建两个操作 Runnable runnableA=new Runnable() { @Override public void run() { //同步代码块 synchronized (s) { s[index]="hello"; index++; } } }; Runnable runnableB=new Runnable() { @Override public void run() { synchronized (s) { s[index]="world"; index++; } } }; //创建两个线程对象 Thread a=new Thread(runnableA,"A"); Thread b=new Thread(runnableB,"B"); a.start(); b.start(); a.join();//加入线程 b.join();//加入线程 System.out.println(Arrays.toString(s)); } }
10、同步代码块
synchronized(临界资源对象){//对临界资源对象加锁 //代码(原子操作) }
每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
(1)锁对象
/** * 票类 * */ public class Ticket implements Runnable{ private int ticket=100; //创建锁 //private Object obj=new Object(); @Override public void run() { while(true) { synchronized (this) {//this ---当前对象 if(ticket<=0) { break; } System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票"); ticket--; } } } }
public class TestTicket { public static void main(String[] args) { //创建Ticket Ticket ticket=new Ticket(); //创建线程对象 Thread w1=new Thread(ticket, "窗口1"); Thread w2=new Thread(ticket, "窗口2"); Thread w3=new Thread(ticket, "窗口3"); Thread w4=new Thread(ticket, "窗口4"); //启动线程 w1.start(); w2.start(); w3.start(); w4.start(); } }
(2)锁对象
public class BankCard { private double money; public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
public class TestBankCard { public static void main(String[] args) { //1创建银行卡 BankCard card=new BankCard(); //2创建两个操作 Runnable add=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { synchronized (card) { card.setMoney(card.getMoney()+1000); System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney()); } } } }; Runnable sub=new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { synchronized (card) { if(card.getMoney()>=1000) { card.setMoney(card.getMoney()-1000); System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney()); }else { System.out.println("余额不足,请存取"); i--; } } } } }; //3创建两个线程对象 Thread xiaoli=new Thread(add, "小李"); Thread xiaoyue=new Thread(sub,"小月" ); xiaoli.start(); xiaoyue.start(); } }
11、同步方法
synchronized 返回值类型 方法名称(形参列表0){//对当前对象(this)加锁
//代码(原子操作)
}
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中
线程退出同步方法时,会释放相应的互斥锁标记。
(1)方法
/** * 票类 * */ public class Ticket implements Runnable{ private int ticket=100; @Override public void run() { while(true) { if(!sale()) { break; } } } //卖票(同步方法) public synchronized boolean sale() {//锁 this if(ticket<=0) { return false; } System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票"); ticket--; return true; } }
(2)静态方法
/** * 票类 * */ public class Ticket implements Runnable{ private static int ticket=100; //创建锁 //private Object obj=new Object(); @Override public void run() { while(true) { if(!sale()) { break; } } } //卖票(同步方法) public static boolean sale() {//锁 this 静态方法 锁 Ticket.class synchronized (Ticket.class) { if(ticket<=0) { return false; } System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票"); ticket--; return true; } } }
12、同步规则
注意:
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
已知JDK中线程安全的类:
StringBuffer
vector
Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法。
13、死锁
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁,
/** * 创建两个锁对象 * */ public class MyLock { //两个锁(两个筷子) public static Object a=new Object(); public static Object b=new Object(); }
public class Boy extends Thread{ @Override public void run() { synchronized (MyLock.a) { System.out.println("男孩拿到了a"); synchronized (MyLock.b) { System.out.println("男孩拿到了b"); System.out.println("男孩可以吃东西了..."); } } } }
public class Girl extends Thread { @Override public void run() { synchronized (MyLock.b) { System.out.println("女孩拿到了b"); synchronized (MyLock.a) { System.out.println("女孩拿到了a"); System.out.println("女孩可以吃东西了..."); } } } }
public class TestDeadLock { public static void main(String[] args) { Boy boy=new Boy(); Girl girl=new Girl(); girl.start(); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } boy.start(); } }
14、线程通信
等待:
public final void wait()
public final void wait(long timeout)
必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
通知:
public final void notify(). public final void notifyAll()

public class BankCard { //余额 private double money; //标记 private boolean flag=false;// true 表示有钱可以取钱 false没钱 可以存取 //存钱 public synchronized void save(double m) { //this while(flag) {//有钱 try { this.wait();//进入等待队列,同时释放锁 ,和cpu } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } money=money+m; System.out.println(Thread.currentThread().getName()+"存了"+m+" 余额是"+money); //修改标记 flag=true; //唤醒取钱线程 this.notify(); } //取钱 public synchronized void take(double m) {//this while(!flag) { try { this.wait();//进入等待队列,同时释放锁 ,和cpu } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } money=money-m; System.out.println(Thread.currentThread().getName()+"取了"+m+" 余额是"+money); //修改标记 flag=false; //唤醒存钱线程 this.notify(); } }
public class AddMoney implements Runnable{ private BankCard card; public AddMoney(BankCard card) { this.card=card; } @Override public void run() { for(int i=0;i<10;i++) { card.save(1000); } } }
public class SubMoney implements Runnable{ private BankCard card; public SubMoney(BankCard card) { this.card=card; } @Override public void run() { for(int i=0;i<10;i++) { card.take(1000); } } }
public class TestBankCard { public static void main(String[] args) { //1创建银行卡 BankCard card=new BankCard(); //2创建操作 AddMoney add=new AddMoney(card); SubMoney sub=new SubMoney(card); //3创建线程对象 Thread chenchen=new Thread(add, "晨晨"); Thread bingbing=new Thread(sub, "冰冰"); Thread mingming=new Thread(add, "明明"); Thread lili=new Thread(sub, "莉莉"); //4启动 chenchen.start(); mingming.start(); bingbing.start(); lili.start(); } }
15、生产者、消费者
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
public class BreadCon { //存放面包的数组 private Bread[] cons=new Bread[6]; //存放面包的位置 private int index=0; //存放面包 public synchronized void input(Bread b) { //锁this //判断容器有没有满 while(index>=6) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } cons[index]=b; System.out.println(Thread.currentThread().getName()+"生产了"+b.getId()+""); index++; //唤醒 this.notifyAll(); } //取出面包 public synchronized void output() {//锁this while(index<=0) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } index--; Bread b=cons[index]; System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+" 生产者:"+b.getProductName()); cons[index]=null; //唤醒生产者 this.notifyAll(); } }
/** * 面包 * */ public class Bread { private int id; private String productName; public Bread() { // 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 Consume implements Runnable{ private BreadCon con; public Consume(BreadCon con) { super(); this.con = con; } @Override public void run() { for(int i=0;i<30;i++) { con.output(); } } }
public class Prodcut implements Runnable { private BreadCon con; public Prodcut(BreadCon con) { super(); this.con = con; } @Override public void run() { for(int i=0;i<30;i++) { con.input(new Bread(i, Thread.currentThread().getName())); } } }
public class Test { public static void main(String[] args) { //容器 BreadCon con=new BreadCon(); //生产和消费 Prodcut prodcut=new Prodcut(con); Consume consume=new Consume(con); //创建线程对象 Thread chenchen=new Thread(prodcut, "晨晨"); Thread bingbing=new Thread(consume, "消费"); Thread mingming=new Thread(prodcut, "明明"); Thread lili=new Thread(consume, "莉莉"); //启动线程 chenchen.start(); bingbing.start(); mingming.start(); lili.start(); } }
16、总结
线程的创建:
方式1:继承Thread类
方式2:实现Runnable接口(一个任务Task),传入给Thread对象并执行。
线程安全:
同步代码块:为方法中的局部代码(原子操作)加锁。
同步方法:为方法中的所有代码(原子操作)加锁。
线程间的通信:
wait() / wait(long timeout):等待
notify() / notifyAll():通知
17、线程池
17.1、概念
问题:
线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
线程池:
线程容器,可设定线程分配的数量上限。
将预先创建的线程对象存入池中,并重用线程池中的线程对象。
避免频繁的创建和销毁。
17.2、线程池原理
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。

17.3、创建线程池
常用的线程池接口和类(所在包java.util.concurrent)
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit (Runnable task)提交任务代码。
Executors工厂类:通过此类可以获得一个线程池。
通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限
/** * 演示线程池的创建 * Executor:线程池的根接口,execute() * ExecutorService:包含管理线程池的一些方法,submit shutdown * ThreadPoolExecutor * ScheduledThreadPoolExecutor * Executors:创建线程池的工具类 * (1)创建固定线程个数线程池 * (2)创建缓存线程池,由任务的多少决定 * (3)创建单线程池 * (4)创建调度线程池 调度:周期、定时执行 * */ public class Demo1 { public static void main(String[] args) { //1.1创建固定线程个数的线程池 //ExecutorService es=Executors.newFixedThreadPool(4); //1.2创建缓存线程池,线程个数由任务个数决定 ExecutorService es=Executors.newCachedThreadPool(); //1.3创建单线程线程池 //Executors.newSingleThreadExecutor(); //1.4创建调度线程池 调度:周期、定时执行 //Executors.newScheduledThreadPool(corePoolSize) Executors.newScheduledThreadPool(3); //2创建任务 Runnable runnable=new Runnable() { private int ticket=100; @Override public void run() { while(true) { if(ticket<=0) { break; } System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票"); ticket--; } } }; //3提交任务 for(int i=0;i<5;i++) { es.submit(runnable); } //4关闭线程池 es.shutdown();//等待所有任务执行完毕 然后关闭线程池,不接受新任务。 } }
18、Callable接口(创建线程3)
public interface Callable<V> { public V call() throws Exception; }
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
Callable具有泛型返回值、可以声明异常。
/** * 演示Callable接口的使用 * Callable和Runnable接口的区别 * (1)Callable接口中call方法有返回值,Runnable接口中run方法没有返回值 * (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常 * */ public class Demo2 { public static void main(String[] args) throws Exception{ //功能需求:使用Callable实现1-100和 //1创建Callable对象 Callable<Integer> callable=new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"开始计算"); int sum=0; for(int i=1;i<=100;i++) { sum+=i; Thread.sleep(100); } return sum; } }; //2把Callable对象 转成可执行任务 FutureTask<Integer> task=new FutureTask<>(callable); //3创建线程 Thread thread=new Thread(task); //4启动线程 thread.start(); //5获取结果(等待call执行完毕,才会返回) Integer sum=task.get(); System.out.println("结果是:"+sum); } }
/** * 使用线程池计算1-100的和 */ public class Demo3 { public static void main(String[] args) throws Exception{ //1创建线程池 ExecutorService es=Executors.newFixedThreadPool(1); //2提交任务 Future:表示将要执行完任务的结果 Future<Integer> future=es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"开始计算"); int sum=0; for(int i=1;i<=100;i++) { sum+=i; Thread.sleep(10); } return sum; } }); //3获取任务结果,等待任务执行完毕才会返回. System.out.println(future.get()); //4关闭线程池 es.shutdown(); } }
19、Future接口
表示ExecutorService.submit()所返回的状态结果,就是call()的返回值
方法:v get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
Future:表示将要完成任务的结果。
需求:使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。
/** * 使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。 * */ public class Demo4 { public static void main(String[] args) throws Exception{ //1创建线程池 ExecutorService es=Executors.newFixedThreadPool(2); //2提交任务 Future<Integer> future1=es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum=0; for(int i=1;i<=50;i++) { sum+=i; } System.out.println("1-50计算完毕"); return sum; } }); Future<Integer> future2=es.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { int sum=0; for(int i=51;i<=100;i++) { sum+=i; } System.out.println("51-100计算完毕"); return sum; } }); //3获取结果 int sum=future1.get()+future2.get(); System.out.println("结果是:"+sum); //4关闭线程池 es.shutdown(); } }
20、线程的同步
形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

21、线程的异步
形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。

22、Lock接口
JDK5加入,与synchronized比较,显示定义,结构更灵活。
提供更多实用性方法,功能更强大、性能更优越。
常用方法:
void lock()//获取锁,如锁被占用,则等待。
boolean tryLock() / /尝试获取锁(成功返回true。失败返回fals不阻塞)
void unlock()//释放锁
23、重入锁
ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能。

/** * ReentrantLock的使用 */ public class MyList { //创建锁 private Lock lock=new ReentrantLock(); private String[] str= {"A","B","","",""}; private int count=2; public void add(String value) { lock.lock(); try { str[count]=value; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count++; System.out.println(Thread.currentThread().getName()+"添加了"+value); } finally { lock.unlock(); } } public String[] getStr() { return str; } }
public class TestMyList { public static void main(String[] args) throws Exception { MyList list=new MyList(); Runnable runnable=new Runnable() { @Override public void run() { list.add("hello"); } }; Runnable runnable2=new Runnable() { @Override public void run() { list.add("world"); } }; Thread t1=new Thread(runnable); Thread t2=new Thread(runnable2); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(Arrays.toString(list.getStr())); } }
public class Ticket implements Runnable{ private int ticket=100; private Lock lock=new ReentrantLock(); @Override public void run() { while(true) { lock.lock(); try { if(ticket<=0) { break; } System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票"); ticket--; } finally { lock.unlock(); } } } }
24、读写锁
ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
写-写:互斥,阻塞。
读-写:互斥,读阻塞写、写阻塞读。
读-读:不互斥、不阻塞。
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

public class ReadWriteDemo { //创建读写锁 private ReentrantReadWriteLock rrl=new ReentrantReadWriteLock(); //获取读锁 private ReadLock readLock=rrl.readLock(); //获取写锁 private WriteLock writeLock=rrl.writeLock(); //互斥锁 private ReentrantLock lock=new ReentrantLock(); private String value; //读取 public String getValue() { //使用读锁上锁 lock.lock(); try { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("读取:"+this.value); return this.value; }finally { lock.unlock(); } } //写入 public void setValue(String value) { lock.lock(); try { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("写入:"+value); this.value=value; }finally { lock.unlock(); } } }
public class TestReadWriteLock { public static void main(String[] args) { ReadWriteDemo readWriteDemo=new ReadWriteDemo(); //创建线程池 ExecutorService es=Executors.newFixedThreadPool(20); Runnable read=new Runnable() { @Override public void run() { readWriteDemo.getValue(); } }; Runnable write=new Runnable() { @Override public void run() { readWriteDemo.setValue("张三:"+new Random().nextInt(100)); } }; long start=System.currentTimeMillis(); //分配2个写的任务 for(int i=0;i<2;i++) { es.submit(write); } //分配18读取任务 for(int i=0;i<18;i++) { es.submit(read); } es.shutdown();//关闭 while(!es.isTerminated()) {//空转 } long end=System.currentTimeMillis(); System.out.println("用时:"+(end-start)); } }
25、集合
25.1、Collection体系集合
Collection体系集合中,除Vector以外的线程安全集合。

25.2、Map安全集合体系

25.3、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实现。
/** * * 演示:使用多线程操作线程不安全集合 */ public class Demo1 { public static void main(String[] args) { //1创建arraylist ArrayList<String> arrayList=new ArrayList<>(); //1.1使用Collections中的线程安全方法转成线程安全的集合 List<String> synList=Collections.synchronizedList(arrayList); //1.2使用CopyOnWriteArrayList //CopyOnWriteArrayList<String> arrayList=new CopyOnWriteArrayList<>(); //2创建线程 for(int i=0;i<20;i++) { int temp=i; new Thread(new Runnable() { @Override public void run() { for(int j=0;j<10;j++) { synList.add(Thread.currentThread().getName()+"===="+temp+"===="+j); System.out.println(synList.toString()); } } }).start(); } } }
25.4、CopyOnWriteArrayList
线程安全的ArrayList,加强版的读写分离。
写有锁,读无锁,读写之间不阻塞,优于读写锁。
写入时,先copy一个容器副本、再添加新元素,最后替换引用。
使用方式与ArrayList无异。
/** * 使用多线程操作CopyOnWriteArrayList * */ public class Demo2 { public static void main(String[] args) { //1创建集合 CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>(); //2使用多线程操作 ExecutorService es=Executors.newFixedThreadPool(5); //3提交任务 for(int i=0;i<5;i++) { es.submit(new Runnable() { @Override public void run() { for(int j=0;j<10;j++) { list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000)); } } }); } //4关闭线程池 es.shutdown(); while(!es.isTerminated()) {} //5打印结果 System.out.println("元素个数:"+list.size()); for (String string : list) { System.out.println(string); } } }
25.5、CopyOnWriteArraySet
线程安全的Set,底层使用CopyOnWriteArrayList实现。
唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
如存在元素,则不添加(扔掉副本)。
/** * 演示CopyOnWriteArraySet * 重复依据:equals方法 * */ public class Demo3 { public static void main(String[] args) { //1创建集合 CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>(); //2添加元素 set.add("pingguo"); set.add("huawei"); set.add("xiaomi"); set.add("lianxiang"); set.add("pingguo"); //3打印 System.out.println("元素个数:"+set.size()); System.out.println(set.toString()); } }
26、Queue接口(队列)
Collection的子接口,表示队列FIFO (First In First Out)先进先出常用方法:
抛出异常:
boolean add(E e)//顺序添加一个元素(到达上限后,再添加则会抛出异常)。
E remove()//获得第一个元素并移除(如果队列没有元素时,则抛异常)。
E element()//获得第一个元素但不移除(如果队列没有元素时,则抛异常)
返回特殊值:推荐使用
boolean offer(E e) //顺序添加一个元素(到达上限后,再添加则会返回false)。
E poll() //获得第一个元素并移除(如果队列没有元素时,则返回null)
E peek() //获得第一个元素但不移除(如果队列没有元素时,则返回null)
/** * Queue接口的使用 * */ public class Demo4 { public static void main(String[] args) { //1创建队列 Queue<String> queue=new LinkedList<>(); //2入队 queue.offer("苹果"); queue.offer("橘子"); queue.offer("葡萄"); queue.offer("西瓜"); queue.offer("榴莲"); //3出队 System.out.println(queue.peek()); System.out.println("----------------"); System.out.println("元素个数:"+queue.size()); int size=queue.size(); for(int i=0;i<size;i++) { System.out.println(queue.poll()); } System.out.println("出队完毕:"+queue.size()); } }
27、ConcurrentLinkedQueue
线程安全、可高效读写的队列,高并发下性能最好的队列
无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
V:要更新的变量、E:预期值、N:新值。
只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。

/** * ConcurrentLinkedQueue的使用 */ public class Demo5 { public static void main(String[] args) throws Exception { //1创建安全队列 ConcurrentLinkedQueue<Integer> queue=new ConcurrentLinkedQueue<>(); //2入队操作 Thread t1=new Thread(new Runnable() { @Override public void run() { for(int i=1;i<=5;i++) { queue.offer(i); } } }); Thread t2=new Thread(new Runnable() { @Override public void run() { for(int i=6;i<=10;i++) { queue.offer(i); } } }); //3启动线程 t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("-------------出队-------------"); //4出队操作 int size=queue.size(); for(int i=0;i<size;i++) { System.out.println(queue.poll()); } } }
28、BlockingQueue接口(阻塞队列)
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
方法:
void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待。
E take() //获取并移除此队列头部元素,如果没有可用元素,则等待。
ArrayBlockingQueue:
数组结构实现,有界队列。(手工固定上限)
LinkedBlockingQueue:
链表结构实现,有界队列。(默认上限Integer.MAX VALUE)
/** * 阻塞队列的使用 * 案例1:创建一个有界队列,添加数据 * 案例2:使用阻塞队列实现生产者和消费者 * */ public class Demo6 { public static void main(String[] args) throws Exception{ //创建一个有界队列,添加数据 ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(5); //添加元素 queue.put("aaa"); queue.put("bbb"); queue.put("ccc"); queue.put("ddd"); queue.put("eee"); //删除元素 queue.take(); System.out.println("已经添加了5个元素"); queue.put("xyz"); System.out.println("已经添加了6个元素"); System.out.println(queue.toString()); } }
/** * 案例2:使用阻塞队列实现生产者和消费者 * */ public class Demo7 { public static void main(String[] args) { //1创建队列 ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(6); //2创建两个线程 Thread t1=new Thread(new Runnable() { @Override public void run() { for(int i=0;i<30;i++) { try { queue.put(i); System.out.println(Thread.currentThread().getName()+"生产了第"+i+"号面包"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }, "晨晨"); Thread t2=new Thread(new Runnable() { @Override public void run() { for(int i=0;i<30;i++) { try { Integer num=queue.take(); System.out.println(Thread.currentThread().getName()+"消费了第"+i+"号面包"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }, "冰冰"); //启动线程 t1.start(); t2.start(); } }
29、ConcurrentHashMap
初始容量默认为16段(Segment),使用分段锁设计。
不对整个Map加锁,而是为每个Segment加锁。
当多个对象存入同一个Segment时,才需要互斥。
最理想状态为16个对象分别存入16个Segment,并行数量16
使用方式与HashMap无异。
/** * ConcurrentHashMap的使用 * */ public class Demo8 { public static void main(String[] args) { //1创建集合 ConcurrentHashMap<String, String> hashMap=new ConcurrentHashMap<String, String>(); //2使用多线程添加数据 for(int i=0;i<5;i++) { new Thread(new Runnable() { @Override public void run() { for(int k=0;k<10;k++) { hashMap.put(Thread.currentThread().getName()+"--"+k, k+""); System.out.println(hashMap); } } }).start(); } } }
30、总结
ExecutorService线程池接口、Executors工厂。
Callable线程任务、Future异步返回值。
Lock、ReentrantLock重入锁、ReentrantReadWriteLock读写锁。
CopyOnWriteArrayList线程安全的ArrayList。
CopyOnWriteArraySet线程安全的Set。
ConcurrentLinkedQueue线程安全的Queue。
ArrayBlockingQueue线程安全的阻塞Queue。(生产者、消费者)
ConcurrentHashMap线程安全的HashMap。

浙公网安备 33010602011771号