多线程总结

 

进程:正在进行中的程序(直译)。

进程其实对应一个应用程序在内存中的空间。

线程:就是进程中一个负责程序执行的执行路径

一个进程中可以有多个执行路径,称之为多线程。

一个进程中至少要有一个线程。

开启多个线程是为了同时运行多部分代码。

每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。

多线程的好处与弊端:

好处:解决了多部分同时运行的问题。

弊端:线程太多 效率降低。

cpu的快速切换依赖于时间片。

JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。

1.执行main函数的线程。

该线程的代码都定义在main函数中。

2.负责垃圾回收的线程。

finalize:当垃圾回收机制确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

如何创建一个线程呢?

创建新执行线程有两种方法。

一种方法是将类声明为Thread的子类。该子类应该重写Thread类的run方法。

-----------------------------------------------------

创建线程方式一:继承Thread类。

步骤:

1.定义一个类,继承Thread类。

2.覆盖Thread类中的run方法。

创建线程的目的是为了开启一条执行路径,去运行指定的代码,和其他代码实现同时运行 。

而运行的指定代码就是这个执行路径的任务。

JVM创建的主线程的任务都定义在了主函数中。

而自定义的线程的任务在哪儿呢?

Thread类用于描述线程,线程是需要任务的,所以Threaf也有对任务的描述。

这个任务就通过Thread类中的run方法来体现,也就是说,run方法就是封装

自定义线程运行任务的函数。

run方法中定义的就是线程要运行的任务代码。

开启线程是为了运行指定代码,所以只有继承Thread类,并覆写run方法。

将运行的代码定义在run方法中。

3.创建Thread类的子类对象,创建线程。

4.调用start方法开启线程。并调用线程的任务run方法执行。

Thread类中的方法线程名称。

可以通过Thread类中的getName获取线程的名称。Thread-编号(从0开始)

主线程的名字是main

currentThread返回当前正在执行的线程对象的引用。

Thread.currentThread().getName()返回当前正在运行的线程的名称。

 

class Demo extends Thread{
 private String name;
 Demo(String name){
  //super(name);Thread中的构造函数,可以给线程自定义名字。
  this.name = name;
 }
 public void run(){
  for (int i = 0; i < 10; i++) {
   for (int j = -9999999; j < 99999999; j++) {}   
   System.out.println(name+"..."+i+".....name="+Thread.currentThread().getName());
  }
 } 
}
public class ThreadDemo1 {
 public static void main(String[] args) {
  Demo d1 = new Demo("旺财");
  Demo d2 = new Demo("xiaoqiang");
  d1.start();//开启线程,调用run方法
  d2.start();
 }
}


-----------------------------------------------------

准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。

通过接口的形式完成。

 创建线程的第二种方式:实现Runnable接口。

 步骤:

 1.定义类实现Runnable接口。

 2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。

 3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递。

为什么呢?因为线程的任务都封装在Runnable接口子类对象的run方法中,

所以要在线程对象创建时就必须明确要运行的任务。 

 4.调用线程对象的start方法开启线程。

Runnable仅仅是将线程的任务进行了对象的封装。

实现Runnable接口的好处:

1.将线程的任务从线程的子类中分离出来,进行了单独的封装。

按照面向对象的思想将任务封装成对象。

2.避免了java单继承的局限性。

所以,创建线程的第二种方式较为常用。

 

class Demo2 implements Runnable{
 public void run(){
  show();
 }
 public void show(){
  for (int i = 0; i < 15; i++) {
   System.out.println(Thread.currentThread().getName()+"..."+ i );
  }
 } 
}
public class RunnableDemo2 {
 public static void main(String[] args) {
  Demo2 d1 = new Demo2();
  Demo2 d2 = new Demo2();
  Thread t1 = new Thread(d1);
  Thread t2 = new Thread(d2);
  t1.start(); 
  t2.start();
 }
}


---------------------------------------------------

线程安全问题产生的原因:

1.多个线程在操作共享的数据。

2.操作共享数据的线程代码有多条。

当一个线程在指向操作共享数据的多条代码过程中,其他线程参与了运算,

就会导致线程安全问题。

解决思路:

就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,

其他线程不可以参与运算。

必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

在java中,用同步代码块就可以解决这个问题。

同步代码块的格式:

synchronized(对象){

需要被同步的代码;

}

同步的好处和弊端。

同步的好处:解决了线程的安全问题。

同步的弊端:相对降低了程序的效率,因为同步外的线程都会判断同步锁。

同步的前提:

同步中必须有多个线程,并使用同一个锁。

 

同步函数的锁是this.

同步函数和同步代码块的区别:

同步函数的锁是固定的this。

同步代码块的锁是任意的对象。

建议使用同步代码块。

静态的同步函数使用的锁是:该函数所属的字节码文件对象。

可以用getClass获取,也可以用 当前类名.class表示。

例如:Class clazz1 = t.getClass();

Class clazz2 = Ticket2.class;

class Ticket2 implements Runnable {
 private int num = 100;
 Object obj = new Object();
 boolean flag = true;
 public void run(){
  if(flag)
   while(true){
    synchronized(this){//Ticket2.class或者this.getClass()
     if(num>0){
      try {
       Thread.sleep(10);
      } catch (InterruptedException e) { 
      }
      System.out.println(Thread.currentThread().getName()+"...code..."+num--);
     }
    }
   }
  else
   while(true)
    show();
 }
 public synchronized void show(){
  if(num>0){
   try {
    Thread.sleep(10);
   } catch (InterruptedException e) { 
   }
   System.out.println(Thread.currentThread().getName()+"...func..."+num--);
  }
 }
}
public class SynchroinzedFunctionLockDemo5 {
 public static void main(String[] args) {  
  Ticket2 t = new Ticket2();
  Thread t1 = new Thread(t);
  Thread t2 = new Thread(t);
  t1.start();
  try {
   Thread.sleep(10);
  } catch (InterruptedException e) {
  }
  t.flag = false;
  t2.start();
 }
}


---------------------------------------------------

死锁:常见情景之一:同步的嵌套。同步中还有同步。(面试中可能会出现)

class Test implements Runnable{
 private boolean flag;
 Test(boolean flag){
  this.flag = flag;
 }
 public void run(){
  if(flag){
   while(true){
    synchronized(MYlock.locka){
     System.out.println("if...locka...");
     synchronized(MYlock.lockb){
      System.out.println("if...lockb...");
     }
    }
   }
  }else{
   while(true){
    synchronized(MYlock.lockb){
     System.out.println("else...lockb...");
     synchronized(MYlock.locka){
      System.out.println("else...locka...");
     }
    }
   }
  }
 }
}
class MYlock{
 public static final Object locka = new Object();
 public static final Object lockb = new Object();
}
public class DeadLockDemo8 {
 public static void main(String[] args) {
  Test a = new Test(true);
  Test b = new Test(false);  
  Thread t1 = new Thread(a);
  Thread t2 = new Thread(b);
  t1.start();
  t2.start();
 }
}


---------------------------------------------------

线程间通讯:

多个线程在处理同一资源,但是任务却不同。

等待唤醒机制:

涉及的方法:

1.wait();让线程处于冻结状态,被wait的线程会被存储到线程池中。

2.notify();唤醒线程池中的任意一条线程。

3.notifyAll();唤醒线程池中所有的线程。

这些方法都必须定义在同步中。

因为这些方法是用于操作线程状态的方法,

必须要明确到底操作的是哪个锁上的线程。

r.wait();  r.notify();

 问设么操作线程的方法,wait notify  notifyAll定义在了Object中。

 因为这些方法是监视器的方法。监视器其实就是锁。

 锁可以是任意的对象,而任意的对象调用的方法一定定义在Object中。

多生产者多消费者问题。 

if判断标记只有一次,会导致不该运行的线程运行了,会出现数据错误的情况。

while判断标记,解决了线程获取执行权后,是否要运行。

notify:只能唤醒一个线程,如果唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。

notifyAll 解决了本方线程一定会唤醒对方线程的问题。

 

class Resource2{
 private String name;
 private int count = 1;
 private boolean flag = false;
 public synchronized void set(String name){
  while(flag)
   try{this.wait();}
   catch(InterruptedException e){}
  this.name = name + count;
  count++;
  System.out.println(Thread.currentThread().getName()+"...生产者"+this.name);  
  flag = true;
  notifyAll();
 }
 public synchronized void get(){
  while(!flag)
   try{this.wait();}
   catch(InterruptedException e){}
  System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
  flag = false;
  notifyAll();
 }
}
class Producer implements Runnable{
 Resource2 r;
 Producer(Resource2 r){
  this.r = r;
 }
 
  public void run(){
  while(true){
   r.set("烤鸭");
  }
 }
}
class Consumer implements Runnable{
 
 Resource2 r;
 Consumer(Resource2 r){
  this.r = r;
 }
 public void run(){
  while(true){
   r.get();
  }
 }
}
public class ConsumerDemo2 {
 public static void main(String[] args) {
  Resource2 r = new Resource2();
  
  Producer pro = new Producer(r);
  Consumer con = new Consumer(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();  
 }
}
 


-----------------------------------------------------------------

JDK1.5解决办法:

jdk1.5以后将同步和锁封装成了对象。

并将操作锁的隐式方式定义到了该对象中,将隐式的动作变成了显示动作。

Lock接口:出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成了显示锁操作。

同时更为灵活,可以一个锁上加上多组监视器。

lock():获取锁

unlock():释放锁,通常需要定义在finally代码块中。

Condition接口:出现替代了object中的wait  notify  notifyAll方法。

将这些监视器方法单独进行了封装,变成Condition监视器对象。

可以和任意的锁进行组合。

await();

signal();

signalAll();

 

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource3{
 private String name;
 private int count = 1;
 private boolean flag = false;
 //创建一个锁对象。
 Lock lock = new ReentrantLock();
 //通过已有的锁获取该锁上的监视器对象。
// Condition c = lock.newCondition();
 //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
 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){}
   this.name = name + count;
   count++;
   System.out.println(Thread.currentThread().getName()+"...生产者"+this.name);  
   flag = true;
   consumer_con.signal();
  }
  finally{
   lock.unlock();
  }
 }
 public void get(){
  lock.lock();
  try{
   while(!flag)
    try{consumer_con.await();}
    catch(InterruptedException e){}
   System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
   flag = false;
   producer_con.signal();
  }
  finally{
   lock.unlock();
  }
 }
}
class Producer2 implements Runnable{
 Resource3 r;
 Producer2(Resource3 r){
  this.r = r;
 } 
  public void run(){
  while(true){
   r.set("烤鸭");
  }
 }
}
class Consumer2 implements Runnable{ 
 Resource3 r;
 Consumer2(Resource3 r){
  this.r = r;
 }
 public void run(){
  while(true){
   r.get();
  }
 }
}
public class NewJDKDemo3 {
 public static void main(String[] args) {
  Resource3 r = new Resource3();
  
  Producer2 pro = new Producer2(r);
  Consumer2 con = new Consumer2(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();  
 }
}


-----------------------------------

多线程中的一点小细节:

wait和sleep的区别。

1.wait可以指定之时间也可以不指定。

  sleep必须指定之间。(sleep一定会醒,wait不一定会醒)

2.在同步中时,对于cpu的执行权和锁的处理不同。

  wait:释放执行权,释放锁。

  sleep:释放执行权,不释放锁。

同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行。

停止线程:

1.stop方法(已过时)。

2.run方法结束。

怎么控制线程的任务结束呢?

任务中都会有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

但是如果线程处于冻结状态,无法读取标记,如何结束呢?

可以使用interrupt();方法将线程从冻结状态强制恢复到运行状态中来,

让线程具备cpu的执行资格。

但是强制动作会发生InterruptedException异常,记得要处理。

setDaemon()当正在运行的线程都是守护线程时,虚拟机退出。

如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。

临时加入一个线程运算是,可以使join方法。

t1.join();//t1线程要申请加入进来运行。主线程会释放执行资格(冻结状态),t1结束后再执行。

哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态,

join 线程结束后恢复

toString()线程的字符串表现形式。

线程优先级在1到10之间10个数。

优先级越大被执行到的几率越大。

t1.setPriority(Thread.MAX_PRIORITY);设置线程t1的优先级为10.

默认优先级是5

线程组:ThreadGroup

yield()方法  可以使线程暂停,暂时释放执行权。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值