java线程

749 阅读6分钟

概述


相关关系:

  • 程序:可理解为一组静态代码。
  • 进程:正在进行的程序(静态的代码,运行起来)。
  • 线程:正在执行程序中的小单元。

线程:

1、主线程 :系统线程

2、用户线程 :main

3、守护线程(精灵):比如GC(垃圾回收)

线程状态:

1、创建线程(new)

2、就绪状态(start())

3、执行状态(CPU分配run)

4、等待/挂起(wait)

5、-----异常/死亡(exception over)/(或重新回到就绪状态)(notify/notifyAll--唤醒)

基本操作

一 、创建线程

  • 方式一:继承Thread类

       1、 定义继承Thread;
      
       2、复写Thread类中的run方法;(目的:将自定义代码存储在run方法中,让线程运行(同时运行,抢夺资源))
      
       3、调用线程的start方法;(目的:启动线程,调用run方法)
    
  • 方式二:实现Runnable接口

      1、定义类实现Runnable接口;
      
      2、覆盖Runnable接口中的run方法;
      
      3、通过Thread类建立线程对象;
      
      4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法;
      
      5、调用Thread类的start方法,开启线程并调节Runnable接口子类的run方法;
    

实现方式和继承方式的区别:

  • 继承只能继承一个,具有局限性。
  • 继承Thread: 线程代码存放在子类run。
  • 实现runnable: 线程代码存放在接口的子类run。

应用举例:

  • 方式一:

      //1、继承Thread
          class Test extends Thread{
                private String name;
                Test(String name)
                {
                    this.name=name;
                }
                public void run(){     //2、run方法
                    for(int x=0;x<60;x++)
                    {
                        System.out.println(name+"----"+x);
            
                    }
                }
            }
            public class TestMain
            {
                public static void main(String[] args) {
                    Test q1=new Test("lisi");
                    Test q2=new Test("lisan");
                    q1.start(); //3、调用start
                    q2.start();
            
                }
            }
    
  • 方式二:

      - 
      class Ticket implements Runnable//1、实现Runnable接口
      {
          private int tick=100;
          public void run()//2、run方法
          {
              while(true)
              {
                  if(tick>0)
                      System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
              }
          }
      
      }
      class TestMain
      {
          public static void main(String[] args) {
              Ticket t=new Ticket();
            //3、创建线程
              Thread t1=new Thread(t);//4、传递参数
              Thread t2=new Thread(t);
              Thread t3=new Thread(t);
              Thread t4=new Thread(t);
              //5、调用start
              t1.start();
              t2.start();
              t3.start();
              t4.start();
      
      
          }
      }
    

二、获取线程对象及名称

  • 获取当前线程对象:Thread.currentThread()
  • 获取线程名称:getName()

多线程的安全问题:

原因:

当多条语句在操作一个共享数据,一个数据对多条语句只执行了一部分, 还未执行完时,另一个参与进来,导致错误。

解决方法:

  • 同步代码块:对于多条共享的语句,让一个线程先执行完。

      synchronized(对象)//相当于“锁”
      {
          需要被同步的代码
          }
    

多线程-同步函数

步骤:

1、明确哪些代码是多线程运行代码;

2、明确共享数据;

3、明确多线程运行代码中哪些语句是操作共享数据的;

多线程同步函数的锁是this:

函数需要被对象调用,那么函数都有一个所属对象引用,即this,所以同步函数的锁就是this.

死锁:(死锁程序要会写)

产生原因:同步中嵌套同步,锁却不同。


单例设计模式:

单例模式的定义与特点:

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

例如,Windows中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

在计算机系统中,还有Windows的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

单例模式有 3 个特点:

1、单例类只有一个实例对象;

2、该单例对象必须由单例类自行创建;

3、单例类对外提供一个访问该单例的全局访问点;

代码实现:

//饿汉式。
/*
class Single
{
	private static final Single s = new Single();
	private Single(){}
	public static Single getInstance()
	{
		return s;
	}
}
*/


//懒汉式(延迟加载)

class Single
{
	private static Single s = null;
	private Single(){}


	public static  Single getInstance()
	{
		if(s==null)//双重判断。提高效率
		{
			synchronized(Single.class)//静态没有this,锁是该类所属的字节码类对象
			{
				if(s==null)
					//--->A;
					s = new Single();
			}
		}
		return s;
	}
}

class SingleDemo 
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}

线程间通信:

为了解决安全问题。

多个线程操作同一个资源,但是操作的动作不同。

代码演示:

class Res
{
	String name;
	String sex;
	boolean flag = false;
}

class Input implements Runnable
{
	private Res r ;
	Input(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		int x = 0;
		while(true)
		{
			synchronized(r)
			{

				if(r.flag)
					try{r.wait();}catch(Exception e){}
				if(x==0)
				{
					r.name="mike";
					r.sex="man";
				}
				else
				{
					r.name="丽丽";
					r.sex = "女女女女女";
				}
				x = (x+1)%2;
				r.flag = true;
				r.notify();
			}
		}
	}
}

class Output implements Runnable
{
	private Res r ;
	
	Output(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			synchronized(r)
			{
				if(!r.flag)
					try{r.wait();}catch(Exception e){}
				System.out.println(r.name+"...."+r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}


class  InputOutputDemo
{
	public static void main(String[] args) 
	{
		Res r = new Res();

		Input in = new Input(r);
		Output out = new Output(r);

		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);

		t1.start();
		t2.start();
	}
}


//notifyAll();

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

都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。

**为什么这些操作线程的方法要定义Object类中呢?**
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁。

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

等待唤醒机制:

wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

生产者、消费者:

    对于多个生产者和消费者?
    为什么要定义while判断标记?
    原因:让被唤醒的线程再一次判断标记。
    
    
    为什么定义notifyAll?
    因为需要唤醒对方线程。
    因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

停止线程:

    一般情况:
    run方法结束(开启多线程运行,运行代码通常是循环结构,所以控制循环即可)
    
    特殊情况:
    当线程处于了冻结状态。
    就不会读取到标记。那么线程就不会结束。
    
    当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。
    强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
    
    Thread类提供该方法 interrupt();

守护线程:(set .Deamon)

[注意:]启用前调用;线程中只剩守护线程时,java退出虚拟机

Join方法:

等待线程终止:

            当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
        
            join可以用来临时加入线程执行。

优先级:yeild方法

即抢资源的频率,最高10,默认5