线程同步
1、具有至少两个线程同时在执行,称为 并发执行2、多个线程操作同一个数据(此数据较共享数据,或者临界资源)
如果并发执行的多个线程间需要共享资源或交换数据,则该组线程为交互线程
交互线程并发执行时相互之间会干扰或影响其他线程的执行结果,因此交互线程间需要有同步机制
当两个或多个线程需要共享同一个数据资源时,为保证数据的正确性,需要通过某种方法来确定该数据资源在任一刻仅被一个线程占用,实现上述目的的过程叫做同步
交互线程之间存在两种关系:竞争关系和合作关系
竞争关系:采用线程互斥方式解决共享资源冲突问题(同步监视器(synchronized),同步锁(Lock))
合作关系:采用线程同步方式解决线程间通信及因执行速度不同而引起的不同步问题(信号灯)
线程的同步机制:
1、线程互斥(用于竞争关系的交互线程)
2、线程同步(用于协作关系的交互线程)
线程互斥是线程同步的特殊情况
线程互斥:解决线程间竞争关系的手段
指若干个线程要使用同一共享资源时,任何时刻最多允许一个线程去使用,其他要使用该资源的线程必须等待,直到占有资源的线程释放该资源
临界资源(critical resource):共享变量代表的资源;
临界区(critical section):并发线程中与共享变量有关的程序段
java提供两种方式进行线程同步:
1、同步监视器(synchronized)
2、同步锁(Lock)
synchronized
Java提供关键字synchronized用于声明一段程序为临界区,使线程对临界资源采用互斥使用方式
synchronized的用法:声明一条语句、声明一个方法
同步语句:使用synchronized 声明一条语句为临界区,该语句称为同步语句
synchronized (对象){
语句
}
对象:多个线程共同操作的公共变量,即需要被锁定的临界资源;将被互斥地使用;
语句:临界区,描述线程对临界资源的操作。
执行过程
当第1个线程希望进入临界区执行<语句>时,它获得临界资源即指定<对象>的使用权,并将对象加锁(对象锁),然后执行语句对对象进行操作
在此过程中,如果有第2个线程也希望对同一个对象执行语句,由于作为临界区资源的对象已经被锁定,所以第2个线程必须等待
当第1个线程执行完临界区语句,它将释放对象锁
之后第2个线程才能获得对象的使用权并运行
同步方法:使用synchronized 声明一个方法为临界区,该方法称为同步方法
public synchronized void method1(){
方法体
}
方法体:临界区,描述线程对临界资源的操作。
synchronized()解决互斥问题
代码示例
package com.gem.day16.tongbu;
public class TestTicket {
public static void main(String[] args) {
MyTicket mt = new MyTicket(20);
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
class MyTicket implements Runnable{
int ticket;
public MyTicket(int ticket) {
super();
this.ticket = ticket;
}
@Override
public void run() {
while(true) {
synchronized (this) {
if(ticket>0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第-->"+ticket--+"张票");
}else{
break;
}
}
}
}
}
在没使用synchronized()时 会发现出现了0,重复, 这与我们的程序设计不符
在使用synchronized()发现解决问题 ,在一个线程调用同一个对象时synchronized()将其监视,使其他线程不能调用这个对象 解决了线程互斥
Lock()解决线程互斥问题
package com.gem.day16.tongbu;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestTicket {
public static void main(String[] args) {
MyTicket mt = new MyTicket(20);
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
class MyTicket implements Runnable{
int ticket;
Lock lock = new ReentrantLock();
public MyTicket(int ticket) {
super();
this.ticket = ticket;
}
@Override
public void run() {
while(true) {
lock.lock();
try {
if(ticket>0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第-->"+ticket--+"张票");
}else{
break;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
出现没处理的问题 用Lock解决
线程死锁
一组线程如果都获得了部分资源,还想得到其他线程所占用的资源,最终所有的线程都将
陷入死锁
代码示例如下
package com.geminno.day16.thread;
public class TestLock {
public static void main(String[] args) {
DeadLock d1 = new DeadLock("张三","五千万");
d1.f = true;
DeadLock d2 = new DeadLock("张三","五千万");
d2.f = false;
new Thread(d1).start();
new Thread(d2).start();
}
}
class DeadLock implements Runnable{
private String money ;
private String person;
public boolean f ;
public DeadLock(String money, String person) {
super();
this.money = money;
this.person = person;
}
@Override
public void run() {
if(f) {
synchronized (person) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("给我钱, 我就放人。。。");
synchronized (money) {
System.out.println("先放人, 我在给钱。。。");
}
}
}
if(!f) {
synchronized (money) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("先放人, 我在给钱。。。");
synchronized (person) {
System.out.println("先给钱,在放人。。。。");
}
}
}
}
}
控制台显示
先放人, 我在给钱。。。
给我钱, 我就放人。。。
陷入死锁 后面的程序没法执行
线程协作
通过线程的协作实现线程同步,也是通过两种方式:
1、通过Object类中的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信,在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调用notify()方法或notifyAll()方法),在线程中调用notify()方法或notifyAll()方法,将通知其他线程从wait()方法处返回。
2、利用Condition接口: Condition是在java 1.5中才出现的,使用Condition的await()、signal()这种方式实现线程间协作。
第一种方式代码示例如下
生产者生产一部电影
消费者观看一部电影
不能再生产者未生产的时候消费者观看电影
电影对象:
package com.geminno.day16.pc;
public class Movie {
private String name;
/**
* 布尔类型的变量 f 就是一个信号灯
* 当f是true时 ,需要生产 消费者要停止执行
* 当f是false的时,需要消费 生产者需要停止执行
*/
boolean f = true;
public synchronized void play(String name) {
if(!f) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name;
System.out.println("拍摄一部电影:--"+name);
this.notify();//通知对方观看
f = false;
}
public synchronized void see() {
if(f) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我观看了一部电影;"+name);
this.notifyAll();
f = true;
}
}
生产者代码:
package com.geminno.day16.pc;
package com.geminno.day16.pc;
public class Player implements Runnable{
private Movie m ;
public Player(Movie m) {
super();
this.m = m;
}
@Override
public void run() {
for(int i=0;i<20 ;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(i % 2 == 0) {
m.play("天龙八部");
}else {
m.play("渴望");
}
}
}
}
消费者
package com.geminno.day16.pc;
public class Watcher implements Runnable{
private Movie m ;
public Watcher(Movie m) {
super();
this.m = m;
}
@Override
public void run() {
for(int i=0;i<20;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
m.see();
}
}
}
测试:
package com.geminno.day16.pc;
public class TestMoive {
public static void main(String[] args) {
Movie m = new Movie();
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
控制台输出在生产者生产一部电影以后,消费者才会观看一部电影
第二种方式使用Condition接口:
class ThreadA extends Thread{
private Lock lock;
private Condition condition;
public ThreadA(String name,Lock lock,Condition condition) {
super(name);
this.lock = lock;
this.condition = condition;
}
public void run() {
lock.lock(); // 获取锁
try {
System.out.println(Thread.currentThread().getName()+" wakup others");
condition.signal(); // 唤醒“condition所在锁上的其它线程”
} finally {
lock.unlock(); // 释放锁
}
}
}
public class TestLock {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
ThreadA ta = new ThreadA("ta",lock,condition);
lock.lock(); // 获取锁
try {
System.out.println(Thread.currentThread().getName()+" start ta");
ta.start();
System.out.println(Thread.currentThread().getName()+" block");
condition.await(); // 等待
System.out.println(Thread.currentThread().getName()+" continue");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}
控制台显示:
main start ta
main block
ta wakup others
main continue线程池 ThreadPoolExecutor:
corePoolSize - 池中所保存的核心线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序
threadFactory - 执行程序创建新线程时使用的工厂
线程池规则
下面都假设任务队列没有大小限制:
如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。
如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
任务队列大小有限时
当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常
代码示例如下
public class TestThreadPool {
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor excutor = new ThreadPoolExecutor(10, 20, 3000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
for(int i=0;i<15;i++) {
MyTask m = new MyTask(i);
/*Thread t = new Thread(m);
t.start();*/
excutor.execute(m);
System.out.println("任务队列中有--------:"+excutor.getQueue().size());
}
excutor.shutdown();
}
}
class MyTask implements Runnable{
private int num;
public MyTask(int num) {
this.num = num;
}
@Override
public void run() {
System.out.println("正在执行任务:" + num);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("任务--"+num+"--执行完毕");
}
控制台:
正在执行任务:0
任务队列中有--------:0
任务队列中有--------:0
任务队列中有--------:0
正在执行任务:1
任务队列中有--------:0
正在执行任务:2
正在执行任务:3
任务队列中有--------:0
正在执行任务:4
任务队列中有--------:0
任务队列中有--------:0
正在执行任务:5
正在执行任务:6
任务队列中有--------:0
正在执行任务:7
任务队列中有--------:0
正在执行任务:8
任务队列中有--------:0
正在执行任务:9
任务队列中有--------:1
任务队列中有--------:2
任务队列中有--------:3
任务队列中有--------:4
任务队列中有--------:5
任务--3--执行完毕
任务--0--执行完毕
正在执行任务:10
任务--1--执行完毕
任务--2--执行完毕
正在执行任务:12
正在执行任务:11
正在执行任务:13
任务--6--执行完毕
任务--5--执行完毕
任务--4--执行完毕
正在执行任务:14
任务--7--执行完毕
任务--8--执行完毕
任务--9--执行完毕
任务--11--执行完毕
任务--10--执行完毕
任务--12--执行完毕
任务--13--执行完毕
任务--14--执行完毕
发现 在线程池中有核心线程闲置时会调用核心线程
在线程池中核心线程被分配完毕,剩余线程在DEQUE任务队列中等待
在其他线程运行完毕后任务队列中的线程才会被分配到线程
TheadLoacl
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度
ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程
代码示例:
package com.gem.day17.ThreadPool;
public class TestThreadLocal {
private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i < 15; i++) {
new Thread(new Mythread(i)).start();;
}
}
static class Mythread implements Runnable {
private int index;
public Mythread(int index) {
super();
this.index = index;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("线程" + index + "的初始value:" + value.get());
for (int i = 0; i < 10; i++) {
value.set(value.get() + i);
}
System.out.println("线程" + index + "的累加value:" + value.get());
}
}
}
控制台打印
每个value的值是一样的 也就是说每个线程都调用了自己的value属性
用ThreadLocal实现了隔离每个线程中变量相对独立于其他线程的变量