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() {
@Override
public void run() {
for(int i=0;i<40;i++){
ticket.sale();
}
}
},"A1").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<40;i++){
ticket.sale();
}
}
},"A2").start();
new Thread(new Runnable() {
@Override
public 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改为while
new 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;
//创建lock
private 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线程
//创建Lock
private Lock lock = new ReentrantLock();
//创建三个condition
private 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;//先修改标识位为2
condition2.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;//先修改标识位为3
condition3.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;//先修改标识位为2
condition1.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();
}
}