注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能 执行一个代码,因为切换的很快,所以就有同时执行的错局。注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
在操作系统中运行的程序就是我们所说的进程,比如我们电脑的应用 网易云、百度、微信等这些都是一个进程。
进程中实现的不同功能,我们称之为线程,比如一个视频中我们能够同时看到弹幕,听到声音,看图像这都可以叫做不同的线程
三者之间的关系:程序中包含多个进程 进程中同时包含多个线程(且至少有一个线程为主线程)
public class Thread1 {
//线程开启不一定执行,由cpu进行调度,
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<200;i++){
System.out.println("我是主线程"+i);
}
}
}
//主线程 子线程的执行顺序 由cpu进行调度,cpu执行速度很快,一般先执行主线程
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<50;i++){
System.out.println("我是子线程"+i);
}
}
}
//多线程抢票
public class ticket {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
new Thread(myTicket,"A").start();
new Thread(myTicket,"B").start();
}
}
class MyTicket implements Runnable {
public int tickets = 100;
@Override
public void run() {
while (tickets>0) {
System.out.println(Thread.currentThread().getName() + "抢到了第" + tickets-- + "张票");
}
}
}
//多线程下载图片
//实现图片的下载要先导入common-io的jar包
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//执行线程
downThread t1 = new downThread("https://img-bss.csdnimg.cn/1615183593661.png","a.jpg");
downThread t2= new downThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa0.att.hudong.com%2F30%2F29%2F01300000201438121627296084016.jpg&refer=http%3A%2F%2Fa0.att.hudong.com&app=2002&size=f9999," +
"10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618125226&t=206ef43a6621700711f384e286ee4220","b.jpg");
//创建执行服务 开启线程池
ExecutorService ser= Executors.newFixedThreadPool(2);
//提交执行
Future<Boolean> future1=ser.submit(t1);
Future<Boolean> future2=ser.submit(t2);
//获取结果
boolean flag1= future1.get();
boolean flag2= future2.get();
System.out.println(flag1);
System.out.println(flag2);
//关闭服务
ser.shutdownNow();
}
}
class downThread implements Callable<Boolean> {
public String url;
public String name;
public downThread(String url,String name){
this.url=url;
this.name=name;
}
@Override
public Boolean call(){
//执行体
webDownLoad webDownLoad = new webDownLoad();
webDownLoad.photoDown(url,name);
System.out.println(name+"图片下载完成");
return true;
}
}
class webDownLoad {
//定义下载图片的方法
public void photoDown(String url, String name) {
//借助封装的类 FileUtils 将该路径的图片 拷贝到指定文件
String file="G:\\idea\\bilibili\\kuang\\Thread\\src\\com\\zz\\demo01";
try {
//将网络路径 url 的资源下载到指定的文件夹中去,并命名为name
FileUtils.copyURLToFile(new URL(url), new File(file,name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.首先来个赛道距离,然后要离终点越来越近
//龟兔赛跑 实例
/**
* 1 自定义一个boolean方法 决出胜利者
* 2 将逻辑代码放入 线程体run方法中
* 3 开启乌龟、兔子两条线程
*/
public class game {
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}
class Race implements Runnable {
public int steps = 10000;
public String winner;
@Override
public void run() {
while (steps > 0) {
if (Thread.currentThread().getName().equals("兔子")) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = gameover(steps);
if (flag) {
break;
} else {
//没到终点就继续跑
System.out.println(Thread.currentThread().getName() + "跑了" + steps-- + "步");
}
}
}
//决出胜利者=
public boolean gameover(int step) {
if (winner != null) {
//已经产生了胜利者
return true;
}
if (step <= 1) {
winner = Thread.currentThread().getName();
System.out.println(winner + "赢了");
return true;
}
return false;
}
}
代理模式在我们生活中很常见,例如 按理说我们在生活中购物的时候,可以直接从工厂进行购物,但是我们生活中常常不是这样,一般都是厂家委托给供销商(超市)进行销售,而我们不直接和厂商进行关联,这其中就引用了静态代理的思想,厂家相当于真实角色,而超市相当于代理角色,厂家产品相当于接口,我们作为购买者 客户端。
其实代理角色的作用就是,辅助真实角色完成一些事情,在真实角色业务的前提下,还可以增加其他的业务,一起帮助真实角色共同完成。后面AOP切面编程就是运用了该思想。
这是常见代理模式常见的 UML 示意图。
下面通过模拟婚庆公司,来实现静态代理 我们真实角色,婚庆公司代理角色 结婚事件为接口
/**
* @Author zz
* @Date 2021/3/12 15:27
*/
//静态代理 多线程也是静态代理原理实现
public class Proxy {
public static void main(String[] args) {
//真实角色
you you = new you();
//代理角色
Marry marry = new WeddingCompany(you);
//代理角色 帮助真实角色实现
marry.marride();
}
}
interface Marry{
//定义一个结婚接口
void marride();
}
//真实角色 结婚
class you implements Marry{
@Override
public void marride() {
System.out.println("xxx结婚了");
}
}
//婚庆公司 帮助我实现结婚 婚庆公司可以在结婚前后增加所需要的业务
class WeddingCompany implements Marry{
//引入真实角色
public Marry target;
public WeddingCompany(Marry target){
this.target=target;
}
@Override
public void marride() {
before();
target.marride();
after();
}
private void before() {
System.out.println("结婚前,进行场地的布置");
}
private void after() {
System.out.println("结婚后,收拾东西回公司");
}
}
结果:
结婚前
xxx结婚了
结婚后
现在可以看到,代理模式可以在不修改被代理对象you的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口marride(),或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 WeddingCompany代理角色这个类。下面要介绍的内容就是动态代理。
理解完了静态代理,我们应用静态代理的思想来分析多线程
Thread底层也是通过静态代理原理实现的,通过我们开启子线程来定义真实角色,Thread为代理角色,Runnable为要实现得到接口,run为接口中的方法
Runnable接口,以及里面的run方法
public interface Runnable {
/**
* When an object implementing interface Runnable
is used
* to create a thread, starting the thread causes the object's
* run
method to be called in that separately executing
* thread.
*
* The general contract of the method run
is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
我们在开启子线程的过程中,其实就是定义一个真实角色MyThread实现了一个Runnable接口,并重写了Runnable接口里面的run()方法 真实角色
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(“我是子线程,同时是真实角色”);
}
}
这里代理对象为Thread类,我们通过Thread源码,分析得Thread也是实现了Runnable接口,并实现了run方法 代理角色
* @author unascribed
* @see Runnable
* @see Runtime#exit(int)
* @see #run()
* @see #stop()
* @since JDK1.0
*/
//这里代理角色Thread也实现了Runnable接口
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing does. */
private static native void registerNatives();
static {
registerNatives();
}
//代理对象中的 构造器
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
代理真实对象实现方法
public static void main(String[] args) {
//真实角色
Runnable myRunnable = new MyRunnable();
//代理角色
Thread thread = new Thread(myRunnable,"A");
//实现的方法
thread.start();
//thread.run();
}
示例代码
public class TestRunnable {
public static void main(String[] args) {
//真实角色
Runnable myRunnable = new MyRunnable();
//代理角色
Thread thread = new Thread(myRunnable,"A");
//实现的方法
thread.start();
//thread.run();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("我是子线程");
}
}
既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
前面我们知道静态代理的实现是要我们手动创建代理对象WeddingCompany实现Marry接口,那么动态代理所体现的不同就是我们可以让程序在运行的时候自动在内存中创建一个实现Marry接口的代理类,而不用手动去定义WeddingCompany这个代理类,这就是它被称为动态代理的原因。
可能概念比较抽象,那么我们通过代码来进行实现
在以上静态代理的场景下来实现动态代理
/**
* @Author zz
* @Date 2021/3/12 18:45
*/
public class ProxyDongTai {
public static void main(String[] args) {
//真实角色
MarryDT realRole = new youDT();
//代理角色 注意这里不是实现接口InvocationHandler
WeddingCompanyDT proxy=new WeddingCompanyDT(realRole);
//生成代理类
//MarryDT proxyRole = (MarryDT) Proxy.newProxyInstance(youDT.class.getClassLoader(), realRole.getClass().getInterfaces(), proxy);
//调用方法 生成代理类
MarryDT proxyRole = (MarryDT)proxy.getProxy();
proxyRole.marride();
}
}
interface MarryDT {
//定义一个结婚接口
void marride();
}
//真实角色 结婚
class youDT implements MarryDT {
@Override
public void marride() {
System.out.println("xxx结婚了");
}
}
//动态代理类 要实现InvocationHandler接口
class WeddingCompanyDT implements InvocationHandler {
//引入接口 真实对象
public MarryDT target;
public WeddingCompanyDT(MarryDT target) {
this.target = target;
}
//生成得到的代理类
// loader 自然是类加载器
// interfaces 代码要用来代理的接口
// h是一个 InvocationHandler 对象
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(target,args);
after();
return null;
}
private void after() {
System.out.println("结婚后");
}
private void before() {
System.out.println("结婚前");
}
}
结果:
结婚前
xxx结婚了
结婚后
动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。
Proxy
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
下面讲解它的 3 个参数意义。
InvocationHandler
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
//动态代理类 要实现InvocationHandler接口
class WeddingCompanyDT implements InvocationHandler {
//引入接口 真实对象
public MarryDT target;
public WeddingCompanyDT(MarryDT target) {
this.target = target;
}
//生成得到的代理类
public Object getProxy(){
//ClassLoader loader,
//Class>[] interfaces
//InvocationHandler h
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(target,args);
after();
return null;
}
private void after() {
System.out.println("结婚后");
}
private void before() {
System.out.println("结婚前");
}
}
以上实例体现的是一个人结婚并且由婚礼公司提供婚事的安排,那如果有多个人结婚那 那要怎么实现那
这里我们只需要写多个接口,就可以模拟多个人结婚的实例,其中都是由婚庆公司(动态代理类)来处理婚事
也可以在一个结婚接口中,定义多个方法来实现
/**
* @Author zz
* @Date 2021/3/12 18:45
*/
public class ProxyDongTai {
public static void main(String[] args) {
//你的 真实角色
MarryDT realRoleYou = new youDT();
//他的真实角色
MarryTa realRoleTa=new TaDT();
//代理角色 注意这里不是实现接口InvocationHandler
WeddingCompanyDT proxyYou=new WeddingCompanyDT(realRoleYou);
WeddingCompanyDT proxyTa=new WeddingCompanyDT(realRoleTa);
//生成代理类
// MarryDT proxyRoleYou =(MarryDT)Proxy.newProxyInstance(youDT.class.getClassLoader(), realRoleYou.getClass().getInterfaces(), proxyYou);
// MarryTa proxyRoleTa =(MarryTa)Proxy.newProxyInstance(TaDT.class.getClassLoader(), realRoleTa.getClass().getInterfaces(), proxyTa);
//调用方法 生成代理类
MarryDT proxyRoleYou = (MarryDT)proxyYou.getProxy();
MarryTa proxyRoleTa = (MarryTa)proxyTa.getProxy();
System.out.println("实现自动代理多个接口");
System.out.println();
proxyRoleYou.marride();
System.out.println("-----------");
proxyRoleTa.marriedTa();
}
}
interface MarryDT {
//定义一个结婚接口
void marride();
}
interface MarryTa{
//定义一个他结婚的接口
void marriedTa();
}
class TaDT implements MarryTa{
@Override
public void marriedTa() {
System.out.println("他也结婚了");
}
}
//真实角色 结婚
class youDT implements MarryDT {
@Override
public void marride() {
System.out.println("你结婚了");
}
}
//动态代理类 要实现InvocationHandler接口
class WeddingCompanyDT implements InvocationHandler {
//引入接口 真实对象
// public MarryDT target;
// public MarryTa ta;
// public WeddingCompanyDT(MarryDT target) {
// this.target = target;
// }
//
// public WeddingCompanyDT(MarryTa ta) {
// this.ta = ta;
// }
/**
* 这里定义真实角色,我们一般使用Object类,若后面可能要引入多个接口,
* 这样在getProxy()方法以及invoke()方法中,不会出现多个不同的接口不能同时进行代理的情况
*/
public Object realTarget;
public WeddingCompanyDT(Object realTarget){
this.realTarget=realTarget;
}
//生成得到的代理类
public Object getProxy(){
//ClassLoader loader,
//Class>[] interfaces
//InvocationHandler h
return Proxy.newProxyInstance(this.getClass().getClassLoader(),realTarget.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(realTarget,args);
after();
return null;
}
private void after() {
System.out.println("结婚后");
}
private void before() {
System.out.println("结婚前");
}
}
结果:
实现自动代理多个接口
结婚前
你结婚了
结婚后
-----------
结婚前
他也结婚了
结婚后
这里要注意下,若程序中出现多个接口代理的情况下,我们要把在代理类中引入真实角色的类型改为Object类型
/**
* 这里定义真实角色,我们一般使用Object类,若后面可能要引入多个接口,
* 这样在getProxy()方法以及invoke()方法中,不会出现多个不同的接口不能同时进行代理的情况
*/
public Object realTarget;
public WeddingCompanyDT(Object realTarget){
this.realTarget=realTarget;
}
其中也可以在主方法中,生成代理类newProxyInstance来实现这样如果多个接口都实现的话,代码就比较冗余
public static void main(String[] args) {
//你的 真实角色
MarryDT realRoleYou = new youDT();
//他的真实角色
MarryTa realRoleTa=new TaDT();
//代理角色 注意这里不是实现接口InvocationHandler
WeddingCompanyDT proxyYou=new WeddingCompanyDT(realRoleYou);
WeddingCompanyDT proxyTa=new WeddingCompanyDT(realRoleTa);
//生成代理类
MarryDT proxyRoleYou =(MarryDT)Proxy.newProxyInstance(youDT.class.getClassLoader(), realRoleYou.getClass().getInterfaces(), proxyYou);
MarryTa proxyRoleTa =(MarryTa)Proxy.newProxyInstance(TaDT.class.getClassLoader(), realRoleTa.getClass().getInterfaces(), proxyTa);
//调用方法 生成代理类
// MarryDT proxyRoleYou = (MarryDT)proxyYou.getProxy();
//
// MarryTa proxyRoleTa = (MarryTa)proxyTa.getProxy();
System.out.println("实现自动代理多个接口");
System.out.println();
proxyRoleYou.marride();
System.out.println("-----------");
proxyRoleTa.marriedTa();
}
我们想下为什么 Proxy 能够动态产生不同接口类型的代理,我的猜测是肯定通过传入进去的接口然后通过反射动态生成了一个接口实例。
比如 MarryDT是一个接口,那么 Proxy.newProxyInstance() 内部肯定会有
new MarryDT();
这样相同作用的代码,不过它是通过反射机制创建的。那么事实是不是这样子呢?直接查看它们的源码好了。需要说明的是,我当前查看的源码是 1.8 版本。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//这里newProxyInstance 的确创建了一个实例,我们点进去
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{
h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
newProxyInstance 的确创建了一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 由 getProxyClass0() 方法获取。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
//从注释我们可以得到说,如果由实现给定接口的给定加载器定义的代理类存在,则只会返回缓存的副本;否则,它将通过ProxyClassFactory创建代理类
//我们点进去ProxyClassFactory这个类
return proxyClassCache.get(loader, interfaces);
}
直接通过缓存获取,如果获取不到,注释说会通过 ProxyClassFactory 生成。
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
这个类的注释说,通过指定的 ClassLoader 和 接口数组 用工厂方法生成 proxy class。 然后这个 proxy class 的名字是:
// Proxy class 的前缀是 “$Proxy”,
private static final String proxyClassNamePrefix = "$Proxy";
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
所以,动态生成的代理类名称是包名+$Proxy+id序号。
生成的过程,核心代码如下:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
下面用一张图让大家记住动态代理涉及到的角色。
红框中 $Proxy0
就是通过 Proxy 动态生成的。
$Proxy0
实现了要代理的接口。
$Proxy0
通过调用 InvocationHandler
来执行任务。
可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?
主要作用,还是在不修改被代理对象的源码上,进行功能的增强。
这在 AOP 面向切面编程领域经常见。
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
上面的引用是百度百科对于 AOP 的解释,至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。
同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?
这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。
至此,静态代理和动态代理者讲完了。
大佬的解释太全面了 膜拜了!!!有详细了解的可以看大佬博客,链接我放下面了
代理详解
lambda表达式其实质属于函数式编程的概念
简化多线程,使用Lambda表达式
new Thread(()->{
System.out.println("我使用了Lambda表达式");
}).start();
为什么要使用Lambda表达式
理解Lambda表达式
理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在。
函数式接口的定义
任何接口,如果只包含唯一个抽象方法(接口中只存在一个方法),那么它就是一个函数式接口,前提是函数式接口的方法,才能使用lambda表达式
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("我是子线程");
}
}
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
lambda表示只能有一行代码的情况下才能简化成一行,若有多行必须用大括号括起来
若方法中含有参数,可以去掉参数类型,若含有多个参数,也可以去掉参数类型,要去掉都去掉,而且必须加上括号。
代码推到Lambda表达式 代码示例如下
public class TestLambda {
//2、静态内部类
static class ILike2 implements Like{
@Override
public void lambda() {
System.out.println("我是静态内部类2");
}
}
public static void main(String[] args) {
Like iLike = new ILike();
iLike.lambda();
new ILike2().lambda();
//3、局部内部类
class ILike3 implements Like{
@Override
public void lambda() {
System.out.println("我是局部内部类3");
}
}
new ILike3().lambda();
//4、匿名内部类 没有类的名称 要借助接口或者父类实现
iLike=new Like() {
@Override
public void lambda() {
System.out.println("我是匿名内部类4");
}
};
iLike.lambda();
//5、lambda表达式实现
iLike=()->{
System.out.println("我是lambdab 5");
};
iLike.lambda();
}
}
//接口
interface Like{
void lambda();
}
//1、外部类实现
class ILike implements Like{
@Override
public void lambda() {
System.out.println("我是外部实现类1");
}
}
线程共包括以下 5 种状态:(严格说应该有6种)
1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
详细解释线程的6种状态 线程六种状态详解
线程状态图:
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒内让正在执行的线程进入休眠状态 |
void join() | 等待该线程终止 |
static void yield() | 暂停正在执行的线程对象,并执行其他的线程 |
void interrupt() | 中断线程,别使用这个方法 |
boolean isAlive() | 测试线程是否处于活动状态 |
public class TestStop implements Runnable{
boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("子线程"+i++);
}
}
//标志位停止线程
private void stop() {
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程"+i);
if (i==900){
testStop.stop();
System.out.println("线程该停止了。。。。");
}
}
}
}
主线程和子线程共同抢占CPU,两者交替执行,当主线程的i==900时,调用了子线程的stop方法,那么子线程停止就不在执行,那么此时程序中就存在一个主线程在执行,直到停止。
//模拟网络延时 放大问题的发生性
public class TestSleep implements Runnable {
//模拟卖票
public int tickets=50;
@Override
public void run() {
while (tickets>0){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了第"+tickets--+"张票");
}
}
public static void main(String[] args) {
TestSleep testSleep = new TestSleep();
new Thread(testSleep,"小明").start();
new Thread(testSleep,"小王").start();
new Thread(testSleep,"老张").start();
}
}
以上例子通过三个线程卖票,模拟网络延迟,放大了问题的发生性,我们知道当我们定义多个线程共同抢夺同一个资源时,也没有使用锁肯定是不安全的(两个人拿到同一张票),我们不使用睡眠时,很难发现;当我们使用了sleep后程序就实现了一个睡眠,可以放大问题的发生性,我们很容易看到问题发生的现状(两个人抢到了同一张票)。
//获取当前系统的时间
public class TestSleep2 {
public static void main(String[] args) {
//获取当前的时间
Date date = new Date(System.currentTimeMillis());
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
//更新当前时间
date=new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
//进行礼让,让另一个线程跑,可能不礼让成功
Thread.yield();
System.out.println(Thread.currentThread().getName()+"停止");
}
public static void main(String[] args) {
TestYield testYield = new TestYield();
new Thread(testYield,"a").start();
new Thread(testYield,"b").start();
}
}
礼让成功:
b执行
a执行
b停止
a停止
不礼让:
b执行
b停止
a执行
a停止
*/
//线程插队join
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程"+i);
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
//当我们主线程i==200时,让子线程进行一个插队
if (i==50){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程"+i);
}
}
}
当主线程的i为50时,此时子线程要全部跑完
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
一个线程可以在一个给定的时间点上只有一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
public class TestThreadState {
public static void main(String[] args) {
Thread thread=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("子线程");
});
//创建完对象后,观察线程的状态
Thread.State state = thread.getState();
System.out.println(state);
//start后线程进入就绪状态
thread.start();
System.out.println(thread.getState());
while (state!=Thread.State.TERMINATED){
//若线程不死 就一直输出
//等待状态说明执行中,出现了阻塞睡眠
try {
Thread.sleep(100);
state=thread.getState();
System.out.println(state);
} catch (InterruptedException e) {
}
}
//线程停止之后不能再次运行,会出现异常
//Exception in thread "main" java.lang.IllegalThreadStateException
//at java.lang.Thread.start(Thread.java:708)
//at com.zz.state.TestThreadState.main(TestThreadState.java:40)
//thread.start();
}
}
输出结果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
子线程
TERMINATED
优先级的设定建议在start之前进行设置
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度,也有可能低优先级的线程先调度
public class TestPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"----->"+Thread.currentThread().getPriority());
}
public static void main(String[] args) {
System.out.println("主线程优先级"+Thread.currentThread().getPriority());
TestPriority testPriority = new TestPriority();
Thread thread1=new Thread(testPriority,"线程1");
thread1.setPriority(1);
thread1.start();
Thread thread2=new Thread(testPriority,"线程2");
thread2.setPriority(Thread.MAX_PRIORITY);//10
thread2.start();
Thread thread3=new Thread(testPriority,"线程3");
thread3.setPriority(3);
thread3.start();
}
}
//守护线程
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
//将上帝设置为守护线程
//默认为false,正常的线程都是用户线程
thread.setDaemon(true);
//开启守护线程
thread.start();
new Thread(you).start();
}
}
//上帝模拟守护线程
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝一直保佑着你");
}
}
}
//用户线程
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("hello world 第"+i+"天");
}
System.out.println("GoodBy world");
}
}
当用户线程死后,我们看到守护线程还在执行一段时间,原因是当我们销毁用户线程后也需要一段时间,因为cpu切换速度比较快,所以我们的守护线程也会运行一段时间。
并发是两个队列交替使用同一台咖啡机,并行是两个队列同时使用两台咖啡机,如果串行,一个队列使用一台咖啡机,那么哪怕前面那个人便秘了去厕所呆半天,后面的人也只能死等着他回来才能去接咖啡,这效率无疑是最低的。
并发是不是一个线程,并行是多个线程?
并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。
并发(多线程操作同一个资源)
并行(多个人一起行走)
并发编程的本质:充分利用CPU性能
现实生活中,我们会遇到 ” 同一个资源 , 多个人都想使用 ” 的问题 , 比如,食堂排队 打饭 , 每个人都想吃饭 , 最天然的解决办法就是 , 排队 . 一个个来.
处理多线程问题时 , 多个线程访问同一个对象 , 并且某些线程还想修改这个对象 . 这时候我们就需要线程同步 . 线程同步其实就是一种等待机制 , 多个需要同时访问此对象的线程进入这个对象的等待池 形成队列, 等待前面线程使用完毕 , 下一个线程再使用
由于同一进程的多个线程共享同一块存储空间 , 在带来方便的同时,也带来了访问 冲突问题 , 为了保证数据在方法中被访问时的正确性 , 在访问时加入 锁机制
synchronized , 当一个线程获得对象的排它锁 , 独占资源 , 其他线程必须等待 , 使用后释放锁即可 . 存在以下问题 :
缺陷 : 若将一个大的方法申明为synchronized 将会影响效率
这里多个线程资源抢占票,若不适用锁机制不同对象会抢到同一张票,还会造成票的负数,显然这是线程不安全的,那么我们使用synchronized方法实现线程安全
//多个对象进行抢票 通过给票上锁来实现线程安全
public class Ticket {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
new Thread(myTicket).start();
new Thread(myTicket).start();
new Thread(myTicket).start();
}
}
class MyTicket implements Runnable {
public int tickets = 20;
boolean flag=true;
@Override
public void run() {
while (true) {
buy();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法,锁的是 this MyTicket这个资源
public synchronized void buy() {
if (tickets<0){
flag=false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到了" + tickets--);
}
}
两个线程进行取钱,取同一个账户,若不加锁两个线程会同时操控一个资源进行,会造成当a取钱时,b也同时进行取钱,就会造成ab同时消耗这个资源,会造成数据的脏读。
补充
1、脏读:事务A读到了事务B未提交的数据。
2、不可重复读:事务A第一次查询得到一行记录row1,事务B提交修改后,事务A第二次查询得到row1,但列内容发生了变化。
3、幻读:事务A第一次查询得到一行记录row1,事务B提交修改后,事务A第二次查询得到两行记录row1和row2。
ACID特效
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
/**
* 模拟夫妻取钱,开启两个线程,同时取钱,一个账户
*/
public class Bannk {
public static void main(String[] args) {
Accout accout = new Accout(100,"卖废品");
MyThread myThread1 = new MyThread(accout,50);
myThread1.start();
MyThread myThread2 = new MyThread(accout,50);
myThread2.start();
}
}
class Accout{
//银行卡余额
public int money;
//存钱备注
public String name;
public Accout(int money, String name) {
this.money = money;
this.name = name;
}
}
class MyThread extends Thread{
public Accout accout;
//取的钱
public int quMoney;
//手里的钱
public int nowMoney;
public MyThread(Accout accout, int quMoney) {
this.accout = accout;
this.quMoney = quMoney;
}
boolean flag=true;
@Override
public void run() {
//锁的对象时变化的对象,这里若使用同步方法,指代this代表银行,明显不行
//使用代码块,锁住变化的对象账户accout
synchronized (accout){
//判断取的钱 是否多余账户余额
if (quMoney>accout.money){
flag=false;
System.out.println(Thread.currentThread().getName()+"取了"+quMoney+"银行卡余额不足,仅剩"+accout.money);
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
//正常取钱
accout.money=accout.money-quMoney;
nowMoney+=quMoney;
System.out.println(Thread.currentThread().getName()+"取了"+quMoney+",手里:"+nowMoney+",银行卡余额:"+accout.money);
}
}
}
不安全原因是当两个线程同时执行的时候,将两个元素加到了同一个数组,就会造成集合的长度小于10000,
Vector 是县城线程安全的
public class unSafeList {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
//Vector
//不安全原因是当两个线程同时执行的时候,将两个元素加到了同一个数组
for (int i = 0; i < 10000; i++) {
new Thread(()->{
//这里变化的资源 是list所以锁住该对象
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("集合中大小:"+list.size());
}
}
CopyOnWriteArrayList 是JUC下的一个方法,在java.util.concurrent.CopyOnWriteArrayList这个包下面
//线程安全
public class TestConyOnWriteArraylist {
public static void main(String[] args) {
CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
源码分析 CopyOnWriteArrayList为什么是线程安全的
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/** The lock protecting all mutators */
//这里使用了可重复锁,是JUC里面的锁,后面会讲到
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
//使用了volatile关键字 保证了原子性
private transient volatile Object[] array;
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
什么是序列化接口?
一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。
什么是序列化?
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。
序列化和反序列化转换过程
把对象转换为字节序列的过程称为对象的序列化
把字节序列恢复为对象的过程称为对象的反序列化
什么情况下需要序列化?
- 当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化
- 为什么还要继承Serializable。那是存储对象在存储介质中,以便在下次使用的时候,可以很快捷的重建一个副本
在开发过程中并没有使用序列化 为什么那?来看一下序列化源码
一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。
比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。
Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。
什么是JVM?
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
为什么要使用SerializableUID变量
从说明中我们可以看到,如果我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID
但是强烈建议用户自定义一个serialVersionUID,因为默认的serialVersinUID对于class的细节非常敏感,反序列化时可能会导致InvalidClassException这个异常。
在前面我们已经新建了一个实体类User实现Serializable接口,并且定义了serialVersionUID变量。
读写文件的代码示例
import java.io.*;
/**
* @Author zz
* @Date 2021/3/16 16:03
*/
public class TestSerializable {
public static void main(String[] args) {
writeUser();
readUser();
}
public static void writeUser(){
User user = new User();
user.setId(1);
user.setName("zz");
user.setAge(21);
String file="G:\\idea\\bilibili\\kuang\\Thread\\src\\com\\zz\\ThreadSyn\\a.txt";
try {
ObjectOutputStream osw = new ObjectOutputStream(new FileOutputStream(new File(file)));
osw.flush();
osw.writeObject(user);
System.out.println("序列化成功");
osw.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static void readUser(){
String file="G:\\idea\\bilibili\\kuang\\Thread\\src\\com\\zz\\ThreadSyn\\a.txt";
try {
ObjectInputStream isr = new ObjectInputStream(new FileInputStream(new File(file)));
User user = (User) isr.readObject();
System.out.println(user.toString());
System.out.println("反序列化成功");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
class User implements Serializable{
private static final Long serialVersionUID=1L;
private int id;
private String name;
private int age;
public static Long getSerialVersionUID() {
return serialVersionUID;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
序列化成功
User{
id=1, name='zz', age=21}
反序列化成功
这个serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误:
实体类不实现序列化接口,将会报以下异常:
java.io.NotSerializableException: com.zz.ThreadSyn.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.zz.ThreadSyn.TestSerializable.writeUser(TestSerializable.java:26)
at com.zz.ThreadSyn.TestSerializable.main(TestSerializable.java:12)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.zz.ThreadSyn.User
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1572)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
at com.zz.ThreadSyn.TestSerializable.readUser(TestSerializable.java:38)
at com.zz.ThreadSyn.TestSerializable.main(TestSerializable.java:13)
Caused by: java.io.NotSerializableException: com.zz.ThreadSyn.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.zz.ThreadSyn.TestSerializable.writeUser(TestSerializable.java:26)
at com.zz.ThreadSyn.TestSerializable.main(TestSerializable.java:12)
代码示例 小明 小王两个线程 枪 布娃娃 两个资源 小明首先拥有枪资源,小王首先拥有布娃娃资源;两个线程各自争夺对方手中的资源,产生死锁
public class DeadLock {
public static void main(String[] args) {
DeadThread thread1= new DeadThread(0,"小明");
thread1.start();
DeadThread thread2= new DeadThread(1,"小王");
thread2.start();
}
}
//两个资源
//枪
class Gun{
String name="枪";
}
//布娃娃
class Doll {
String name = "布娃娃";
}
class DeadThread extends Thread{
static Gun gun=new Gun();
static Doll doll=new Doll();
int choice;
String name;
public DeadThread(int choice,String name){
this.choice=choice;
this.name=name;
}
@Override
public void run() {
makeup();
}
public void makeup(){
if (choice==0){
synchronized (gun){
System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
//当走在这里的时候,小王也拿到了doll的锁并没有释放,
synchronized (doll){
System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
}
}
}else {
synchronized (doll){
System.out.println(Thread.currentThread().getName()+"获得了----》"+doll.name+"的锁");
//这里小明一开始就拿到了 gun的锁并没有释放,故就产生了死锁
synchronized (gun){
System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
}
}
}
}
}
结果 程序一直停在此状态,也不停止
解决死锁
当小王想要抢小明手中枪的时候,必须等待对方释放锁后,才能拿得到,不在产生死锁
public void makeup(){
if (choice==0){
synchronized (gun){
System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
//当走在这里的时候,小王也拿到了doll的锁并没有释放,
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (doll){
System.out.println(Thread.currentThread().getName()+"获得了----》"+doll.name+"的锁");
}
}else {
synchronized (doll){
System.out.println(Thread.currentThread().getName()+"获得了----》"+doll.name+"的锁");
//这里小明一开始就拿到了 gun的锁并没有释放,故就产生了死锁
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (gun){
System.out.println(Thread.currentThread().getName()+"获得了----》"+gun.name+"的锁");
}
}
}
产生死锁的四个必要条件:
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
public class TestLock {
public static void main(String[] args) {
LockThread lockThread = new LockThread();
new Thread(lockThread).start();
new Thread(lockThread).start();
new Thread(lockThread).start();
}
}
class LockThread implements Runnable {
private int tickets = 10;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "---->" + tickets--);
Thread.sleep(1000);
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
生产者消费者模式
这是一个线程同步问题 , 生产者和消费者共享同一个资源 , 并且生产者和消费者之 间相互依赖 , 互为条件 .
Java提供了几个方法解决线程之间的通信问题
注意:均是Object类的方法 , 都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
并发协作模型 “ 生产者 / 消费者模式 ” —>管程法
生产者 : 负责生产数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;
消费者 : 负责处理数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;
缓冲区 : 消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区
生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出数据
public class TestShoppig {
public static void main(String[] args) {
SyContainer syContainer = new SyContainer();
new Product(syContainer).start();
new Consumer(syContainer).start();
}
}
//定义两个线程 生产者 消费者
class Product extends Thread {
SyContainer syContainer;
public Product(SyContainer syContainer){
this.syContainer=syContainer;
}
@Override
public void run() {
super.run();
for (int i = 1; i < 100; i++) {
syContainer.push(new Chicken(i));
System.out.println("生产了--->" + i);
}
}
}
class Consumer extends Thread {
SyContainer syContainer;
public Consumer(SyContainer syContainer){
this.syContainer=syContainer;
}
@Override
public void run() {
super.run();
for (int i = 1; i < 100; i++) {
Chicken pop = syContainer.pop();
System.out.println("消费了"+pop.count);
}
}
}
//鸡
class Chicken {
//数量
int count;
public Chicken(int id) {
this.count = id;
}
}
//盘子
class SyContainer {
int count = 0;
//一个盘子放10只鸡
Chicken[] chickens = new Chicken[10];
//生产者 放入产品
public synchronized void push(Chicken chicken) {
if (count == chickens.length) {
//盘子已满 通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//未满 生产丢入盘子
chickens[count] = chicken;
count++;
//通知消费者消费,唤醒消费者
this.notifyAll();
}
}
//消费者消费产品
public synchronized Chicken pop() {
if (count == 0) {
//盘子为空,通知生产者生产,消费者等待\\
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//可以消费
count--;
Chicken chicken = chickens[count];
this.notifyAll();
return chicken;
}
}
并发协作模型 “ 生产者 / 消费者模式 ”
package com.zz.ThreadComm;
/**
* @Author zz
* @Date 2021/3/17 21:48
*/
public class TestTv {
public static void main(String[] args) {
Tv tv = new Tv();
new Thread(new Player(tv)).start();
new Thread(new Watcher(tv)).start();
}
}
class Tv {
//演员表演 观众观看
private String voice;
boolean flag=true;
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了"+voice);
//唤醒观众去看
this.notifyAll();
this.flag=!this.flag;
this.voice=voice;
}
public synchronized void watch(){
if (flag){
try {
//等待演出
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了--->"+voice);
this.notifyAll();
this.flag=!this.flag;
}
}
//演出者
class Player implements Runnable{
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i%2==0){
tv.play("光头强");
}
tv.play("喜洋洋");
}
}
}
//观众
class Watcher implements Runnable{
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
tv.watch();
}
}
}
public class TestPool {
public static void main(String[] args) {
//创建线程池 后面参数为线程池大小
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
executorService.execute(new ThreadPool());
//关闭连接
executorService.shutdown();
}
}
class ThreadPool implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
结果: