1.临界资源问题:
1.1:临界资源:同一个进程中多个线程共享的资源,叫临界资源。
1.2:临界资源问题:当一个线程获得cpu时间片,运行共享资源,还没来得及修改共享资源 时,另一个线程将cpu时间片抢过去,运行还未来得有修改共享资源时就出 现问 题,叫临界资源问题。
2.线程同步:解决临界资源问题。将要一起执行的代码绑定在一块形成代码块,当一个线程抢到cpu 时间片,进入这个代码块执行时,其他的线程不能进行这个代码块中执行,直到当前的线 程执行完代码块,其他的线程抢到cpu时间片并且此时代码块中无线程执行,这个线程 才能进入代码块去执行。
2.1:同步代码块:
2.1.1:synchronized (同步锁) {
要绑定在一起执行的代码;
}
2.1.2:同步锁:只要是这多个线程共享的一个对象都可以作为同步锁对象,锁对象的值一般是不变。
2.1.3:锁的范围:锁的范围越小越好。
eg:/**
* 任务类
* @author sx
* @version 1.0 2019年7月16日
*/
public class MyRunnable2 implements Runnable{
/**
* 票数的成员变量
*/
public int ticket=1000;
/**
* 声明一个变量作为锁对象
*/
Object ob=new Object();
/**
* 任务方法
*/
@Override
public void run() {
while (true) {
//7,1,4
//同步块
synchronized (ob) {
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+"正在销售第"+ticket+"张票");
ticket--;
}else {
break;
}
}
}
System.out.println("票已售完");
}
}
public static void main(String[] args) {
//创建任务对象
MyRunnable2 mr=new MyRunnable2();
//创建10个线程对象售票,10个线程共享一个任务对象
for (int i = 1; i <=10; i++) {
if (i==10) {
Thread t1=new Thread(mr);
t1.setName("窗口0"+i);
t1.start();
}else {
Thread t1=new Thread(mr);
t1.setName("窗口00"+i);
t1.start();
}
}
}
2.2:同步方法:
2.2.1:同步方法的语法:
public synchronized 返回值类型 方法名(形参列表) {
要绑定在一起执行的代码;
}
2.2.2:同步方法的锁范围越小越好。
eg:/**
* 任务类
* @author sx
* @version 1.0 2019年7月16日
*/
public class MyRunnable2 implements Runnable{
/**
* 票数的成员变量
*/
public int ticket=1000;
/**
* 声明一个变量作为锁对象
*/
Object ob=new Object();
/**
* 任务方法
*/
@Override
public void run() {
while (true) {
if(saleTicket()==true) {
break;
}
}
System.out.println("票已售完");
}
/**
* 同步方法
*/
public synchronized boolean saleTicket() {
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+"正在销售第"+ticket+"张票");
ticket--;
return false;
}else {
return true;
}
}
}
public static void main(String[] args) {
//创建任务对象
MyRunnable2 mr=new MyRunnable2();
//创建10个线程对象售票,10个线程共享一个任务对象
for (int i = 1; i <=10; i++) {
if (i==10) {
Thread t1=new Thread(mr);
t1.setName("窗口0"+i);
t1.start();
}else {
Thread t1=new Thread(mr);
t1.setName("窗口00"+i);
t1.start();
}
}
}
2.3:对象互斥锁:
2.3.1:对象互斥锁的语法:
/**
* 声明一个锁对象
*/
Lock l=new ReentrantLock();
//上锁
l.lock();
要绑定在一起执行的代码
//解锁
l.unlock();
2.3.2:锁的范围越小越好。
2.3.3:对象互斥锁要确保在任何情况下解锁,防止出现死锁。
eg:public class MyRunnable2 implements Runnable{
/**
* 票数的成员变量
*/
public int ticket=1000;
/**
* 声明一个对象互斥锁对象
*/
Lock l=new ReentrantLock();
/**
* 任务方法
*/
@Override
public void run() {
while (true) {
try {
//上锁
l.lock();
if (ticket>0) {//9
System.out.println(Thread.currentThread().getName()+"正在销售第"+ticket+"张票");
ticket--;
}else {
break;
}
} finally {
//解锁
l.unlock();
}
}
System.out.println("票已售完");
}
}
3.线程同步的作用:解决临界资源问题
优点:安全性高。同步的代码块中每次只允许一个线程进去执行,只有等待这个线程执行完 了,其他的线程才有机会进行执行。
缺点:效率低。一个线程要抢到cpu时间片,并且同步代码块中没有线程在执行,才能进入同步 块中去执行。
4.线程池的作用:减少创建线程和关闭线程的时间和系统资源的消耗。
5.线程池:存放多个线程对象的容器。
6.线程池中重要的接口和类
6.1:ExecutorService:线程池的接口
6.2:Executors:创建各种线程池的工具类
6.3:newSingleThreadExecutor():创建一个单线程的线程池。这个线程池只有一个线程在工 作,也就是相当于单线程串行执行所有任务。如果这个唯一的线 程因 为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序 按照任务的提交顺序执行。
6.4:newFixedThreadPool(int n)
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池 的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行 异常而结束,那么线程池会补充一个新线程。
6.5:newCachedThreadPool()
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那 么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又 可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池 大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
eg:public static void main(String[] args) {
//创建线程池对象
//ExecutorService pool1=Executors.newSingleThreadExecutor();
//ExecutorService pool1=Executors.newFixedThreadPool(2);
ExecutorService pool1=Executors.newCachedThreadPool();
//创建任务对象
MyRunnable mr1=new MyRunnable();
MyRunnable mr2=new MyRunnable();
MyRunnable mr3=new MyRunnable();
//从线程池中取出线程对象执行任务
pool1.submit(mr1);
pool1.submit(mr2);
pool1.submit(mr3);
//关闭线程池
pool1.shutdown();
}