01-juc-入门概念:
juc: java.util .concurrent工具包的简称。这是一个处理线程的工具包, JDK .1.5开始出现的。
进程和线程:
进程:在面向线程设计的计算机结构中,进程是线程的容器。由多个线程组成。
线程:是操作系统能够进行运算调度的最小单位。 它被包含在进程之中,是进程中的实际运作单位
线程的状态:
创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)
查看源代码Thread.State
public enum State {
NEW,(新建)
RUNNABLE,(准备就绪)
BLOCKED,(阻塞)
WAITING,(不见不散)
TIMED_WAITING,(过时不候)
TERMINATED;(终结) }
wait和sleep的区别:类的区别和是否释放锁的区别。
wait:是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程
sleep:Thread 的静态方法,任何对象实例都能调用。不会释放锁,也不需要占用锁。让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。线程进入休眠,对象的机锁不释放。其他线程无法访问。
并发和并行:
串行:表示所有任务都一一按先后顺序进行
并发:抢占cpu,多个任务交互执行,是同一时刻多个线程在访问同一个资源
并行:同时取得多个任务,并同时去执行所取得的这些任务
管程:
管程在java中是锁,在操作系统中是monitor监视器,代表一种同步机制,同一时间内只能有一个线程访问且被保护数据
比如jvm的同步基于进入和退出,是管程对象实现,每个对象都有一个monitor管程对象,都会随着java的对象进行创建和销毁
管程对象对临界区加锁和解锁,大意就是进加锁,退是解锁,通过管程对象管理
多线程编程步骤**(高内聚低耦合)**:第一步 创建资源类,创建属性和操作方法,第二步 创建多线程调用资源类的方法。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2.修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
synchronized不属于方法的一部分,因此synchronized 关键字不能被继承。如果在父类中的某个方法使用了synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。
3.修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4.修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
package com.juc.sync;//1。创建一个资源类,定义属性和操作的方法
class Ticket{//票数private int number=30;//操作的方法:买票public synchronized void sale(){//判断:是否有票if(number > 0) {System.out.println(Thread.currentThread().getName()+" : 卖出:"+(number--)+" 剩下:"+number);}}
}
//2.创建多个线程,调用资源类的操作方法
public class SaleTick {public static void main(String[] args) {//创建ticket对象Ticket ticket = new Ticket();//创建线程new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<40;i++){ticket.sale();}}},"A1").start();new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<40;i++){ticket.sale();}}},"A2").start();new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<40;i++){ticket.sale();}}},"A3").start();}
}
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待I0或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程只能等待。
可重入锁:类比厕所,可重用
LOCK是类,可通过类实现同步访问,多个接口实现类:可重入锁等
可重入锁的代码定义private final ReentrantLock lock = new ReentrantLock(true);上锁lock.lock();解锁lock.unlock();
上锁与解锁中的代码如果出现异常,解锁会执行不了,所以最好加try…finally
其他同synchronized方法。
//卖票方法public void sale() {//上锁lock.lock();try {//判断是否有票if(number > 0) {System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);}} finally {//解锁lock.unlock();}}
Lock与的Synchronized区别.
Lock不是Java语言内置的,synchronized 是Java语言的关键字,是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
Lock和synchronized的不同, synchronized不需要手动释放锁,当synchronized 方法或者synchronized 代码块执行完之后,
系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
public interface Lock {//用来获取锁。如果锁已被其他线程获取,则进行等待。必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一
般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。void lock();//void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();//关键字synchronized与wait0/notify0可实现等待/通知模式,Lock的newContition()方法返回Condition对象,Condition 类
也可以实现等待/通知模式。用notify()通知时, JVM会随机唤醒某个等待的线程,使用Condition类可以进行选择性通知,Condition 比较常用的两个方法:
await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
signal()用于唤醒一个等待的线程。Condition newCondition();
}
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法
ReentrantLock可重入锁
ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和 writeLock()用来获取读锁和写锁
writeLock();来获取读锁
readLock();获取写锁
假设有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁,反之同理
线程间通信的模型有两种:共享内存和消息传递
线程间的通信具体步骤:
synchronized案例
操作线程的时候,等待线程使用wait(),通知另外的线程操作用notify()、notifyAll()
假设有两个线程,该线程在执行过程中,判断值(不是该值等待,让其他线程抢),操作值,通知另外一个线程的调度
通过使用两个线程对0这个值操作,一个线程加1,一个线程减1,交替实现多次
package com.juc.sync;//第一步,创建资源类,定义属性和操作方法
class Share {//初始值private int number = 0;//第二步,干活,执行方法//+1的方法public synchronized void incr() throws InterruptedException {//第二步 判断 干活 通知
// if (number != 0) { //判断number值是否是0,如果不是0,等待
// this.wait(); //在哪里睡,就在哪里醒
// }while (number != 0) { //可以避免虚假唤醒this.wait();}//如果number值是0,就+1操作number++;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程this.notifyAll();}//-1方法public synchronized void decr() throws InterruptedException {//判断
// if (number != 1) {
// this.wait();
// }while (number != 1) { //可以避免虚假唤醒this.wait();}//干活number--;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程this.notifyAll();}
}/*** 实现一个两个线程,一个线程对数据+1,另一个线程对数字-1.测试线程之间的通讯*/
public class ThreadDemo1 {//第三步:创建多个线程,调用资源类的操作方法public static void main(String[] args) {Share share = new Share();//创建线程new Thread(()->{for (int i=1;i<=10;i++){try {share.incr();}catch (Exception ex){ex.printStackTrace();}}},"A1").start();//做加一操作//创建线程new Thread(()->{for (int i=1;i<=10;i++){try {share.decr();}catch (Exception ex){ex.printStackTrace();}}},"A2").start();//做减一操作//多个线程产生的虚假唤醒情况,虚假唤醒是因为wait(); //在哪里睡,就在哪里醒,会跳过之前的if判断,//直接执行下面的代码。修改方式将if改为whilenew Thread(()->{for (int i=1;i<=10;i++){try {share.incr();}catch (Exception ex){ex.printStackTrace();}}},"A3").start();//做加一操作//创建线程new Thread(()->{for (int i=1;i<=10;i++){try {share.decr();}catch (Exception ex){ex.printStackTrace();}}},"A4").start();//做减一操作}
}
if产生了虚拟唤醒:如果一个线程执行完毕后,通知其他线程,该线程又进入等待睡眠,可能会因为某些原因被唤醒后,if结构的语句就不会判断了,一直往下执行,所以需要将if换成while结构,每次都判断。因为wait在哪里睡眠就在哪里被唤醒,结果被某个异常唤醒了后回不去了,if结构不会在判断了,需要更改为while。
lock实现以上案例
private int number = 0;//创建lockprivate Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();//+1方法public void incr(){//上锁lock.lock();try {//判断while (number!=0){condition.await();}//执行干活number++;System.out.println(Thread.currentThread().getName() + " :: " + number);//通知其他线程condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {//解锁lock.unlock();}}
线程间的定制化通讯
定制化通信即让线程进行一定的顺序操作
案列:启动三个线程,按照如下要求:A1打印5此,A2打印10次,A3打印15次,一共进行10轮
思路:
通过标志位操作那个线程执行,是该标志位则执行操作,并且修改为下一个标志位,通知下一个标志位的线程
package com.juc.lock;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 线程的自定义通讯,指定线程顺序执行和执行次数。案例来源尚硅谷*/
//第一步创建资源类
class ShareResource{//属性,定义标志位private int flag=1;//1,A1线程,2,A2线程 3表示A3线程//创建Lockprivate Lock lock = new ReentrantLock();//创建三个conditionprivate Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();//打印5次的方法,参数第几轮public void print5(int loop){lock.lock();try {while (flag!=1){condition1.await();}for (int i=1;i<=5;i++){System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);}flag=2;//先修改标识位为2condition2.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void print10(int loop){lock.lock();try {while (flag!=2){condition2.await();}for (int i=1;i<=10;i++){System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);}flag=3;//先修改标识位为3condition3.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void print15(int loop){lock.lock();try {while (flag!=3){condition3.await();}for (int i=1;i<=15;i++){System.out.println(Thread.currentThread().getName()+"::"+i+":轮数:"+loop);}flag=1;//先修改标识位为2condition1.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}public class ThreadDemo3customcom {public static void main(String[] args) {ShareResource shareResource = new ShareResource();new Thread(()->{for(int i=1;i<=10;i++){try {shareResource.print5(i);}catch (Exception ex){ex.printStackTrace();}}},"A1").start();new Thread(()->{for(int i=1;i<=10;i++){try {shareResource.print10(i);}catch (Exception ex){ex.printStackTrace();}}},"A2").start();new Thread(()->{for(int i=1;i<=10;i++){try {shareResource.print15(i);}catch (Exception ex){ex.printStackTrace();}}},"A3").start();}
}
上一篇:Android源码编译原生模拟器