Java 复习笔记8 - 多线程
概念
关于什么是线程,进程等概念,请看下面文章:
上述文章代码使用的是python,但是所有的概念和原理都是相同的;
需要特别强调的是java中的线程在多核处理器上是可以真正并行执行的,没有cpython中的全局锁这么一说
线程的状态切换以及生命周期:
注意:stop方法以及被弃用建议不要使用了,线程正确的结束,应该是线程任务全部完成,或者是被作为守护线程,被守护线程运行结束,再或者调用中断方法interrupt
java中启动线程的方式:
package com.yh.lesson.collection.thread; import sun.nio.ch.threadpool; import java.util.concurrent.threadpoolexecutor; import java.util.concurrent.timeunit; public class test { public static void main(string[] args) { //1.匿名类 thread td1 = new thread(new runnable() { @override public void run() { system.out.println(thread.currentthread().getname()); } }); td1.start(); //2.lambda表达式 djk1.8 thread td2 = new thread(()->{ system.out.println(thread.currentthread().getname()); }); td2.start(); //3.runnable实现类 thread td3 = new thread(new runner()); td3.start(); } } class runner implements runnable{ @override public void run() { system.out.println(thread.currentthread().getname()); } } //4.继承extends 覆盖run方法 class ttt extends thread{ @override public void run() { system.out.println(thread.currentthread().getname()); } }
其他方法属性参照api,你可以把线程看做一个子程序,无法是启动,中断,获取状态,设置守护等等操作
常用方法 sleep,join
sleep是线程睡眠一段时间然后继续运行
注意:sleep是thread类的静态方法,需要捕获异常
//下面的代码输出大写a-z 每个500毫秒循环一次 new thread(()->{ for (int i = 65; i < 91; i++) { char c = (char) i; system.out.printf("%s",c); try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } } }).start();
join使当前线程等到调用join的线程执行结束才能执行
注意:join是对象方法,也需要捕获异常
system.out.println("start!"); thread th1 = new thread(()->{ for (int i = 0; i < 10; i++) { system.out.print(i); } }); try { th1.join(); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("over!"); /* 输出: start! 0123456789 over! */
使得主线程等待th1执行完毕后再输出over!
另外join函数重载了一个带有超时的方法,可以使主线程等待一段时间后开始运行
优先级:
线程锁:
为什么使用synchronized
当多个线程并发的操作同一个资源时就看你引发数据安全问题,解决方案就是使并发变成串行(异步变同步)
使用synchronized关键字,可以使得代码串行,其可应用于方法声明或是代码块
案例:
在方法声明中使用,表示该方法不允许并发执行:
public class lockdemo { public static void main(string[] args) { new thread(()->{ func("pic"); }).start(); new thread(()->{ func("text"); }).start(); } static synchronized void func(string s){ for (int i=0;i < 10;i++){ try { thread.sleep(2); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(s+" "+i); } } }
在代码块中使用,表示该代码块不允许并发执行:
public class lockdemo2 { public static void main(string[] args) { new thread(()->{ func("pic"); }).start(); new thread(()->{ func("text"); }).start(); } static void func(string s){ // synchronized (new string("")) { //注意用于同步的对象必须保持一致,否则无法同步 synchronized ("") {//字符时常量所以对象地址没有变化 可以实现同步 for (int i = 0; i < 10; i++) { try { thread.sleep(2); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(s + " " + i); } } } }
注意:synchronized ()括号中可以填写任意对象,但是要保证多个线程使用的是同一个对象,否则无法实现同步
避免随意加锁
很多人在碰到安全问题时会随意的加锁,有加在方法上的,有随便加载代码上的.,很随意...
锁的粒度:粒度表示锁住代码范围,
- 被锁住的代码越少粒度越小,性能越好
- 被锁住的代码越少粒度越大,性能越差
锁对象的选择:
锁对象可以是任意对象,有的人用this有的用person.class,各种各样的....
哪种都不算错,但是要注意,不要让不想关的线程在对象*问锁,
例如:有一个余额变量balance
,线程a和线程b需要并发的操作这个变量,但是线程c并不会对balance
做任何修改,此时就不应该让c也使用a/b线程所使用的锁;
简单的说:尽量减少同一个锁对象管理的线程数量
死锁:
当多个线程对同一个锁对象执行wait时将进入死锁(为什么要这么做?)
synchronized代码块的设计极大的避免了死锁出现的情况,就这点来看java更方便,python需要明确的加锁和解锁,代码量挺多....
线程间通讯:
首先明确多个线程共享进程中的资源,也就是是说本来就可以相互通讯,但是我们直接通过访问某个变量来通讯的话,是效率非常低的, 因为你不知道什么时候对方线程准备好了,所有你只能无限的循环去检查变量的值;
来看这样一个例子:
小明派小刚去安装炸弹,安装完成后小明只需要按下手中的遥控器就可以引爆,问题是小明不知道什么时候安装完成,并且小刚已经撤离,一旦过早的引爆小刚会发生危险,过晚的引爆又无法及时完成任务;这时候就需要让他们之间可以传递讯号,来获知彼此的状态;
这个例子说明了为什么需要在线程间通讯,并且我们可以发现小明不需要不断的询问小刚是否完成,而是等待小刚给自己发信号,这就是线程通讯要实现的效果
强调:上面的唤醒,始终针对的是在当前同步对象(锁对象)上等待的线程
案例:
package com.yh.lesson.collection.thread; public class threadcommunication { static boolean finished = false;//表示炸弹是否安装完成的变量 static string lock = new string();//锁 public static void main(string[] args) throws interruptedexception { //小明线程 thread t1 = new thread(() -> { system.out.println("等待...."); synchronized (lock){ try { lock.wait(); system.out.println("收到信号!"); if(finished){ system.out.println("安全引爆,任务完成!"); }else{ system.out.println("安装未完成,小刚牺牲了!"); } } catch (interruptedexception e) { e.printstacktrace(); } } }); //小刚线程 thread t2 = new thread(() -> { try { system.out.println("开始安装!"); thread.sleep(3000); system.out.println("安装完成!"); finished = true; synchronized (lock) { lock.notifyall(); } } catch (interruptedexception e) { e.printstacktrace(); } }); t1.start(); t2.start(); } }
注意:wait()和notify(),notifyall(),必须在同步代码中使用
ps:就这个synchronize和 notify坑了多少年轻人(当年也年轻),notify和wait本质上就是在操作一个作为标记的变量表示事件是否发生了,
synchronize本质也是操作一个标志表示是否加锁了,偏偏把两者强行绑在一起,真让人捉摸不透,这点python整的很好,锁是锁,事件是事件,有兴趣的可以看看我的另一篇文章:在文章的最下面
推荐阅读
-
Java多线程高并发学习笔记(二)——深入理解ReentrantLock与Condition
-
guava笔记8-Primitives 博客分类: java相关guava
-
Java笔记(8)——多态和对象实例化
-
Java8学习笔记(一)--Lambda表达式 博客分类: java开发
-
《Java编程思想》读书笔记 —— 第7章 复用类,第8章 多态
-
Java分布式应用学习笔记05多线程下的并发同步器----后篇 博客分类: 分布式集群 分布式集群线程调度多线程同步器
-
Java分布式应用学习笔记05多线程下的并发同步器----前篇 博客分类: 分布式集群 分布式集群并发包线程调度器多线程
-
《Java8实战》-第八章笔记(重构、测试和调试)
-
小白Java学习笔记d8(==。equals,数据类型的转换)
-
Java整理笔记基础 IO 面向对象 多线程 容器