JavaSE_第十四章:多线程

JavaSE学习历程

第一章:Java初识
第二章:Java语言基础
第三章:选择结构与分支结构
第四章:循环结构
第五章:方法/函数
第六章:数组
第七章:面向对象
第八章:三大特性
第九章:三个修饰符
第十章:接口
第十一章:常用类
第十二章:集合
第十三章:异常

第十四章:多线程

1 什么是线程

1.1 进程

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

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

1.2 线程

线程:又称轻量级进程(Light Weight Process)。

  • 程序中的一个顺序控制流程,同时也是CPU的基本调度单位。
  • 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。

比如:

  • 迅雷是一个进程,当中的多个下载任务即是多个线程。
  • Java虚拟机是一个进程,默认包含主线程(main),通过代码创建多个独立线程,与main并发执行。

1.3 进程和线程区别

  • 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
  • 一个程序运行后至少有一个进程。
  • 一个进程可以包含多个线程,但是至少需要有一个线程。
  • 进程间不能共享数据段地址,但同进程的线程之间可以。

1.4 线程的组成

任何一个线程都具有基本的组成部分:

  • CPU时间片:操作系统(OS)会为每个线程分配执行时间。
  • 运行数据:
  • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
  • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
  • 线程的逻辑代码。

创建线程[重点]

2.1 创建线程的方式

Java中创建线程主要有两种方式:

  • 继承Thread类
  • 实现Runnable接口

2.2 继承Thread类

步骤:

  • 编写类、继承Thread.
  • 重写run()方法.
  • 创建线程对象.
    调用start()方法启动线程.

2.3 实现Runnable接口

步骤:

  • 编写类实现Runnable接口,并重写run方法
  • 创建Runnable实现类对象
  • 创建线程对象,传递实现类对象
  • 启动线程.

2.4 案例

/**
 * @Classname MyThread
 * @Description TODO:
 * @Created by xx
 */
public class TestMyThread {
    public static void main(String[] args) {
        //继承Thread类
        //1.创建线程对象
        MyThread myThread = new MyThread();
        //2.启动线程
        myThread.start();

        MyThread myThread2 = new MyThread();
        myThread2.start();
    
        //实现Runnable接口
        //1.创建MyRunnable对象,表示线程要执行的功能
        MyRunnable myRunnable = new MyRunnable();
        //2.创建线程对象
        Thread thread = new Thread(myRunnable);
        //3.启动线程
        thread.start();
    
        MyRunnable myRunnable1 = new MyRunnable();
        Thread thread1 = new Thread(myRunnable, "myRunnable线程1");
        //3.启动线程
        thread1.start();
    
        for (int i = 0; i < 20; i++) {
            System.out.println("线程id:" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName() + "主线程" + i);
        }

    }

}

class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    public MyThread() {
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
//            System.out.println("子线程:"+i);

            //第一种方法获取线程id与name--->要求类继承Thread
//            System.out.println("线程id:"+this.getId()+" 线程名称:"+this.getName()+" 子线程............."+i);

            //第二种方法获取线程id与name--->不要求类继承Thread
            System.out.println("线程id:" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName() + "子线程继承Thread" + i);
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("线程id:" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName() + "子线程实现Runnable" + i);
        }
    }
}

3 线程的状态

3.1 线程状态(基本)

线程状态:新建、就绪、运行、终止.

3.2 常见方法

方法名说明
public static void sleep(long millis)当前线程主动休眠 millis 毫秒。
public static void yield()当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public final void join()允许其他线程加入到当前线程中。
public void setPriority(int)线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
public void setDaemon(boolean)设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程)

3.3 线程状态(等待)

案例:

/**
 * @Classname TestJoin
 * @Description TODO:测试结合(阻塞)方法
 * @Created by xx
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        Sleep sp = new Sleep();
        Thread t = new Thread(sp,"子线程");
        t.start();
        //加入线程,阻塞当前线程
        t.join();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
/**
 * @Classname TestSleep
 * @Description TODO:测试线程休眠方法
 * @Created by xx
 */
public class TestSleep {
    public static void main(String[] args) throws InterruptedException {
        Sleep sp = new Sleep();
        Thread t = new Thread(sp,"子线程");
        t.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            //睡眠线程1000时间
            Thread.sleep(1000);
        }
    }
}

class Sleep implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
/**
 * @Classname TestYield
 * @Description TODO:测试放弃方法
 * @Created by xx
 */
public class TestYield {
    public static void main(String[] args) {
        Sleep sp = new Sleep();
        Thread t = new Thread(sp,"子线程");
        t.start();
        //放弃线程--->进入休眠状态
        Thread.yield();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
/**
 * @Classname TestPriority
 * @Description TODO:编写一个Java程序(包括一个主程序类,一个Thread类的子类)。在主程序中创建2个线程(用子类),将其中一个线程的优先级设为10,另一个线程的优先级设为6。让优先级为10的线程打印200次“线程1正在运行”,优先级为6的线程打印200次“线程2正在运行”。
 * @Created by xx
 */
public class TestPriority {
    public static void main(String[] args) {
        //创建对象
        Priority p1 = new Priority();
        Priority p2 = new Priority();

        //命名
        p1.setName("线程1正在运行");
        p2.setName("线程2正在运行");

        //设置优先级
        p1.setPriority(10);
        p2.setPriority(6);

        //启动线程
        p1.start();
        p2.start();
    }
}

class Priority extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 200; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }

}

4 线程安全

为什么会出现线程安全问题?

  • 需求: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));
		
	}
}

4.1 同步代码块

语法

synchronized(临界资源对象){ //对临界资源对象加锁
	//代码(原子操作)
}
  • 每个对象都有一个互斥锁标记,用来分配给线程的。
  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
    +[线程退出同步代码块时,会释放相应的互斥锁标记。
class Ticket implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {

        while (true){
        //对象锁
            synchronized (this) {
                if (ticket<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName() +"买票,剩余:" + ticket);
                ticket--;
            }
        }
    }
}

4.2 线程状态(阻塞)

线程状态:新建、就绪、运行、阻塞、终止。

4.3 同步方法

语法:

synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
	// 代码(原子操作)
}
  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
  • 线程退出同步方法时,会释放相应的互斥锁标记。
  • 如果方式是静态,锁是类名.class。

4.4 同步规则

  • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

JDK中线程安全的类:

  • StringBuffer
  • Vector
  • Hashtable
    以上类中的公开方法,均为synchonized修饰的同步方法。

4.5 死锁

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
/**
 * @Classname TestDeadLock
 * @Description TODO:测试死锁
 * @Created by xx
 */
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();
    }
}

class MyLock {
    //两个锁(两个筷子)
    public static Object a=new Object();
    public static Object b=new Object();
}

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("男孩可以吃东西了...");
            }
        }
    }
}

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("女孩可以吃东西了...");
            }
        }
    }
}
/*
运行结果:===>正常版
女孩拿到了b
女孩拿到了a
女孩可以吃东西了...
男孩拿到了a
男孩拿到了b
男孩可以吃东西了...
*/
/*
运行结果:===>死锁版
女孩拿到了b
男孩拿到了a
*/

5 线程通信

5.1 线程通信方法

方法说明
public final void wait()释放锁,进入等待队列
public final void wait(long timeout)在超过指定的时间前,释放锁,进入等待队列
public final void notify()随机唤醒、通知一个线程
public final void notifyAll()唤醒、通知所有线程

5.2 生产者消费者

若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

案例

/**
 * @Classname TestProducerConsumer
 * @Description TODO:测试生产者消费者问题
 * @Created by xx
 */
public class TestProducerConsumer {
    public static void main(String[] args) {
        Doll doll = new Doll();
        Produce produce = new Produce(doll);
        Consume consume = new Consume(doll);

        Thread yy = new Thread(produce, "yy生-");
        Thread xx = new Thread(produce, "xx生-");
        Thread zz = new Thread(produce, "zz生-");
        Thread ww = new Thread(consume, "ww消-");
        Thread xf = new Thread(consume, "xf消-");

        yy.start();
        xx.start();
        zz.start();
        ww.start();
        xf.start();
    }
}

/**
 * 玩具娃娃类
 */
class Doll {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Doll() {
    }

    public Doll(int count) {
        this.count = count;
    }

    /**
     * 生产娃娃
     */
    public synchronized void produceDoll(int count) throws InterruptedException {
        while (this.getCount() > 25) {
            //有   25个娃娃不生产
            this.wait();
        }
        this.count += count;
        System.out.println(Thread.currentThread().getName() + "生产娃娃" + ",共有" + this.getCount() + "件娃娃");
        this.notifyAll();
    }

    /**
     * 购买娃娃
     */
    public synchronized void buyDoll(int count) throws InterruptedException {
        while (this.getCount() <= 0) {
            //没有娃娃不购买
            this.wait();
        }
        this.count -= count;
        System.out.println(Thread.currentThread().getName() + "购买娃娃" + ",还剩" + this.getCount() + "件娃娃");
        this.notifyAll();
    }
}

/**
 * 生产者
 */
class Produce implements Runnable {
    private Doll dl;

    public Produce() {
    }

    public Produce(Doll dl) {
        this.dl = dl;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                dl.produceDoll(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 消费者
 */
class Consume implements Runnable {
    private Doll dl;

    public Consume(Doll dl) {
        this.dl = dl;
    }

    public Consume() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                dl.buyDoll(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6 线程池[重点]

6.1 为什么需要线程池

  • 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
  • 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。

6.2 线程池原理

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

7.3 线程池API

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

Executor:线程池的顶级接口.
ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码.
Excutors工厂类:通过此类可以获得一个线程池.

方法名描述
newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,无上限。
newSingleThreadExecutor()创建单个线程的线程池,只有一个线程。
newScheduledThreadPool()创建固定大小的线程池,可以延迟或定时执行任务。

案例:测试线程池:

import java.util.concurrent.*;

/**
 * @Classname Demo0301
 * @Description TODO:4.使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。
 * @Created by xx
 */
public class Demo04 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建线程池对象
        ExecutorService es = Executors.newFixedThreadPool(4);
        //2.提交任务
        Future<Integer> future = es.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 1; i <= 50; i++) {
//                    System.out.println(Thread.currentThread().getName()+"===>"+i);
                    sum += i;
                }
                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++) {
//                    System.out.println(Thread.currentThread().getName()+"===>"+i);
                    sum += i;
                }
                return sum;
            }
        });

        System.out.println("1-50和:" + future.get());
        System.out.println("51-100和:" + future2.get());
        System.out.println("总和:" + (future2.get() + future.get()));
        //关闭线程池
        es.shutdown();
    }
}

6.4 Callable接口

public interface Callable< V >{
		public V call() throws Exception;
}
  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值、可以声明异常。

Runnable接口和Callable接口的区别:

  • Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
  • Callable接口中call方法有声明异常,Runnable接口中run方法没有异常.

6.5 Future接口

  • Future接口表示要执行完任务的结果.
  • get()以阻塞形式等待Future中的异步处理结果(call()的返回值).

案例:线程池实现1-100的和

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;


/**
 * @Classname TestCallable
 * @Description TODO:线程池实现1-100的和
 * @Created by xx
 */
public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(2);

        //提交任务
        Future<Integer> future = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "开始计算奇数:");
                int oddSum = 0;
                for (int i = 0; i <= 100; i++) {
                    //判断奇数
                    if (i % 2 != 0) {
                        oddSum += i;
                    }
                }
                return oddSum;
            }
        });

        Future<Integer> future2 = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "开始计算偶数:");
                int evenSum = 0;
                for (int i = 0; i <= 100; i++) {
                    //判断偶数
                    if (i % 2 == 0) {
                        evenSum += i;
                    }
                }
                return evenSum;
            }
        });

      /*  //1.创建Callable对象
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "开始计算奇数:");
                int oddSum = 0;
                for (int i = 0; i <= 100; i++) {
                    //判断奇数
                    if (i % 2 != 0) {
                        oddSum += i;
                    }
                }
                return oddSum;
            }
        };

        Callable<Integer> callable2 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "开始计算偶数:");
                int evenSum = 0;
                for (int i = 0; i <= 100; i++) {
                    //判断偶数
                    if (i % 2 == 0) {
                        evenSum += i;
                    }
                }
                return evenSum;
            }
        };

        //2把Callable对象,转换成可执行任务
        FutureTask<Integer> task = new FutureTask<Integer>(callable);
        FutureTask<Integer> task1 = new FutureTask<Integer>(callable2);
        //3创建线程
        Thread odd = new Thread(task);
        Thread even = new Thread(task1);
        //4.启动线程
        odd.start();
        even.start();
        //5.获取结果--->get方法具有阻塞功能,等待call方法结束后,才会返回
        Integer oddSum = task.get();
        Integer evenSum = task1.get();
        System.out.println("1-100奇数和:"+oddSum);
        System.out.println("1-100偶数和:"+evenSum);
        System.out.println("1-100总和:"+(oddSum+evenSum));*/

        //3获取任务结果,等待任务执行完毕后才会返回
        System.out.println("奇数和:" + future.get());
        System.out.println("偶数和:" + future2.get());
        System.out.println("总和:" + (future.get() + future2.get()));
        //关闭线程池
        es.shutdown();
    }
}

7 Lock接口

7.1 Lock

  • JDK5加入,与synchronized比较,显示定义,结果更加灵活.
  • 提供更多实用性方法,功能更强大,性能更优越.

常用方法:

方法名描述
void lock()获取锁,如锁被占用,则等待.
boolean tryLock()尝试获取锁(成功返回true。失败返回false,不阻塞)。
void unlock()释放锁。

7.2 重入锁

ReentrantLock:

  • Lock接口的实现类,与synchronized一样具有互斥锁功能.

案例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Classname TestReentrantLock
 * @Description TODO:测试重入锁
 * @Created by xx
 */
public class TestReentrantLock {
    public static void main(String[] args) {
        SaleTicket st = new SaleTicket();
        Thread t = new Thread(st, "1");
        Thread t2 = new Thread(st, "2");
        Thread t3 = new Thread(st, "3");
        t.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket implements Runnable {
    private int ticket = 100;

    public int getTicket() {
        return ticket;
    }

    public void setTicket(int ticket) {
        this.ticket = ticket;
    }

    //创建一个重入所对象
    Lock lr = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //上锁
                lr.lock();
                if (ticket <= 0) {
                    break;
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + "购票,还剩:" + ticket);
            } finally {
                //释放锁
                lr.unlock();
            }
        }
    }
}

7.3 读写锁

ReentrantReadWriteLock:

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
  • 支持多次分配读锁,使多个读操作可以并发执行。
    互斥规则:
  • 写-写:互斥,阻塞。
  • 读-写:互斥,读阻塞写、写阻塞读。
  • 读-读:不互斥、不阻塞。
  • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

案例:测试读写锁

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Classname ReadWriteDemo
 * @Description TODO:测试读写锁
 * @Created by xx
 */
public class ReadWriteDemo {
    //创建读写锁
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //获取读锁
    private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    //获取写锁
    private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.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("读取:" + this.value);
            this.value = value;
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

class TestReadWriteLock{
    public static void main(String[] args) {
        ReadWriteDemo readWriteDemo = new ReadWriteDemo();
        //创建线程池
        ExecutorService es = Executors.newFixedThreadPool(10);
        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();
        //分配两个写任务
        for (int i = 0; i < 2; i++) {
            es.submit(write);
        }
        //分配8个读任务
        for (int i = 0; i < 8; i++) {
            es.submit(read);
        }

        //关闭资源
        es.shutdown();
        //等待开始但未完成的`任务完成
        while (!es.isTerminated()){}
        //任务结束时的毫秒值
        long end = System.currentTimeMillis();
        System.out.println("用时:"+(end-start));
    }
}

8 线程安全的集合

Collection工具类中提供了多个可以获得线程安全集合的方法.

方法名
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实现.

8.1 CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版读写分离.
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁.
  • 写入时,现copy一个容器副本,再添加元素,最后替代引用.
  • 使用方式与ArrayList无异.

案例:测试安全的ArrayList集合

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

/**
 * @Classname TestCopyOnWriteArrayList
 * @Description TODO:测试线程安全的ArrayList集合
 * @Created by xx
 */
public class TestCopyOnWriteArrayList {
    public static void main(String[] args) {
        //1.创建集合
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        //2.使用多线程操作
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //3.提交任务
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        list.add(Thread.currentThread().getName()+"==="+new Random().nextInt(1000));
                    }
                }
            });
        }
        
        //关闭线程池
        executorService.shutdown();while (!executorService.isTerminated()){}
        //5.打印结果
        System.out.println("元素个数:"+list.size());
        for (String str:list) {
            System.out.println(str);
        }
    }
}

8.2 CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现.
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。
  • 如存在元素,则不添加(扔掉副本)。
public class TestCopyOnWriteArraySet {
	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());
	}
}

8.3 ConcurrentHshMap

  • 初始容量默认为16段(Segment),使用分段锁设计.
  • 不对整个Map集合加锁,而是为每个Segment加锁.
  • 当多个对象存入同一个Segment是,才需要互斥.
  • 最理想状态为16个对象分别存入16个Segment,并行数量16.
  • 使用方法与HashMap无异.
public class TestConcurrentHashMap {
	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();
		}
	}

}

8.4 Queue

  • Collection的子接口,表示队列FIFO(First In First Out).

常用方法:

方法名描述
boolean offer(E e)顺序添加一个元素 (到达上限后,再添加则会返回false)。
E poll()获得第一个元素并移除 (如果队列没有元素时,则返回null)。
E keep()获得第一个元素但不移除 (如果队列没有元素时,则返回null)。
public class TestQueue {
	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());
		
	}
}

8.5 ConcurrentLinkedQueue

  • 线程安全,可高效读写的队列,高并发下性能最好的队列.
  • 无锁,采用CAS比较交换算法,修改的方法包含三个核心参数(V,E,N).
  • V:要更新的变量,E:预期值,N:新值.
  • 只有当V==E时,V=N;否则表示已被更新过,取消当前操作.
public class TestConcsurrentLinkedQueue {
	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());
		}
		
	}
}

8.6 BlockingQueue

  • Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法.
  • 可用于解决生产消费者问题.

常用方法:

方法名描述
void put(E e)将指定元素插入此队列中,如果没有可用空间,则等待。
E take()获取并移除此队列头部元素,如果没有可用元素,则等待。
8.6.1 ArrayBlockingQueue
  • 数组结构实现,有界队列.
  • 手工固定上限.
8.6.2 LinkedBlockingQueue
  • 链表结构实现,无界队列.
  • 默认上限:Integer.MAX_VALUE.
  • 使用方法和ArrayBlockingQueue一致.

案例:测试阻塞队列完成生产者消费者问题

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @Classname TestBolckQueue
 * @Description TODO:测试阻塞队列完成生产者消费者问题
 * @Created by xx
 */
public class TestBolckQueue {
    public static void main(String[] args) {
        //创建阻塞队列
        BlockingQueue<Doll> bq = new ArrayBlockingQueue<Doll>(5);

        //创建任务
        Runnable r = new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Doll D = new Doll(i, "娃娃" + i);
                    try {
                        //队列添加元素
                        bq.put(D);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "生产" + D);
                }
            }
        };

        Runnable r2 = new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Doll d = new Doll(i, "娃娃" + i);
                    try {
                        //删除队列元素
                        bq.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "消费" + d);
                }
            }
        };

        //创建线程
        Thread t = new Thread(r, "生产者");
        Thread t2 = new Thread(r2, "消费者");

        //启动线程
        t.start();
        t2.start();


    }
}

class Doll {
    private int count;
    private String name;

    @Override
    public String toString() {
        return "Doll{" +
                "count=" + count +
                ", name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Doll(int count, String name) {
        this.count = count;
        this.name = name;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Doll() {
    }

    public Doll(int count) {
        this.count = count;
    }

    /**
     * 生产娃娃
     */
    public synchronized void produceDoll(int count) throws InterruptedException {
        while (this.getCount() > 25) {
            //有   25个娃娃不生产
            this.wait();
        }
        this.count += count;
        System.out.println(Thread.currentThread().getName() + "生产娃娃" + ",共有" + this.getCount() + "件娃娃");
        this.notifyAll();
    }

    /**
     * 购买娃娃
     */
    public synchronized void buyDoll(int count) throws InterruptedException {
        while (this.getCount() <= 0) {
            //没有娃娃不购买
            this.wait();
        }
        this.count -= count;
        System.out.println(Thread.currentThread().getName() + "购买娃娃" + ",还剩" + this.getCount() + "件娃娃");
        this.notifyAll();
    }
}

死锁的四个必要条件

  • 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
  • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
  • 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
  • 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
    以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值