0.Create a Thread
方法①
方法②(常用)
两种创建方式区别
多线程内存示意图
start和run方法的区别
1.The Status of Thread
2.Security Problems in Multithreading(多线程)
3.Four kinds of usage of Synchroized(四种修饰)
需要同步的地方
代码块
方法
静态方法
一个类
4.Multithreading Security Problems in (多线程安全问题)
Lazy/Hungry Man singleton pattern(单例饿汉/懒汉)
饿汉(没有新对象,用getInstance来获得)
懒汉(懒得创建新对象,用之前有的)
解决方法:
5.Consumer and Producer Problem(生产者-消费者问题)
背景前瞻
产生问题
等待唤醒机制(wait() / notify() / notifyAll())
多消费者、多生产者问题
6.Details in Multithreading
sleep()和wait()的异同点
线程如何停止
守护(后台)线程
优先级&线程组
join() & yield()
微妙的地方
继承Thread类,重写run()方法,调用start方法开启线程(也就是调用重写的run方法)
实现Runnable接口,重写run方法,将实现类作为参数传递给Thread对象,最后调用start方法开启线程(也就是调用run方法)
Demo d = new Demo(); //Demo类实现了Runnable接口,但他还不是线程对象
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
Thread源码
class Thread{
private Runnable target;
Thread(Runnable target){
this.target = target;
}
public void run(){
if(target != null){
target.run();
}
}
}
**所有的线程共享实现类的成员变量
比如Demo中有一个成员变量ticket=100,t1/t2的run方法都可以对其进行操作,而且数值会保存
方法①:线程任务和线程对象耦合在一起,一旦创建了Thread对象,就代表创建了线程任务以及线程对象,也就是有线程任务就有对象了
方法②:线程分为两部分:线程对象(Thread对象)+线程任务(实现Runnable接口的类),将线程任务和线程对象解耦,此方式更加面向对象,也就更常用
public static void main(String[] args){
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.run(); //由当前线程负责
t2.start(); //由新创建的Thread-1负责
}
调用run方法不开启线程,仅是对象调用方法,由当前线程负责方法的执行;
调用start方法开启线程,并让jvm调用run方法在新开启的线程中负责方法执行。
*参考地址:https://www.cnblogs.com/happy-coder/p/6587092.html
1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
*根本原因也就是对共享数据操作
而要解决此多线程安全问题就用到了同步锁
public void run(){
Object obj = new Object();
synchroized(obj){
//需要同步的代码块,对共享数据的操作
}
}
在线程执行run方法之前,会要求得到对象obj,而obj只有一个。
只要其中一个线程A得到了此对象obj,即使CPU切换线程B来执行,线程B得不到对象obj也就不能执行下去。
而线程A在执行完代码块之后,就会将对象obj返回,其他线程就可以得到对象obj了。
使用同步锁弊端:降低了程序的性能(切换进程时得不到对象obj-->不做事)
可能出现的问题:同步中用的不是同一把锁(对象obj不是同一个),导致同步失效
同步里面一般都是循环,如果不是循环,瞬间执行完了就没必要多线程了
一般都是同步操作共享数据的代码,所以用同步来修饰的地方既不能过大(可能变成单线程),也不能过小(线程安全问题)
被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(this),可以在括号内指定锁Synchroized(Obj obj){},锁是obj
被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象(this)
作用的范围是整个静态方法,作用的对象是这个类的所有对象(类.class这个对象)
其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象(类.class这个对象)
class Single{
private static Single s = new Single();
private Single();
public static Single getInstance(){
return s;
}
}
**多线程并发问题:线程0判断完s=null,CPU切换执行权到线程1判断s=null后创建对象s,切换到线程0之后又创建对象s
class Single{
private static Single s = null;
private Single();
//加入synchronized解决多线程同步问题,但是影响了效率
public static /*synchronized*/ Single getInstance(){
if(s==null){
s = new Single();
return s;
}
}
}
加入synchronized(解决并发问题)
如上述代码注释部分
双重判断,减少判断锁的次数(解决加同步后效率低下问题)
减少判断锁的次数:顾名思义,也就是减少执行synchronized()这一行或synchronized修饰的方法
双重判断最核心的地方:第一个线程创建完对象之后,其他线程不用再判断锁(因为在第一个判断中就已经出去了)
class Single{
private static Single s = null;
private Single();
//双重判断
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null){
s = new Single();
return s;
}
}
}else return s;
}
}
生产者线程每生产一个资源,由消费者线程消费,然后生产者线程再生产……(一个生产者、一个消费者)
class Resource{
private String name;
public synchronized void set(String name) {
this.name = name;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
}
public synchronized void out() {
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
}
}
class Customer implements Runnable{
private Resource r ;
Customer(Resource r){
this.r = r;
}
public void run() {
while(true) {
r.set("面包");
}
}
}
class Producer implements Runnable{
private Resource r ;
Producer(Resource r){
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
public class PAndC {
public static void main(String[] args) {
Resource r = new Resource();
Producer p1 = new Producer(r);
Customer c1 = new Customer(r);
Thread t1 = new Thread(p1);
Thread t2 = new Thread(c1);
t1.start();
t2.start();
}
}
生产者生产多次,才到消费者消费多次
*解决:等待唤醒机制
通过flag标记,如果flag为真(消费者wait(),生产者执行并notify()),如果flag为假(生产者wait(),消费者执行并notify())
class Resource{
private String name;
private boolean flag = false;
public synchronized void set(String name) {
if(flag)try {wait();}catch(Exception e) {};
this.name = name;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
flag = true;
notify();
}
public synchronized void out() {
if(!flag)try {wait();}catch(Exception e) {};
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
flag = false;
notify();
}
}
**这三个方法必须使用在同步(synchronized)中,因为要标识这些方法所属的锁,通过锁来判断wait()或者notify()哪个线程池
wait():将调用此方法的线程临时存储到线程池中
notify():会唤醒线程池中任一 一个线程
notifyAll():会唤醒线程池中所有线程
*同一个锁上的notify(),只能唤醒该锁上的被wait()的线程
上述解决方法中,对资源对象(单例)锁上,调用set的进程0,调用out的进程1分别临时存储到线程池中,因为只有一个资源对象,所以可以判断是同一个线程池。
*问题:消费者唤醒的对象可能是消费者,也可能是生产者(我们需要的是唤醒不同方)
唤醒同方会导致生产两次 or 消费两次
*解决:while替代if的判断(多消费者、生产者必备while)
*新问题:替代之后导致死循环。eg:生产者唤醒生产者之后,占着资源,死循环(消费者还未将标识变换,本应是唤醒消费者的)
*解决:唤醒所有(notifyAll()),但是效率低,因为又唤醒了本方
*效率低解决办法:jdk1.5之后提供的java.util.concurrent.locks代替同步
Lock接口:lock()获得锁、unlock()释放锁 用于代替synchronized
Condition接口:await(),singal(),singalAll() 用于代替wait()、notify()、notifyAll()
*同操作系统中的P/V操作是一个概念,Condition对象就是信号量
class Resource{
private String name;
private boolean flag = false; //判断是否需要停止生产者或者消费者的标识
private Lock lock = new ReentrantLock();
private Condition p = lock.newCondition(); //生产
private Condition c = lock.newCondition(); //消费
public void set(String name) {
lock.lock();
try {
while(flag)try {p.await();}catch(Exception e) {};
this.name = name;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
flag = true;
c.signal();
}finally {
lock.unlock();
}
}
public void out() {
lock.lock();
try {
while(!flag)try {c.await();}catch(Exception e) {};
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
flag = false;
p.signal();
}finally {
lock.unlock();
}
}
}
stop():过时,因为执行之后会释放其拥有的所有锁,会引发其他问题,所以被弃用
*interrupt():将线程的冻结状态清除,让线程恢复到正常运行状态,因为是强制性的所以会自带异常
**将notify()比喻作唤醒一个睡觉的人;interrupt()就是一棍子打醒睡觉的人,打出来的包就是异常。
和一般线程(前台)一样,在启动线程之前需要(thread.setDaemon(true))就可以变成后台线程
*特点:线程结束方式:
默认优先级5,数字表示1-10,明显的优先级1,5,10
serPriority(Thread.MAX_PRIORITY);
线程组:可以对多个同组的线程,进行统一的操作(比如interrupt())
t1.join():主线程释放执行权,让其余的线程抢夺执行权,只有在t1执行完之后主线程才能获得执行权(处于冻结状态)
yield():释放CPU执行权,让其他线程有机会获取(自己也能再次获取)。让线程放缓,增加间隔性
new Thread(new Runnable(){
public void run(){
System.out.println("runnable run");
}
}){
public void run(){
System.out.println("subthread run");
}
}.start();
new Thread() {.....}创建了Thread的子类重写了父类的run方法
父类的run方法原本是:
class Thread{
private Runnable r;
Thread(Runnable r){
this.r = r;
}
public void run(){
if(r != null){
r.run();
}
}
public void start(){
run();
}
}
所以重写了之后,最后输出的结果的subthread run