黑马程序员——多线程



 

 ------- android培训java培训、期待与您交流! ----------

——————————————————————————————————————————————— 


21、多线程

|——概念:
线程就是进程中一个负责程序执行的控制单元(执行路径)。
进程中可以同时有多个执行路径
|——多核处理器可以真正意义上的多线程
|——JVM启动时,就启动了多个线程
有一个垃圾回收机制,显式 public static void gc()运行垃圾回收器。
protected void finalize()
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize 方法,以配置系统资源或执行其他清除。
java中垃圾回收器由JVM直接调用。显式的调用System.gc();方法也不确定垃圾回收机制会被执行
class Demo {
protected void finalize() throws Throwable {
System.out.println("finalize called.");
}
}


class ThreadDemo{
public static void main(String []args){
new Demo();
new Demo();
System.gc();//显式的调用java的垃圾回收器
new Demo();
System.out.println("Main Thread end.");
}
}


//运行结果可能会出现以下多种情况:
————————————————————————————————————————————————
Main Thread end.
finalize called.//main线程执行完执行,这里主线程结束,垃圾回收器的线程还没有结束
————————————————————————————————————————————————
finalize called.
finalize called.
Main Thread end.//调用后直接执行
————————————————————————————————————————————————
Main Thread end.//程序结束也没有执行

|——线程的创建(示例)
注-->注-->线程可以由操作系统默认创建。JVM创建的线程都定义在了主函数中
|--创建新执行线程有两种方法。
|---一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
|---另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。
然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

|---自定义线程
class MyThread extends Thread
class MyThread implements Runnable
|---开启线程的方法(对应上边的两个自定义线程)
new MyThread().start();
new Thread(new MyThread()).start();//这里只能这样开启,不能像Thread的子类那样
|--开启自定义线程要继承Thread类,并复写run方法。
注-->注--> 线程对象调用start()方法, 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
|--线程对象调用run方法,没有开启线程
|--线程创建的时候就已经确定了线程的编号。要获得线程开启的线程,使用Thread.currentThread.getName();
这里可以通过直接调用run方法看出,没有开启线程也有编号。
|——线程的状态:
被创建 运行 消亡 冻结(sleep())
start run结束 wait(),唤醒用notify();

|--CPU的执行资格:可以被CPU的处理,在处理队列中排队
CPU的执行权:正在被CPU的处理

|——Runnable接口的出现就是将任务封装成一个对象。Thread 类也是Runnable接口的实现类
|——继承Thread类和实现Runnable接口的区别:
|--将线程的任务从线程的子类中分离出来,进行了单独的封装
按照面向对象的思想将任务封装成对象;
|--避免了java中类的单继承的局限性
|--所以一般情况下多用实现Runnable接口的方法,将任务封装成对象。符合了面向接口(面向对象)编程的思想。


|——示例1
class Demo extends Thread{
private String name;
Demo(String name){
this.name = name;
}
public void run(){
for(int x=0;x<10;x++)
System.out.println(name+"..."+x);
}
}


class ThreadDemo{
public static void main(String []args){
new Demo("zhangsan").start();
new Demo("lisi").start();
}

//运行结果可能会出现以下多种情况:
————————————————————————————————————————————————
zhangsan...0 zhangsan...0
zhangsan...1 zhangsan...1
zhangsan...2 lisi...0
zhangsan...3 lisi...1
zhangsan...4 lisi...2
zhangsan...5 zhangsan...3
lisi...0 zhangsan...4
lisi...1 lisi...3
.. ..


|——示例2 //实现Runnable接口的类。注意开启线程的方法
// new Thread(new MyThread("xxxx")).start();
|--步骤:
定义类实现Runnable接口。
实现run方法,将线程的任务代码封装到run方法中。
通过Thread类创建线程对象,并将Runnable接口的实现类作为构造函数的参数进行传递


class MyThread implements Runnable{
private String name;
MyThread(String name){
this.name = name;
}
public void run(){
for(int x=0;x<10;x++)
System.out.println(name+"..."+Thread.currentThread().getName());
}
}

注-->注-->new MyThread().start();是错误的,编译就出现错误。这样不是开启线程
(向上转型,不能调用接口中没有的方法,也就是实现类的特有方法)
|——示例3 //Thread先找自己的run方法,如果自己需要开启的线程代码在
// 一个实现了Runnable接口的类中,
new Thread(new Runnable(){
实现Runnable接口的run方法;
})
{
重写Thread父类的run方法 //先找这里,如果找不到,就会找实现Runnable接口的run方法
}.start();

new Thread(new Runnable(){
public void run(){
System.out.println("runnable Thread called");
}
}){
public void run(){
System.out.println("My Thread called.");// 匿名线程类中有自己的run方法,
}//这个时候线程会先去找Thread(父类)run方法,如果找不到,会找Runnable子类的run方法
}.start();


|——示例4 卖票实例
|--以下两种都没有实现线程间的同步问题
1继承Thread类
——————————————————————————————————————————————————————————————
class MyThread extends Thread{//继承Thread类
private static int tickets = 100;//为了使多个线程共享着100张票,
// 将tickets设置为静态的(共享数据)
public void run(){
while(tickets>=0){
try{
Thread.sleep(1);
}catch(Exception e){}
System.out.println("tickets left "+(tickets--)+" ,Thread name : "+Thread.currentThread().getName());
}
}
}
class ThreadDemo{
public static void main(String []args)throws Exception{
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
mt1.start();
mt2.start();
}


2实现了Runnable接口 //这样的实现类称为线程任务类
——————————————————————————————————————————————————————————————


class MyThread implements Runnable{ //这里实现了Runnable接口
private int tickets = 100;//这里可以共享也可以不共享。
public void run(){
while(tickets>=0){ //假设有两个线程都进入到了这里,条件也判断过了,
// Thread-0还没有迹象执行,CPU切换到了Thread-1上。下次
// 切换过来的时候就不在进行判断
System.out.println("tickets left "+(tickets--)+
" ,Thread name : "+Thread.currentThread().getName());
}
}
}
//在主方法中这样开启线程
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();

|——线程安全问题产生的原因:
多个线程在操作共享数据
操作共享数据线程代码有多条
|——解决线程安全问题:(同步代码块,用关键字 synchronized)
将需要同步的代码封装起来。只有当当前线程将这些代码执行完毕后,其他线程才能执行
|--同步的前提:
有多个 线程并使用同一个锁。
|--同步函数:在函数名前加 synchronized 同步函数的锁是 this
public synchronized void show(){
if(tickets>0)
// System.out.println("saled tickets:"+(100-tickets));
System.out.println("tickets left "+(tickets--)+
" ,Thread name : "+Thread.currentThread().getName());
}
public void run(){

while(true){
show();
}
}

注-->注-->|--一个普通方法和一个静态方法同步就需要用到字节码
静态方法就没有 this 需要用到类的字节码
|---获得字节码的两种方式:
对象.getClass();
类.class;

|--单例设计模式的线程安全问题:
|---饿汉式没有问题
|---懒汉式有线程安全问题

解决单例设计的第一种模式
同步函数:降低了效率
______________________________________________________
class Single{
private static final Single s = null;
public synchronized Single getInstance(){//
if(s == null)
s = new Single();
return s;
}
}

解决单例设计的第二种模式提高效率
同步代码块
______________________________________________________

class Single{
private static final Single s = null;
public synchronized Single getInstance(){//
if(s == null)
synchronized(Single.class){
if(s == null)
s = new Single();
}
return s;
}
}




|---死锁示例:
解析:
示例中有为了解决问题,又开启了两个线程,
class Demo implements Runnable{
boolean flag = true;
private int num = 100;
String s = "";

public synchronized void show(){
synchronized(s){ //Thread-0要进入这里的时候发现Thread-1拿着这个锁,等待
if(num>0){
System.out.println(Thread.currentThread().getName()+"....show.."+num--);
}
}
}
public void run(){
if(flag){ //Thread-0满足条件,拿到了s这个锁。这个时候可能Thread-1已经拿到了this这个锁。
while(num>0){ //加入两个线程各自拿到一个锁后,状态改变。两个线程还没有释放掉锁拿到的锁
synchronized(s){
show();
}
}
}
else{
while(num>0){
show(); //要运行到这里的时候,一看Thread-1拿着这个this锁。等待
}
}
}
}


public class ThreadDemo{
public static void main(String []args){
Demo d = new Demo();
new Thread(d).start();
try{Thread.sleep(1);}catch(InterruptedException e){}
d.flag = false;
new Thread(d).start();
}
}



死锁清晰实例
————————————————————————————————————————————————————————————————————

class MyLock{
public static final MyLock locka = new MyLock();
public static final MyLock lockb = new MyLock();
}


class Demo implements Runnable{
boolean flag ;
Demo(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"..if ..locka..");
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"..if ..lockb..");
// try{Thread.sleep(10);}catch(InterruptedException e){}
}
}
}
else{
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"..else ..lockb..");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"..else ..locka..");
}
}
}
}
}


public class ThreadDemo{
public static void main(String []args){
Demo d1 = new Demo(true);
Demo d2 = new Demo(false);
new Thread(d1).start();
new Thread(d2).start();

}
}

|——线程间通信 (等待唤醒机制)
|--wait(); 让线程处于冻结状态,被wait的线程会被存储到线程池中
notify(); 唤醒线程池中的一个线程。(任意)
notifyAll();唤醒线程池中的所有线程
|--这些方法都 必须定义在同步中,
因为这些方法是用于操作线程状态的方法,
必须要明确到底操作的是哪个锁。
class Resource{
String name;
String sex;


boolean flag = true;
public synchronized void put(String name,String sex){
if(!flag){
try {
wait();//当标志位为false,线程wait这里其实是r.wait();
} catch (InterruptedException e1){}
}
this.name = name;
this .sex = sex;

flag = false; //执行完后,将标志位状态改变。
notify(); //并且唤醒一个线程这里其实是r.notify();
}

public synchronized void get(){
if(flag){
try {
wait();    //当标志位为true,线程wait。这里其实是r.wait();
} catch (InterruptedException e1){}
}
System.out.println(name+"....."+sex);

flag = true;//执行完后,将标志位状态改变。
notify();//并且唤醒一个线程这里其实是r.notify();

}


class Input implements Runnable{
Resource r ;
private int x=0;
Input(Resource r){
this.r = r;
}
public  void run(){   //这里有一个线程任务
while(true){
if(x == 0){
r.put("zhangsan","nan");//这里可以将同步代码封装
}else{
r.put("lisi","nv");
}
x = (x+1)%2;
}
}
}


class Output implements Runnable{
Resource r ;
Output(Resource r){
this.r = r;
}

public void run(){ //这里有一个线程任务
while(true){
r.get(); //这里可以将同步代码封装
}
}
}


经典 多生产者多消费者问题
——————————————————————————————————————————————————————————————————
语言描述:
两个知识点:
用while不用if判断标记   ,解决了可能出现的一个重复生产,或者一个重复消费的情况
用notifyAll()不用notify(); //避免生产线程唤醒生产线程(这样在while下一判断,又跑线程池里了),
// 避免消费线程唤醒消费线程。这是产生死锁的情况
这里有四个线程:
Thread-0和Thread-1作为生产者:
Thread-2和Thread-3作为消费者:
flag一开始是false,当Thread-0来到线程任务后,判断flag,flag为false。Thread-0生产了一只烤鸭。
此时notify()或是notifyAll()是没有唤醒任何线程池中的线程。
将flag设置为true。如果用notify(),任意唤醒一个线程。可能是Thread-1;
假设这个时候:Thread-1也来到线程生产任务。进行等待,Thread-1进入线程池。
这个时候flag依然还是true,Thread-0进入生产线程。判断后也进入等待,Thread-0也会进入线程池。
此时生产线程都没有了执行权
同理,消费者也是一样的。

if和while
if(flag)
try {wait();} catch (InterruptedException e){}
Thread-0 到这里以判断,flag为false。直接往下运行。生产烤鸭1
flag = true;

Thread-2 判断一次,flag为true,向下运行,消费烤鸭1
flag = false;
Thread-3判断一次,flag为false,Thread-3被冻结
Thread-1到这里判断,flag为false。
flag = true;
Thread-2 判断一次,flag为true。唤醒了Thread-3,这个时候就不在进行判断。直接消费一次。
这样就出现了同一只烤鸭消费了两次

/* // Thread-1到这里判断,flag为true。Thread-1冻结。
// 这个时候线程池中只有Thread-1 ,唤醒了Thread-1。
// Thread-1 直接往下运行(if不在进行判断),生产烤鸭2
flag = true;
假设这个时候又来了Thread-0 ,判断后,wait()Thead-0进入线程池。
Thread-1来了后,flag为true,向下运行,消费烤鸭2 */


class Resource{
private String name ;
private int count =1;
private boolean flag = false;

public synchronized void put(String name){
while(flag) //这里用while不用if,目的是在线程唤醒后再判断一次
try {wait();} catch (InterruptedException e){} //Thread-
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...Producer..........s"+this.name);
flag = true;
notifyAll();
}

public synchronized void get(){
while(!flag)
try {wait();} catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...xiaofeile...."+name);
flag = false;
notifyAll();
}
}
// 生产者线程任务类
class Producer implements Runnable{
Resource r ;
Producer(Resource r){
this.r = r;
}

public void run(){
while(true)
r.put("kaoya");
}
}
 
//消费者线程任务类
class Constumer implements Runnable{
Resource r ;
Constumer(Resource r){
this.r = r;
}

public void run(){
while(true)
r.get();
}
}
 
public class ProducerDemo{
public static void main(String []args){
Resource r = new Resource();
Producer p1 = new Producer(r);
Producer p2 = new Producer(r);
Constumer c1 = new Constumer(r);
Constumer c2 = new Constumer(r);

new Thread(p1).start();
new Thread(p2).start();
new Thread(c1).start();
new Thread(c2).start();
}

 
|——java 1.5的新特性

Lock 实现提供了比使用 synchronized 
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。 
 
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 
实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 
Object 监视器方法的使用

对比以上示例:
使用了一个锁,两个监视器。目的是,实现线程间的同步(互斥)。唤醒机制,只唤醒需要监视器中的线程对象
public void put(String name){
lock.lock(); //直接在这里加了锁,相当于家里synshronized将函数同步
try{
while(flag) //这里使用的是监视器con1的await()方法
try {con1.await();} catch (InterruptedException e){ e.printStackTrace();}
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...Producer..........s"+this.name);
flag = true;
// notifyAll();
con2.signal(); //这里使用的是监视器的signal,唤醒消费者线程(任意一个)。
}
finally{
lock.unlock(); //锁用完了就要释放掉
}

}

public void get(){
lock.lock();
try{
while(!flag) //这里使用的是监视器con2的await()方法
try {con2.await();} catch (InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"...xiaofeile...."+name);
flag = false;
// notifyAll();
con1.signalAll();//这里使用的是监视器的signal,唤醒生产者线程(任意一个)。
}
finally{
lock.unlock();
}
}
}
 
上边示例中出现了Lock接口:替代了同步代码块或者同步函数。将同步的隐式操作变成显示锁,
并且更加灵活。(可以使用多组监视器)

|--守护线程(幽灵线程)
后台线程和前台线程同样都开启。前台线程必须手动结束,后台线程在前台线程结束的情况下,后台线程也会自动消失

 

 ------- android培训java培训、期待与您交流! ----------

——————————————————————————————————————————————— 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值