Java学习笔记-多线程
来源:互联网 发布:saas软件市场规模 编辑:程序博客网 时间:2024/05/19 04:05
该博文是传智播客Java基础视频的学习笔记,仅为个人学习笔记,谢谢
public class ThreadDemo1 {public static void main(String[] args) {Demo d1 = new Demo();Demo d2 = new Demo();Demo d3 = new Demo();d1.run();d2.start();d3.start();System.out.println(Thread.currentThread().getName()+"..."+"over");}}class Demo extends Thread{public void run() {super.run();for(int x = 1;x < 6;x++){System.out.println(currentThread().getName()+"..."+getName()+"..."+x);}}}
main...Thread-0...1
main...Thread-0...2
main...Thread-0...3
main...Thread-0...4
main...Thread-0...5
main...over
Thread-1...Thread-1...1
Thread-2...Thread-2...1
Thread-2...Thread-2...2
Thread-2...Thread-2...3
Thread-2...Thread-2...4
Thread-2...Thread-2...5
Thread-1...Thread-1...2
Thread-1...Thread-1...3
Thread-1...Thread-1...4
Thread-1...Thread-1...5
需要注意的是:
1、d1.run();仅仅是对象调用方法,d2.start();才是开启一个线程并执行run方法
2、Thread.currentThread().getName()是获取当前运行的线程的name,而Thread的getName()只是获取线程对象的name,在执行new Demo();时,Thread的构造函数中已经为Thread对象初始化了name:
private static int threadInitNumber;public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0);}private static synchronized int nextThreadNum() { return threadInitNumber++;}
class Demo extends Thread{Demo(String name){super(name);}public void run() {super.run();}}利用构造函数为线程定义新的名称。
public class ThreadDemo1 {public static void main(String[] args) {Demo d1 = new Demo("aaa");Demo d2 = new Demo("bbb");d1.start();d2.start();System.out.println(4/0);System.out.println(Thread.currentThread().getName()+"..."+"over");}}class Demo extends Thread{Demo(String name){super(name);}public void run() {super.run();for(int x = 1;x < 6;x++){System.out.println(currentThread().getName()+"..."+getName()+"..."+x);}}}aaa...aaa...1Exception in thread "main"
bbb...bbb...1
aaa...aaa...2
bbb...bbb...2
bbb...bbb...3
aaa...aaa...3
bbb...bbb...4
aaa...aaa...4
aaa...aaa...5
bbb...bbb...5
java.lang.ArithmeticException: / by zero
at com.hwgt.thread1.ThreadDemo1.main(ThreadDemo1.java:9)
主线程出现异常,aaa和bbb线程正常执行
线程的状态:
被创建 —start()—>运行状态—sleep(time)—>冻结状态—sleep(time)时间到—>运行状态
被创建 —start()—>运行状态—wait()—>冻结状态—notify()唤醒—>运行状态
运行状态—stop()—>消亡
运行状态—run方法结束—>消亡
还有一种状态,正在等待被执行,叫做临时阻塞状态,和冻结状态不一样。冻结状态调用notify()或sleep时间到了,可能回到运行状态或者临时阻塞状态
创建线程的第一种方式:继承Thread类,覆写run方法
如果一个类中有需要另开线程来做的工作,可以考虑使这个类继承Thread类,将需要另开线程来做的工作放在run方法中。但如果这个类已经有了一个直接父类,就不能再继承Thread类了,所以需要考虑通过其他方式扩展这个类的功能,Java提供的扩展方式就是Runnable接口,即
创建线程的第二种方式:实现Runnable接口,覆写run方法,创建Thread子类对象,将Runnable接口的子类对象作为构造函数的参数传递给Thread对象,调用Thread的start方法
实现Runnable接口方式的好处:
1、将线程的任务从线程的子类中分离出来,进行了单独的封装
2、避免了Java单继承的局限
所以,第二种方式比较常用。
多线程产生安全问题的原因:
1、多个线程在操作共享的数据
2、操作共享数据的线程代码有多条
解决方式:保证同一时刻只能有一个线程执行操作共享数据的代码,同步代码块:synchronized(对象){需要被同步的代码},同步的弊端是会降低效率
使用同步的前提:
必须有多个线程并使用同一个锁
也可以使用同步函数,将synchronized作为函数的修饰符就是同步函数,同步函数的锁是this,同步代码块的锁是任意对象,建议使用同步代码块。
静态同步函数使用的锁是该函数所属的字节码文件对象,可以用getClass()方法获取,也可以用当前类名.class表示。
单例模式涉及的多线程问题:
public class ThreadDemo2 {public static void main(String[] args) {Single s = Single.getInstance();}}class Single{private Single(){}private static Single s = null;public static Single getInstance(){if(s == null){synchronized(Single.class){if(s == null){s = new Single();}}}return s;}}单例模式,懒汉式,多线程操作时会有安全隐患,使用同步代码块和同步函数都可以解决,但稍微有些低效,可以使用双重判断的方式解决效率低的问题。
使用同步的过程中,当锁嵌套时,就可能出现死锁的情况:
public class DeadLockDemo {public static void main(String[] args) {Demo d = new Demo();new Thread(d).start();try{Thread.sleep(100);d.flag = true;}catch(InterruptedException e){e.printStackTrace();}new Thread(d).start();}}class Demo implements Runnable{private int x = 100;public boolean flag;Object obj = new Object();public void run() {if(flag){while(true){synchronized(obj){// --> Thread-0 持有obj锁,但需要this锁show();}}}else{while(true){show();}}}private synchronized void show(){// -->Thread-1 持有this锁,但需要obj锁synchronized(obj){if(x>0){try{Thread.sleep(20);}catch(InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"......"+x--);}}}}要避免出现死锁。
第13天末尾有一个答疑视频。
多个线程在处理同一个资源,但是处理的方式不同。线程间通信,等待唤醒机制,涉及到的方法:
wait(),让线程处于冻结状态,被wait的线程会被存储到线程池中
notify(),唤醒线程池中的一个线程
notifyAll(),唤醒线程池中的所有线程
这三个方法都是从Object类继承而来,因为这几个方法是监视器也就是锁的方法,而锁可以使任意对象。
此处涉及到线程池和监视器的知识,待补充 ... ...
这些方法都是用于操作线程状态的方法,都必须定义在同步中,必须要明确到底操作的是哪个锁上的线程。
ThreadDemo3 有尚未解决的问题 ... ...
多生产者多消费者模型中,需要注意的问题有:
if判断和while判断的区别,if只会进行一次判断,这会导致应该等待的线程又重新开始执行,而while判断能让处于wait状态的线程进入临时阻塞状态后再次判断标记来确定要继续wait还是执行。
notify和notifyAll的区别,notify只能唤醒一个线程,如果只是唤醒了本方,没有意义,而且,while判断和notify的组合会导致所有线程都进入wait状态
而使用notifyAll,就一定能唤醒对方线程。
在同步代码块中,获取锁和释放锁的操作都是隐式的,5.0之后,将同步和锁进行了封装,就出现了Lock
Lock接口:替代了同步代码块和同步函数,将隐式锁操作换成了显示锁操作,同时更为灵活,可以绑定多个监视器对象
Condition接口:替代了同步代码块和同步函数中的锁,将锁对象的wait、notify和notifyAll进行了封装。
public class ProducerConsumerDemo1 {public static void main(String[] args) {Resource1 r = new Resource1();Producer1 pro = new Producer1(r);Consumer1 con = new Consumer1(r);Thread t0 = new Thread(pro);Thread t1 = new Thread(pro);Thread t2 = new Thread(con);Thread t3 = new Thread(con);t0.start();t1.start();t2.start();t3.start();}}class Resource1{private String name;private int count = 1;private boolean flag = false;Lock lock = new ReentrantLock();Condition producer_con = lock.newCondition();Condition consumer_con = lock.newCondition();public void set(String name){lock.lock();try{while(flag){try {producer_con.await();} catch (InterruptedException e) {e.printStackTrace();}}this.name = name+count;count++;System.out.println(Thread.currentThread().getName()+"...生产者........."+name);flag = true;consumer_con.signal();}finally{lock.unlock();}}public void out(){lock.lock();try{while(!flag){try{consumer_con.await(); }catch(InterruptedException e){e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+".........消费者..."+name);flag = false;producer_con.signal();}finally{lock.unlock();}}}class Producer1 implements Runnable{private Resource1 r;Producer1(Resource1 r){this.r = r;}public void run() {while(true){r.set("烤鸭");}}}class Consumer1 implements Runnable{private Resource1 r;Consumer1(Resource1 r){this.r = r;}public void run() {while(true){r.out();}}}
wait和sleep的区别:
1、wait可以指定时间,也可以不指定,sleep必须指定
2、在同步中时,wait,停止执行,释放锁,sleep,停止执行,不释放锁
尚未解决的问题:同步中有多个线程处于wait状态?锁不就是为了保证在同一时间只能有一个线程执行某一个代码片段吗?
停止线程的方式:
1、stop方法:已过时
2、run方法结束。那怎么控制线程的任务结束呢?可以设置标识符来进行判断,但当线程进入了wait状态后,就会出现不读取标识符的情况,这个时候,可以使用interrupt方法清除线程的中断状态,(从冻结状态强制恢复到运行状态中来)但会有interruptedException,需要处理,比如在catch语句中重新设置标识符。
setDaemon();将线程标记为守护线程,当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用
setPriority();有三个常用的值,Thread.MAX_PRIORITY等。
join(); t1.join():t1申请加入执行,等到t1执行结束后,主线程才开始执行,临时加入一个线程运算时可以使用该方法。
toString(); 返回线程的名称、优先级和组。
public class ThreadDemo4 {public static void main(String[] args) {new Thread(new Runnable(){public void run(){System.out.println("runnable run");}}){public void run(){System.out.println("thread run");}}.start();}}执行结果是:thread run
- Java多线程学习笔记
- Java多线程学习笔记
- Java学习笔记---多线程
- java多线程学习笔记
- Java多线程学习笔记
- Java多线程学习笔记
- JAVA多线程学习笔记
- Java 多线程学习笔记
- java多线程学习笔记
- Java多线程学习笔记
- [学习笔记]Java多线程
- java多线程学习笔记
- Java多线程学习笔记
- Java学习笔记-多线程
- Java多线程学习笔记
- Java多线程学习笔记
- Java多线程学习笔记
- Java 多线程学习笔记
- 卷积RBM源码解读
- 关于分布式系统的数据一致性问题(三)
- 比特币论文:一个点对点的电子现金系统
- ]跟波澜死磕Swift3基础篇②-Swift开发环境-第一节
- bootstrap-datetimepicker详情的API
- Java学习笔记-多线程
- Android Studio中将一个android工程打成.aar包或者jar包
- Android自定义相机,带边框截图
- MYSQL外键的使用以及优缺点
- java虚拟机学习之路-走进java
- hive获取今天、昨天、明天的日期
- linux ehci ehci_urb_enqueue之qh_urb_transaction()分析(一)
- 第十一周 oj训练 日期妙算星座
- bzoj 2431: [HAOI2009]逆序对数列 动态规划